关于继承多种实现的笔记

大纲

原型链继承

回到顶部

先说原型链继承的问题:包含引用类型属性的原型(不想被共享的引用属性)会变成共享。

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 SuperType(name){
this.name = name;
this.type = 'color';
this.colors = ['yellow', 'white'];
}
SuperType.prototype.sayType = function() {
console.log(this.type);
};
SuperType.prototype.sayName = function() {
console.log(this.name);
};


function SubType(name){
this.name = name;
}
SubType.prototype = new SuperType(this.name);

var ins1 = new SubType();
ins1.colors.push('red');
console.log(ins1.colors); //'yellow', 'white', 'red'

var ins2 = new SubType('issac');
console.log(ins2.colors); //'yellow', 'white', 'red'
ins2.sayType();
ins2.sayName();
关于继承多种实现的笔记

根据超类,colors应该为每个实例独有、不共享的,但由上面的代码可见,由于原型链继承后,colors变成了共享的属性

造成此结果的原因是:以SuperType的实例作为了prototype,而每个对象实例都只是指向这个prototype,因此每个实例拥有的colors实际都是同一个colors,换言之ins1.colors和ins2.colors存放都是同一个指针。

原型链继承的问题:无法保证“私有”引用类型继续“私有”

借调构造函数继承

回到顶部

为解决原型链本应为实例独有的引用属性变成共享属性的问题,提出借调构造函数继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function SuperType(name){
this.name = name;
this.colors = ['yellow', 'white'];
}
SuperType.prototype.sayName = function(){
console.log(this.name);
}

function SubType(name, age){
SuperType.call(this, name);
this.age = age;
}

var sub = new SubType('issac', 18);
sub.sayName();
关于继承多种实现的笔记

借调构造函数解决上面提出了的问题,而且接受参数更加的方便

构造函数自身的老问题:

由于构造函数会将它的把内容创建在一个新的作用域内赋给实例,因此,同样功能的方法就会为每个实例新建一份。这样本应共有的方法一旦多起来,创建的对象实例一旦多起来,就会造成很大冗余,这就是方法无法复用的问题。而且你也看到了,假如父类是使用组合式创建对象(组合原型式和构造函数式),子类是无法继承父类的原型的,因此上面 sub.sayName(); 才会抛出异常,因为subType中确实没有这个方法。

组合继承

回到顶部

由上面两种继承的方式可以看出: 原型链继承 不能保证“私有”引用属性继续“私有”,但是 借调构造函数 可以;借调构造函数继承 不可以复用方法,但 原型链继承 可以。那么可以组合两种方式的长处,使用 原型链继承 继承共享属性和方法,使用 借调构造函数 继承“私有”属性。

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function SuperType(name){
this.name = name;
this.colors = ['yellow', 'white'];
}
SuperType.prototype.sayName = function(){
console.log(this.name);
}
function SubType(name, age){
SuperType.call(this, name);
this.age = age;
}
SubType.prototype = new SuperType();
//公用方法和属性要定义后将父对象的实例赋值给SubType对象之后,不然会因为重写prototype而被“抹去”
SubType.prototype.constructor = SuperType;
SubType.prototype.sayAge = function(){
console.log(this.age);
}
var ins1 = new SubType('a', 18);
ins1.colors.push('red');
console.log(ins1.colors); //'yellow', 'white', 'red'
var ins2 = new SubType('b', 18);
console.log(ins2.colors); //'yellow', 'white'

要理解组合继承是怎么工作的,要先知道一件事:js会先执行prototype的代码,再执行构造函数的代码。
首先是执行 SubType.prototype = new SuperType(); 这句相当于给SubType的原型对象创建了

1
2
3
4
5
6
//以下是涉及的主要部分
{
name: undefined,
colors: ['yellow', 'white'],
[[prototype]]: SuperType.prototype //包含了SuperType的公有方法和属性
}

然后在新建SubType对象实例 **var ins1 = new SubType(‘a’, 18);**的时候调用构造函数,相当于给ins1对象创建“私有”属性:

1
2
3
4
//这里我不太会表达下面属性是ins1独有的属性,姑且如下面这样写,但愿不会误导人
var name = 'a';
var age = 18;
var colors = ['yellow', 'white'];

这些属性会屏蔽原型对象的同名属性,从而达到“私有”。

 

原型式继承

回到顶部

通过封装一个函数来作为继承的媒介

1
2
3
4
5
6
7
8
9
10
11
12
function object(o){
function F(){}
F.prototype = o;
return new F();
}
var person = {
name: "issac",
friends: ['frank', 'Aye'],
};
var anthorPerson = object(person);
anthorPerson.name = 'Annd';
anthorPerson.friends.push('Dda');

在ES5中添加Object.create()方法规范了这种继承,这个方法接受两个参数,一个是原型对象,第二个是需要新增的属性会方法[可选]

1
2
3
4
5
6
7
8
9
10
11
12
var person = {
name: "issac",
friends: ['frank', 'Aye'],
};
var anthorPerson = Object.create(person);
anthorPerson.name = 'Annd';
anthorPerson.friends.push('Dda');
var yetPerson = Object.create(person, {
name: {
value: 'issac'
}
});

Object.create()仅在IE9+支持,特别指出IE其他的浏览器就不说了

寄生式继承

回到顶部

寄生式继承是和原型式继承紧密相关的思路。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function object(o){
function F(){}
F.prototype = o;
return new F();
}

function createAnthor(o){
var clone = object(o);
clone.sayHi = function(){
console.log('Hi Issac');
}
}
var person = {
name: "issac",
friends: ['frank', 'Aye'],
};
var anthorPerson = createAnthor(person);
anthorPerson.sayHi(); //Hi Issac

寄生的精髓即在原有对象上进行扩展

寄生组合式继承

回到顶部

在说组合继承的时候,已经说过超类是会被调用两次,“私有”属性的继承即通过对象实例的属性对原型对象的屏蔽,显然,原型对象中的“私有”属性是多余了。为解决中多余,提出了 寄生组合式继承

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
function object(o){
function F(){}
F.prototype = o;
return new F();
}
function inheritPrototype(subType, superType){
var prototype = object(superType.prototype);
prototype.constructor = superType;
subType.prototype = prototype;
}
function SuperType(name){
this.name = name;
this.colors = ['yellow', 'white'];
console.log(Math.round(Math.random()*10)); //打印一个0~10的随机数
}
SuperType.prototype.sayName = function(){
console.log(this.name);
}
function SubType(name, age){
SuperType.call(this, name);
this.age = age;
}
//继承
inheritPrototype(SubType, SuperType);
SubType.prototype.sayAge = function(){
console.log(this.age);
}
var ins1 = new SubType('issac1', 18);
ins1.colors.push('red');
console.log(ins1.colors);

以上代码的执行结果

显然,使用寄生组合式继承仅仅调用了一次超类。
使用组合继承,会调用2次超类:
第一次,以超类的实例作为子类的原型对象(原型链式继承);
第二次,创建子类的对象实例时调用子类构造函数,子类构造函数,通过借调构造函数继承调用超类的构造函数;
 
寄生组合式继承是抹去上面说的第一次调用超类。它是通过直接将超类的原型对象赋值给子类的原型对象,然后子类在此基础上进行个扩充,这样就会不会触发到超类的构造函数。