我担心以下代码段:
//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 Shape
do 创建的对象一样,但无需调用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
可以让您了解这是如何做到的东西有效。