4

对不起,但我真的不知道如何更准确地指定问题。在这个例子中可以看到最常见的继承方式:

function Shape() {
  this.x = 0;
  this.y = 0;
}

//superclass method
Shape.prototype.move = function(x, y) {
    this.x += x;
    this.y += y;
    console.info("Shape moved.");
};

// Rectangle - subclass
function Rectangle() {
  Shape.call(this); //call super constructor.
}

//subclass extends superclass
Rectangle.prototype = new Shape();

var rect = new Rectangle();

rect.move(2, 1);

我担心以下代码段:

//subclass extends superclass
Rectangle.prototype = new Shape();

直接连接到父原型属性不是更好吗。

Rectangle.prototype = Shape.prototype;

据我所知,所有对象都是直接从 Object 继承的。他们的原型不是连接到 Object 的某个实例,而是连接到它的原型属性?

在上述情况下 Shape.call(this); 将复制属性(或类似的东西),我们只需要继承 Shape.prototype 中的某某方法。或者我可能错过了什么?

4

2 回答 2

4

我担心以下代码段:

//subclass extends superclass
Rectangle.prototype = new Shape();

你应该是,这是一个常见的模式,但不是一个很好的模式,因为你Shape 同时使用创建原型初始化实例。它应该做一个或另一个。(例如,考虑一下,如果你需要提供Shape参数,你会怎么做?你会使用什么参数来创建Rectangle.prototype?)

但是你也不想要Rectangle.prototype = Shape.prototype,因为那样你就不能添加东西Rectangle.prototype(因为它指向同一个对象Shape.prototype,你也可以将它们添加到Shape实例中)。

所以我们想要的是创建一个新对象,Shape.prototype用作它的原型(例如,继承自Shape.prototype),就像通过new Shapedo 创建的对象一样,但无需调用Shape创建它;然后将该新对象用作Rectangle.prototype.

从 ECMAScript5 开始,我们可以使用Object.create

Rectangle.prototype = Object.create(Shape.prototype);

Object.create创建一个新对象并将您给它的第一个参数分配为对象的底层原型。

完成后,虽然不是绝对必要的,但我们还应该constructor在新对象上设置属性,以便它引用Rectangle

Rectangle.prototype.constructor = Rectangle;

我们这样做是因为那样,两者之间的关系Rectangle.prototype看起来Rectangle就像规范 §13.2所说的那样。基本上,如果你有构造函数X,那么X.prototype.constructor应该参考X. 有时人们依赖它来进行克隆操作等(JavaScript 本身不依赖它——例如,instanceof不依赖它)。

所以我们可以使用 ES5Object.create来做到这一点。但即使在今天,也不是所有的 JavaScript 引擎都支持Object.create. 因此,我们可以间接地做到这一点,通过创建一个临时构造函数,让它借用Shape.prototype它的prototype属性,然后使用该临时构造函数创建我们的新对象以用作 as Rectangle.prototype

function Ctor() { }
Ctor.prototype = Shape.prototype; // Borrow the prototype
Rectangle.prototype = new Ctor(); // Create the new object

...我们应该再次设置constructor属性:

Rectangle.prototype.constructor = Rectangle;

通常,您不是每次都写出所有内容,而是使用这样的帮助程序:

function derive(Parent, Child) {
    function Ctor() { this.constructor = Child; }
    Ctor.prototype = Parent.prototype;
    Child.prototype = new Ctor();
    return Child; // For chaining
}

然后像这样使用它:

derive(Shape, Rectangle);

所有这一切的最终结果是我们只Shape从 inside调用Rectangle来初始化实例。我们不称它为创建Rectangle.prototype.

如果您对 JavaScript 中的继承管道感兴趣,您可能会对我的Lineage脚本感兴趣,它处理上述内容以及更多内容。当我说“感兴趣”时,我并不一定是指使用它(尽管欢迎您使用它),但是查看未缩小的源代码并在此页面上与在没有帮助者的情况下做同样的事情进行比较Lineage可以让您了解这是如何做到的东西有效。

于 2013-11-05T14:07:35.813 回答
1

JavaScript 是一种原型面向对象的编程语言。这就是说对象继承自其他对象。

JavaScript 中的对象有一个特殊的[[proto]]属性,它指向该对象的原型。该属性由 JavaScript 引擎内部维护,每个对象都有一个链接的原型链。例如:

          null
           ^
           | [[proto]]
           |
  +------------------+
  | Object.prototype |
  +------------------+
           ^
           | [[proto]]
           |
  +------------------+
  |  Shape.prototype |
  +------------------+
           ^
           | [[proto]]
           |
+---------------------+
| Rectangle.prototype |
+---------------------+
           ^
           | [[proto]]
           |
        +------+
        | rect |
        +------+

当您编写Rectangle.prototype = new Shape();原型链时,如上所示。请注意,执行此操作的新方法是Rectangle.prototype = Object.create(Shape.prototype);.

          null
           ^
           | [[proto]]
           |
  +------------------+
  | Object.prototype |
  +------------------+
           ^
           | [[proto]]
           |
+---------------------+
| Rectangle.prototype |
|  / Shape.prototype  |
+---------------------+
           ^
           | [[proto]]
           |
        +------+
        | rect |
        +------+

如果您编写Rectangle.prototype = Shape.prototype;,那么您的原型链将如上所示。因为Rectangle.prototypeShape.prototype您将仅从一个原型而不是两个原型继承相同。这意味着您不能将Rectangle特定方法添加到Rectangle.prototype.

你应该选择哪种方法?我更喜欢使用第一种方法,因为我们有两个独立的原型。您可以将Rectangle特定方法添加到Rectangle.prototype. 此外,它实际上是在扩展原型。在第二种情况下,根本没有继承。

这就是我现在通常编写代码的方式:

function defclass(type, body) {
    var neo = function () {
        var self = new constructor;
        return prototype.hasOwnProperty("constructor") &&
            prototype.constructor.apply(self, arguments), self;
    };

    var constructor = function () {}; constructor.prototype = type;
    var prototype = constructor.prototype = new constructor; prototype.new = neo;
    return body.call(prototype, type), prototype;
}

使用defclass您现在可以按如下方式创建类:

var shape = defclass(null, function () {
    this.constructor = function () {
        this.x = 0;
        this.y = 0;
    };

    this.move = function (x, y) {
        this.x += x;
        this.y += y;
        console.info("Shape moved.");
    };
});

var rectangle = defclass(shape, function (shape) {
    this.constructor = function () {
        shape.constructor.call(this);
    };
});

最后,您可以按如下方式创建类的实例:

var rect = rectangle.new();

rect.move(2, 1);

亲自查看演示:http: //jsfiddle.net/VHfBd/1

链接:

  1. JavaScript 中的对象继承
  2. 如何在类声明上实现伪经典继承权?
  3. 为什么原型继承很重要
于 2013-11-05T14:38:10.280 回答