關(guān)于js中的繼承,已經(jīng)老生常談了,本文將對js的繼承做一個大概的總結(jié).
首先我們可以看一下,es5繼承關(guān)系圖,理解繼承的實現(xiàn),然后再討論不同的繼承的實現(xiàn)方式的問題
1.在js實現(xiàn)繼承靠的構(gòu)造函數(shù)的原型對象(即Prototype)
2.js中所有的對象都繼承Object
3.Object的原型對象的[[prototype]]指向null
看完上面的原型繼承圖之后,我們接下來就可以按照原型鏈去實現(xiàn)繼承了
1.組合繼承
function Super(age){
this.name = "lsh";
this.age = age;
}
Super.prototype.sayName = function (){
console.log(this.name);
}
function Sub(){
Super.call(this,99);
}
Sub.prototype = new Super;
Sub.prototype.constructor = Sub;
Sub.prototype.hello = function (){
console.log(this.age);
}
首先借用構(gòu)造函數(shù),利用的是js中函數(shù)執(zhí)行的環(huán)境問題,call和apply可以修改函數(shù)執(zhí)行的函數(shù),實際上是修改了函數(shù)內(nèi)部的this對象.其實就是在子類的構(gòu)造函數(shù)內(nèi)部調(diào)用父類的構(gòu)造函數(shù),通過call或apply修改其this對象,實現(xiàn)一種繼承的現(xiàn)象.然后再設(shè)置子類的prototype為父類的對象,完成原型的繼承
2.原型是繼承
原型繼承要求你必須有一個對象作為另一個對象的基礎(chǔ),將這個對象傳入Objec.create函數(shù)中,create函數(shù)返回一個新的對象,這個新的對象的原型就是你傳入的對象.這樣新的對象就會在原型上擁有你傳入對象的所有屬性和方法,包括其原型
function Person(){
this.name = 'lsh';
this.friends = ['name1','name2'];
}
Person.prototype.sayName = function(){
console.log(this.name);
}
var person = new Person();
var person1 = Object.create(person);
person1.friends.push('name3');
var person2 = Object.create(person);
person2.name = 'xindi';
person2.friends.push('name4');
這樣的結(jié)果其實就是相當(dāng)于創(chuàng)造了一個你傳入對象的副本.如果你傳入的對象包含有引用類型的屬性,則這個屬性會被共享.
此時原型鏈的關(guān)系就會是
superPerson.__proto__.__proto__ === Person.prototype
superPerson.__proto__.constructor === Person
其實create方法內(nèi)部的實現(xiàn)機(jī)制就是創(chuàng)造了一個臨時的構(gòu)造函數(shù),然后將臨時構(gòu)造函數(shù)的原型指向你傳入的對象,最后返回臨時構(gòu)造函數(shù)生成的對象.
function create(o){
function F(){};
F.prototype = o;
return new F();
}
所以當(dāng)你采用原型式繼承的時候,要考慮清楚這種繼承是否滿足你的需要.
3.寄生式繼承
寄生式繼承和原型式繼承關(guān)系緊密,其實就是封裝一個繼承用的函數(shù),在函數(shù)內(nèi)部調(diào)用原型式繼承,同時在函數(shù)內(nèi)部可以選擇給新的對象增加一個方法或?qū)傩?/p>
function createAnother(ori){
let clone = Object.create(ori);
clone.sayHi = function(){
}
return clone;
}
4.寄生組合式繼承
寄生組合式繼承,就是通過構(gòu)造函數(shù)來繼承屬性,通過原型鏈的混成方式來繼承原型的方法或?qū)傩?/p>
function inherit(superType,subType){
let prototype = Object.create(superType.prototype);
prototype.constructor = subType;
sub.prototype = prototype;
}
函數(shù)第一步創(chuàng)建了一個父類的原型的副本,然后讓這個對象的contructor指向子類的構(gòu)造函數(shù),最后將子類構(gòu)造函數(shù)的原型設(shè)置為這個對象.這樣就實現(xiàn)了一個繼承.
寄生組合式繼承是最理想的繼承方式,推薦使用這個方式.
ps:如何確定實例和原型的關(guān)系:
1.instance ?instanceof Super
2.Super.prototype.isPrototypeof instance?
給子類原型添加方法,無論是重寫超類的方法還是添加新的,一定要在原型被替換之后再寫,也就是在調(diào)用繼承之后再添加.
不過在ES6 ,js添加了class這個語法糖,其繼承也只需簡單的寫一個extends.
class Super {
? ? ? constructor(name){
? ? ? ? this.name = 'name';
? ? }
? ? ? sayName(){
? ? ? console.log(this.name);
? ? ? }
? ? ? }
class Sub extends Super{
constructor(name){
super(name);
?? ? this.name = 'xindi';
}
}
這個語法糖使得es6的繼承變得無比簡單,不過es6的繼承還是和es5的稍微有點區(qū)別,如下圖
es6的繼承相較于es5,其實就多了一個子類構(gòu)造函數(shù)和父類構(gòu)造函數(shù)的關(guān)系上,在es5中兩個構(gòu)造函數(shù)沒有任何關(guān)系,但是在es6中子類構(gòu)造函數(shù)的__proto__(即[[prototype]])指向了父類的構(gòu)造函數(shù).