1. 原型链继承
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| function Parent(){ this.name = '缪克立'; }
Parent.prototype.getName = function(){ return this.name }
function Child(){
}
Child.prototype = new Parent(); var child1 = new Child(); console.log(child1.getName())
|
问题:
- 引用类型的属性被所有实例共享,举个例子:
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 27
| function Parent(){ this.names = ['缪克立', '缪立克']; }
function Child(){ this.age = [18] }
Child.prototype = new Parent();
var child1 = new Child();
child1.names.push('mkl'); child1.age.push(19)
console.log(child1.names); console.log(child1.age)
var child2 = new Child();
console.log(child2.names); console.log(child2.age);
console.log(Child) console.log(Child.prototype)
|
child1
和child2
都是new
的两个新的对象,双方都有各自的age
属性和names
属性,但是它们的原型都是Parent
即定义成了一个对象,当修改child1
的names
属性时,由于child1
没有这个属性,就往原型上找,于是找到了Parent
,并且Parent
是引用类型,child1
保存的原型就是parent
的地址值,所以修改names
属性就顺着地址值找到Parent
的names
属性更改。child1
和child2
的age
属性是基本数据类型,双方保存的都是原本的值,所以没有共享。
- 在创建
Child
的实例时,不能向Parent
传参
2. 借用构造函数(经典继承)
废话不多说,直接上代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| function Parent(){ this.names = ['缪克立', '缪立克']; }
function Child(){ Parent.call(this); }
var child1 = new Child();
child1.names.push('mkl');
console.log(child1.names);
var child2 = new Child();
console.log(child2.names);
|
上面的Parent.call(this)
就是借用构造函数的调用。通过call()
或apply()
方法,Parent
构造函数在为Child
的实例创建的新对象的上下文中执行了。相当于在新的Child
对象上运行了Parent()
函数中的所有初始化代码。结果就是每个child
实例都会有自己的names
属性。
优点:
避免了引用类型的属性被所有实例共享。
可以在Child中向Parent传参。
举个例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| function Parent(name){ this.name = name; }
function Child(name){ Parent.call(this,name); }
var child1 = new Child('kevin');
console.log(child1.name);
var child2 = new Child('daisy');
console.log(child2.name);
|
缺点:
方法都在构造函数中定义,每次创建实例都会创建一遍方法。
3. 组合继承
结合原型链继承和经典继承。
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 27 28 29 30 31
| function Parent(name){ this.name = name; this.colors = ['red','blue','green']; }
Parent.prototype.getName = function(){ return 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('kevin', '18');
child1.colors.push('black');
console.log(child1.name); console.log(child1.age); console.log(child1.colors); console.log(child1.getName()) var child2 = new Child('daisy', '20') console.log(child2.name); console.log(child2.age); console.log(child2.colors); console.log(child2.getName())
|
通过这种方式就可以创建两个child
实例,让这两个实例都有自己的属性,包括colors
,同时还共享相同的方法,弥补了借用构造函数继承的缺点,实现函数服用。
优点:融合原型链继承和经典继承的优点,是JavaScript中最常用的继承模式。
4. 原型式继承
1 2 3 4 5
| function createObj(o){ function F(){} F.prototype = o; return new F(); }
|
实际上就是 ES5 Object.create
的模拟实现,将传入的对象作为创建的对象的原型。
缺点:包含引用类型的属性值始终都会共享相同的值,这点跟原型链继承一样。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| function createObj(o){ function F(){} F.prototype = o; return new F(); } var person = { name: 'kevin', friends: ['daisy', 'kelly'] }
var person1 = createObj(person) var person2 = createObj(person)
person1.name = 'person1'; console.log(person2.name)
person1.friends.push('taylor'); console.log(person2.friends);
|
注意:修改person1.name
的值,person2.name
的值并未发生改变,并不是因为person1
和person2
有独立的name
值,而是因为person1.name = 'person1'
,给person1
添加了name
值,并非修改了原型上的name
值。
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 15 16 17 18 19 20 21 22 23
| function Parent(name){ this.name = name; this.colors = ['red','blue','green']; }
Parent.prototype.getName = function(){ return 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('kevin', '18');
child1.colors.push('black');
console.log(child1);
|
组合继承最大的缺点是会嗲用两次父构造函数。
一次是设置子类型实例的原型的时候:
1
| Child.prototype = new Parent();
|
一次在创建子类型实例的时候:
1
| var child1 = new Child('kevin', '18')
|
回想一下new的模拟实现,在这句中,我们就会执行:
在这里,我们又会调用了以此Parent构造函数。
所以在这里例子中,如果我们打印child1
对象,我们会发现child1.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('kevin', '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);
|
引用《JavaScript高级程序设计》:
这种方式的高效率体现它只调用了一次 Parent 构造函数,并且因此避免了在 Parent.prototype 上面创建不必要的、多余的属性。与此同时,原型链还能保持不变;因此,还能够正常使用 instanceof 和 isPrototypeOf。开发人员普遍认为寄生组合式继承是引用类型最理想的继承范式。