大纲
原型链继承
回到顶部
先说原型链继承的问题:包含引用类型属性的原型(不想被共享的引用属性)会变成共享。
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);
var ins2 = new SubType('issac'); console.log(ins2.colors); 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.constructor = SuperType; SubType.prototype.sayAge = function(){ console.log(this.age); } var ins1 = new SubType('a', 18); ins1.colors.push('red'); console.log(ins1.colors); var ins2 = new SubType('b', 18); console.log(ins2.colors);
|
要理解组合继承是怎么工作的,要先知道一件事:js会先执行prototype的代码,再执行构造函数的代码。
首先是执行 SubType.prototype = new SuperType(); 这句相当于给SubType的原型对象创建了
1 2 3 4 5 6
| { name: undefined, colors: ['yellow', 'white'], [[prototype]]: SuperType.prototype }
|
然后在新建SubType对象实例 **var ins1 = new SubType(‘a’, 18);**的时候调用构造函数,相当于给ins1对象创建“私有”属性:
1 2 3 4
| 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();
|
寄生的精髓即在原有对象上进行扩展
寄生组合式继承
回到顶部
在说组合继承的时候,已经说过超类是会被调用两次,“私有”属性的继承即通过对象实例的属性对原型对象的屏蔽,显然,原型对象中的“私有”属性是多余了。为解决中多余,提出了 寄生组合式继承
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)); } 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次超类:
第一次,以超类的实例作为子类的原型对象(原型链式继承);
第二次,创建子类的对象实例时调用子类构造函数,子类构造函数,通过借调构造函数继承调用超类的构造函数;
寄生组合式继承是抹去上面说的第一次调用超类。它是通过直接将超类的原型对象赋值给子类的原型对象,然后子类在此基础上进行个扩充,这样就会不会触发到超类的构造函数。