原型继承
现在,让我们从一个叫作“原型继承”的模式来讨论没有类的现代继承模式。在这种模式中,没有任何类牵涉进来,一个对象继承自另外一个对象。你可以这样理解它:你有一个想复用的对象,然后你想创建第二个对象,并且获得第一个对象的功能。下面是这种模式的用法:
// 需要继承的对象
var parent = {
name: "Papa"
};
// 新对象
var child = object(parent);
// 测试
alert(child.name); // "Papa"
在这个代码片段中,有一个已经存在的使用对象字面量创建的对象叫parent
,我们想创建一个和parent
有相同的属性和方法的对象叫child
。child
对象使用object()
函数创建。这个函数在JavaScript中并不存在(不要与构造函数Object()
混淆),所以我们来看看怎样定义它。
与Holy Grail类式继承相似,可以使用一个空的临时构造函数F()
,然后设定F()
的原型为parent
对象。最后,返回一个临时构造函数的新实例。
function object(o) {
function F() {}
F.prototype = o;
return new F();
}
图6-9展示了使用原型继承时的原型链。这样创建的child
总是一个空对象,它没有自有属性但通过原型链(__proto__
)拥有父对象的所有功能。
图6-9 原型继承模式
讨论
在原型继承模式中,parent
不一定需要使用对象字面量来创建(尽管这是一种常用的方式),也可以使用构造函数来创建。注意,如果你这样做,那么自有属性和原型上的属性都将被继承:
// 父构造函数
function Person() {
// 自有属性
this.name = "Adam";
}
// 原型上的属性
Person.prototype.getName = function () {
return this.name;
};
// 使用Person()创建一个新对象
var papa = new Person();
// 继承
var kid = object(papa);
// 测试:自有属性和原型上的属性都被继承了
kid.getName(); // "Adam"
也可以使用这种模式的一个变种,只继承已存在的构造函数的原型对象。记住,对象继承自对象,而不管父对象是怎么创建的。这是前面例子的一个修改版本:
// 父构造函数
function Person() {
// 自有属性
this.name = "Adam";
}
// 原型上的属性
Person.prototype.getName = function () {
};
// 继承
var kid = object(Person.prototype);
typeof kid.getName; // "function",因为它在原型中
typeof kid.name; // "undefined",因为只有原型中的成员被继承了
ECMAScript5中的原型继承
在ECMAScript5中,原型继承已经正式成为语言的一部分。这种模式使用Object.create()
方法来实现。换句话说,你不再需要自己去写类似object()
的函数,它是语言原生的部分了:
var child = Object.create(parent);
Object.create()
接收一个额外的参数——一个对象。这个额外对象中的属性将被作为自有属性添加到返回的子对象中。这让我们可以很方便地将继承和创建子对象在一个方法调用中实现。例如:
var child = Object.create(parent, {
age: { value: 2 } // ES5中的属性描述符
});
child.hasOwnProperty("age"); // true
你可能也会发现原型继承模式已经在一些JavaScript类库中实现了,比如,在YUI3中,它是Y.Object()
方法:
YUI().use('*', function (Y) {
var child = Y.Object(parent);
});