作者按:
关于JavaScript 继承的几种方式,网上有很多文章。但其实红宝书里已经讲解的非常透彻,思路清晰。本篇文章更像是学习笔记,第八章常读常新。

以下是正文:
1. 原型链继承
1 2 3 4 5 6 7 8 9 10 11 12 13
| function Parent () { this.name = 'yang'; }
Parent.prototype.getName = function () { console.log(this.name); } function Child () {
} Child.prototype = new Parent(); var child1 = new Child(); console.log(child1.getName())
|
问题1:引用类型的属性被所有实例共享
问题2:在创建 Child实例时,不能像Parent传参
2. constructor stealing (盗用构造函数)
1 2 3 4 5 6 7 8 9 10 11
| function Parent () { this.names = ['yang', 'yao']; } function Child () { Parent.call(this); } var child1 = new Child(); child1.names.push('xuezhuo'); console.log(child1.names); var child2 = new Child(); console.log(child2.names);
|
优点:
1.避免了引用类型的属性被所有实例共享
2.可以在 Child 中向 Parent 传参
缺点:
方法都在构造函数中定义,每次创建实例都会创建一遍方法。
3. 组合继承(经典继承)
原型链继承和经典继承的双剑合璧,基本的思路是使用原型链继承原型上的属性和方法,而通过盗用构造函数继承实例属性。这样既可以把方法定义在原型上以实现重用,又可以让每个实例都有自己的属性。
弥补了原型链和盗用构造函数的不足,是 JavaScript 中使用最多的继承模式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| function Parent (name) { this.name = name; this.colors = ['red', 'blue', 'green']; } Parent.prototype.getName = function () { console.log(this.name) } function Child (name, age) { Parent.call(this, name); this.age = age; } Child.prototype = new Parent(); Child.prototype.constructor = Child;
var child1 = new Child('yang', '18'); child1.colors.push('black');
console.log(child1.name); console.log(child1.age); console.log(child1.colors);
var child2 = new Child('miemie', '20');
console.log(child2.name); console.log(child2.age); console.log(child2.colors);
|
4. 原型式继承
1 2 3 4 5
| function createObj(o) { function F(){} F.prototype = o; return new F(); }
|
这个函数是 Douglas Crockford 给出的,适用于在一个对象的基础上在创造一个对象。
ECMAScript 5通过增加Object.create()方法将原型式继承的概念规范化了。
但是它的缺点还是跟原型链继承一样,属性中包含的引用值还是会在对象中共享。
5. 寄生式继承
创建一个实现继承的函数,以某种方式增强对象,然后返回这个对象。
1 2 3 4 5 6 7
| function createObj (o) { var clone = Object.create(o); clone.sayName = function () { console.log('hi'); } return clone; }
|
但是它还是有跟构造函数模式一样的缺点
6. 终极法器:寄生组合式继承
先来复习一下组合继承的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| function Parent (name) { this.name = name; this.colors = ['red', 'blue', 'green']; } Parent.prototype.getName = function () { console.log(this.name) } function Child (name, age) { Parent.call(this, name); this.age = age; } Child.prototype = new Parent(); var child1 = new Child('kevin', '18'); console.log(child1)
|
组合继承最大的缺点是会调用两次父构造函数。
一次是设置子类型实例的原型的时候:
1
| Child.prototype = new Parent();
|
一次在创建子类型实例的时候:
1
| var child1 = new Child('yang', '18');
|
回想下 new 的模拟实现,其实在这句中,我们会执行:
1
| Parent.call(this, name);
|
在这里,我们又会调用了一次 Parent 构造函数。
所以,在这个例子中,如果我们打印 child1 对象,我们会发现 Child.prototype 和 child1 都有一个属性为colors,属性值为[‘red’, ‘blue’, ‘green’]。
那么我们该如何精益求精,避免这一次重复调用呢?
如果我们不使用 Child.prototype = new Parent() ,而是间接的让 Child.prototype 访问到 Parent.prototype 呢?
看看如何实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| function Parent (name) { this.name = name; this.colors = ['red', 'blue', 'green']; }
Parent.prototype.getName = function () { console.log(this.name) }
function Child (name, age) { Parent.call(this, name); this.age = age; }
var F = function () {};
F.prototype = Parent.prototype;
Child.prototype = new F();
var child1 = new Child('yang', '18');
console.log(child1);
|
最后我们封装一下这个继承方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| function object(o) { function F() {} F.prototype = o; return new F(); }
function prototype(child, parent) { var prototype = object(parent.prototype); prototype.constructor = child; child.prototype = prototype; }
prototype(Child, Parent);
|
寄生式组合继承可以算是引用类型继承的最佳模式。