【问题标题】:Like Java, should Object.call be the first call in Javascript constructor too?像 Java 一样,Object.call 也应该是 Javascript 构造函数中的第一个调用吗?
【发布时间】:2015-09-16 20:53:37
【问题描述】:

我正在用 Javascript 编写一些 OOP 代码,继承的设计提出了一个问题。像Java一样,超级构造函数调用应该是Javascript子类构造函数的第一行吗?

var Effect = function (container, height, width) {};

var XEffect = function (container, height, width, resource) {
    Effect.call(container, height, width ); // really necessary to be first line?
};

【问题讨论】:

  • 应该是,如果构造函数要做的第一件事是运行另一个构造函数。如果这不是你想要的,那么它不应该是。 Effect 构造函数是否执行对XEffect 实例至关重要的操作?如果是这样,请调用它。
  • JS 中的 .call 并没有像你想象的那样做。 .call 调用一个函数,其中第一个参数绑定为函数内部的this,所有其余参数作为参数传入。它与继承本身无关,但如果您将this 作为您的this 值传入它可以在继承模式中使用(感到困惑吗?)。
  • 应该是Effect.call(this, container, height, width);

标签: javascript oop inheritance constructor


【解决方案1】:

引用 Douglas Crockford 的话:“JavaScript 有一个无类的对象系统,其中对象直接从其他对象继承属性。这确实很强大,但是对于受过经典训练的程序员来说是不熟悉的。如果您尝试直接应用经典设计模式对于 JavaScript,你会感到沮丧。但如果你学会使用 JavaScript 的原型特性,你的努力将会得到回报。"

查看Object.prototype,了解 JavaScript 中如何处理继承。

在您的代码 sn-p 中,Effect.call(container, height, width); 将简单地评估为 undefined,除非您事先明确定义它。要调用 Effect 函数的功能,只需将其调用为 Effect()

【讨论】:

    【解决方案2】:

    这完全取决于 Effect 构造函数的作用。

    当您到达构造函数中的第一行代码时,对象及其原型链已经设置完毕。也就是说,在XEffect的构造函数设置之前,this已经存在,其__proto__已经设置为XEffect.prototype。所以你的对象已经是一个XEffect,它继承自Effect

    但是,到目前为止,EffectXEffect 中的代码还没有运行XEffect 代码将运行,因为它是构造函数的一部分,但 Effect 不会运行,除非您告诉 JavaScript 这样做。如果你想明确地调用Effect,你可以。正如您所描述的,Effect.call(this, ...)Effect.apply(this, []) 可能是您想要这样做的方式。

    问题是 Effect 在你告诉它运行时运行:不迟,不早。如果Effect 写入对象的某些属性,然后XEffect 中的代码写入这些相同的属性,XEffect 获胜:这些是对象结束时将具有的值。如果你颠倒这个顺序——写入XEffect 中的属性,然后调用Effect,它会写入相同的属性——然后Effect 获胜。

    如果您习惯于以“基于类”的方式编写代码,那么首先调用Effect 可能最接近您的需要。它写入对象的属性,然后XEffect(可能)覆盖它们。但是,如果您出于某种原因希望 Effect 获胜,您就没有必须这样做。

    【讨论】:

      【解决方案3】:

      就我个人而言,我不喜欢用 Java 和 C++ 等语言实现的面向对象系统。我最喜欢的面向对象系统是由Fortress programming language 实现的。例如,考虑以下 Fortress 代码,取自 Guy Steele(又名 The Great Quux)撰写的著名博文 Why Object-Oriented Languages Need Tail Calls

      trait IntSet
        getter isEmpty(): Boolean = false
        adjoin(x: ZZ): IntSet = AdjoinObject(self, x)
        union(other: IntSet): IntSet = UnionObject(self, other)
        contains(y: ZZ): Boolean
      end
      
      object AdjoinObject(s: IntSet, x: ZZ) extends IntSet
        contains(y: ZZ) = (y = x) OR: s.contains(y)
      end
      
      object UnionObject(s1: IntSet, s2: IntSet) extends IntSet
        isEmpty: Boolean = s1.isEmpty AND s2.isEmpty
        contains(y: ZZ) = s1.contains(y) OR: s2.contains(y)
      end
      
      object EmptyObject extends IntSet
        isEmpty: Boolean = true
        contains(y: ZZ) = false
      end
      

      Fortress 中的对象定义引入了对象构造函数。这些构造函数可以是空的(例如EmptyObject)或参数化的(例如AdjoinObjectUnionObject)。

      特征类似于abstract base classes。因此,您不能直接创建特征的实例。相反,您需要创建一个实现 trait 接口的具体对象。

      请注意,在这种情况下,您永远不需要调用超类构造函数,因为没有超类构造函数。所有超类(特征)都是抽象的。它们没有状态,因此不需要初始化。对象的状态封装在对象构造函数中。这很重要,因为它提升了data abstraction,这是面向对象程序的一个重要属性。

      那么这个 Fortress 代码在 JavaScript 中的外观如何?

      有很多方法可以在 JavaScript 中编写面向对象的代码。但是,我最喜欢以下 Fortress 风格的面向对象的 JavaScript 代码:

      class IntSet {
          get isEmpty() { return false; }
          adjoin(x) { return AdjoinObject(this, x); }
          union(other) { return UnionObject(this, other); }
          contains(y) { throw new Error("Abstract method invocation"); }
      }
      
      const AdjoinObject = (s, x) => new class extends IntSet {
          contains(y) { return y === x || s.contains(y); }
      }
      
      const UnionObject = (s1, s2) => new class extends IntSet {
          get isEmpty() { return s1.isEmpty && s2.isEmpty; }
          contains(y) { return s1.contains(y) || s2.contains(y); }
      }
      
      const EmptyObject = new class extends IntSet {
          get isEmpty() { return true; }
          contains(y) { return false; }
      }
      
      const Empty = EmptyObject;
      
      const example = Empty.adjoin(1).adjoin(2).union(Empty.adjoin(3));
      
      console.log(example.contains(1)); // true
      console.log(example.contains(2)); // true
      console.log(example.contains(3)); // true
      console.log(example.contains(4)); // false

      简单的solid JavaScript 中的面向对象编程。此外,您无需担心超类构造函数。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-09-22
        • 1970-01-01
        • 2015-07-02
        • 2016-07-03
        相关资源
        最近更新 更多