原型继承

现在,让我们从一个叫作“原型继承”的模式来讨论没有类的现代继承模式。在这种模式中,没有任何类牵涉进来,一个对象继承自另外一个对象。你可以这样理解它:你有一个想复用的对象,然后你想创建第二个对象,并且获得第一个对象的功能。下面是这种模式的用法:

  1. // 需要继承的对象
  2. var parent = {
  3. name: "Papa"
  4. };
  5. // 新对象
  6. var child = object(parent);
  7. // 测试
  8. alert(child.name); // "Papa"

在这个代码片段中,有一个已经存在的使用对象字面量创建的对象叫parent,我们想创建一个和parent有相同的属性和方法的对象叫childchild对象使用object()函数创建。这个函数在JavaScript中并不存在(不要与构造函数Object()混淆),所以我们来看看怎样定义它。

与Holy Grail类式继承相似,可以使用一个空的临时构造函数F(),然后设定F()的原型为parent对象。最后,返回一个临时构造函数的新实例。

  1. function object(o) {
  2. function F() {}
  3. F.prototype = o;
  4. return new F();
  5. }

图6-9展示了使用原型继承时的原型链。这样创建的child总是一个空对象,它没有自有属性但通过原型链(__proto__)拥有父对象的所有功能。

图6-9 原型继承模式

图6-9 原型继承模式

讨论

在原型继承模式中,parent不一定需要使用对象字面量来创建(尽管这是一种常用的方式),也可以使用构造函数来创建。注意,如果你这样做,那么自有属性和原型上的属性都将被继承:

  1. // 父构造函数
  2. function Person() {
  3. // 自有属性
  4. this.name = "Adam";
  5. }
  6. // 原型上的属性
  7. Person.prototype.getName = function () {
  8. return this.name;
  9. };
  10. // 使用Person()创建一个新对象
  11. var papa = new Person();
  12. // 继承
  13. var kid = object(papa);
  14. // 测试:自有属性和原型上的属性都被继承了
  15. kid.getName(); // "Adam"

也可以使用这种模式的一个变种,只继承已存在的构造函数的原型对象。记住,对象继承自对象,而不管父对象是怎么创建的。这是前面例子的一个修改版本:

  1. // 父构造函数
  2. function Person() {
  3. // 自有属性
  4. this.name = "Adam";
  5. }
  6. // 原型上的属性
  7. Person.prototype.getName = function () {
  8. };
  9. // 继承
  10. var kid = object(Person.prototype);
  11. typeof kid.getName; // "function",因为它在原型中
  12. typeof kid.name; // "undefined",因为只有原型中的成员被继承了

ECMAScript5中的原型继承

在ECMAScript5中,原型继承已经正式成为语言的一部分。这种模式使用Object.create()方法来实现。换句话说,你不再需要自己去写类似object()的函数,它是语言原生的部分了:

  1. var child = Object.create(parent);

Object.create()接收一个额外的参数——一个对象。这个额外对象中的属性将被作为自有属性添加到返回的子对象中。这让我们可以很方便地将继承和创建子对象在一个方法调用中实现。例如:

  1. var child = Object.create(parent, {
  2. age: { value: 2 } // ES5中的属性描述符
  3. });
  4. child.hasOwnProperty("age"); // true

你可能也会发现原型继承模式已经在一些JavaScript类库中实现了,比如,在YUI3中,它是Y.Object()方法:

  1. YUI().use('*', function (Y) {
  2. var child = Y.Object(parent);
  3. });