6種JavaScript繼承方式及優缺點

web小學生發表於2020-02-05

溫馨提示:想要更好的理解JS繼承方式,須瞭解建構函式、原型物件、例項化物件、原型鏈等概念

第一種:原型鏈繼承

利用原型鏈的特點進行繼承

function Parent(){
    this.name = 'web前端';
    this.type = ['JS','HTML','CSS'];
}
Parent.prototype.Say=function(){
    console.log(this.name);
}
function Son(){};
Son.prototype = new Parent();
son1 = new Son();
son1.Say();
複製程式碼

以上例子解釋:

①建立一個叫做Parent的建構函式,暫且稱為父建構函式,裡面有兩個屬性name、type

②通過Parent建構函式的屬性(即原型物件)設定Say方法,此時,Parent有2個屬性和1個方法

③建立一個叫做Son的建構函式,暫且稱為子建構函式

④設定Son的屬性(即原型物件)值為父建構函式Parent的例項物件,即子建構函式Son繼承了父建構函式Parent,此時Son也有2個屬性和1個方法

⑤對Son建構函式進行例項化,結果賦值給變數son1,即son1為例項化物件,同樣擁有2個屬性和1個方法

⑥輸出son1的Say方法,結果為"web前端"

優點:可以實現繼承

缺點:

①因為Son.prototype(即原型物件)繼承了Parent例項化物件,這就導致了所有Son例項化物件都一樣,都共享有原型物件的屬性及方法。程式碼如下:

son1 = new Son();
son2 = new Son();
son1.type.push('VUE');
console.log(son1.type);//['JS','HTML','CSS','VUE']
console.log(son2.type);//['JS','HTML','CSS','VUE']
結果son1、son2都是['JS','HTML','CSS','VUE']
複製程式碼

②Son建構函式例項化物件無法進行引數的傳遞

第二種:建構函式繼承

通過建構函式call方法進行繼承,直接來看程式碼:

function Parent(){
    this.name = 'web前端';
    this.type = ['JS','HTML','CSS'];
}
function Son(){
    Parent.call(this);
}
son1 = new Son();
son1.type.push('VUE');
console.log(son1.type);//['JS','HTML','CSS','VUE']
son2 = new Son();
console.log(son2.type);//['JS','HTML','CSS']
複製程式碼

以上例子解釋:

①建立父級建構函式Parent,有name、type兩個屬性

②建立子級建構函式Son,函式內部通過call方法呼叫父級建構函式Parent,實現繼承

③分別建立建構函式Son的兩個例項化物件son1、son2,對son1的type屬性新增元素,son2沒有新增,結果不一樣,說明實現了獨立

優點:

①實現例項化物件的獨立性;

②還可以給例項化物件新增引數

function Parent(name){
    this.name = name;
}
function Son(name){
    Parent.call(this,name);
}
son1 = new Son('JS');
console.log(son1);//JS
son2 = new Son('HTML');
console.log(son2);//HTML
複製程式碼

缺點:

①方法都在建構函式中定義,每次例項化物件都得建立一遍方法,基本無法實現函式複用

②call方法僅僅呼叫了父級建構函式的屬性及方法,沒有辦法呼叫父級建構函式原型物件的方法

第三種:組合繼承

利用原型鏈繼承和建構函式繼承的各自優勢進行組合使用,還是看程式碼:

function Parent(name){
    this.name = name;
    this.type = ['JS','HTML','CSS'];
}
Parent.prototype.Say=function(){
    console.log(this.name);
}
function Son(name){
    Parent.call(this,name);
}
Son.prototype = new Parent();
son1 = new Son('張三');
son2 = new Son('李四');
son1.type.push('VUE');
son2.type.push('PHP');
console.log(son1.type);//['JS','HTML','CSS','VUE']
console.log(son2.type);//['JS','HTML','CSS','PHP']
son1.Say();//張三
son2.Say();//李四
複製程式碼

以上例子解釋:

①建立一個叫做Parent的建構函式,裡面有兩個屬性name、type

②通過Parent建構函式的屬性(即原型物件)設定Say方法,此時,Parent有2個屬性和1個方法

③建立子級建構函式Son,函式內部通過call方法呼叫父級建構函式Parent,實現繼承

④子建構函式Son繼承了父建構函式Parent,此時Son也有2個屬性和1個方法

⑤分別建立建構函式Son的兩個例項化物件son1、son2,傳不同引數、給type屬性新增不同元素、呼叫原型物件Say方法

優點:

①利用原型鏈繼承,實現原型物件方法的繼承

②利用建構函式繼承,實現屬性的繼承,而且可以引數

組合函式基本滿足了JS的繼承,比較常用

缺點:

無論什麼情況下,都會呼叫兩次父級建構函式:一次是在建立子級原型的時候,另一次是在子級建構函式內部

第四種:原型式繼承

建立一個函式,將引數作為一個物件的原型物件

function fun(obj) {
    function Son(){};
    Son.prototype = obj;
    return new Son();
}        
var parent = {
    name:'張三'
}
var son1 = fun(parent);
var son2 = fun(parent);
console.log(son1.name);//張三
console.log(son2.name);//張三
複製程式碼

以上例子解釋:

①建立一個函式fun,內部定義一個建構函式Son

②將Son的原型物件設定為引數,引數是一個物件,完成繼承

③將Son例項化後返回,即返回的是一個例項化物件

優缺點:跟原型鏈類似

第五種:寄生繼承

在原型式繼承的基礎上,在函式內部豐富物件

function fun(obj) {
    function Son() { };
    Son.prototype = obj;
    return new Son();
}
function JiSheng(obj) {
    var clone = fun(obj);
    clone.Say = function () {
        console.log('我是新增的方法');
    }
    return clone;
}
var parent = {
    name: '張三'
}
var parent1 = JiSheng(parent);
var parent2 = JiSheng(parent);
console.log(parent2.Say==parent1.Say);// false
複製程式碼

以上例子解釋:

①再原型式繼承的基礎上,封裝一個JiSheng函式

②將fun函式返回的物件進行增強,新增Say方法,最後返回

③呼叫JiSheng函式兩次,分別賦值給變數parent1、parent2

④對比parent1、parent2,結果為false,實現獨立

優缺點:跟建構函式繼承類似,呼叫一次函式就得建立一遍方法,無法實現函式複用,效率較低

這裡補充一個知識點,ES5有一個新的方法Object.create(),這個方法相當於封裝了原型式繼承。這個方法可以接收兩個引數:第一個是新物件的原型物件(可選的),第二個是新物件新增屬性,所以上面程式碼還可以這樣:

function JiSheng(obj) {
    var clone = Object.create(obj);
    clone.Say = function () {
        console.log('我是新增的方法');
    }
    return clone;
}
var parent = {
    name: '張三'
}
var parent1 = JiSheng(parent);
var parent2 = JiSheng(parent);
console.log(parent2.Say==parent1.Say);// false
複製程式碼

第六種:寄生組合繼承

利用組合繼承和寄生繼承各自優勢

組合繼承方法我們已經說了,它的缺點是兩次呼叫父級建構函式,一次是在建立子級原型的時候,另一次是在子級建構函式內部,那麼我們只需要優化這個問題就行了,即減少一次呼叫父級建構函式,正好利用寄生繼承的特性,繼承父級建構函式的原型來建立子級原型。

function JiSheng(son,parent) {
    var clone = Object.create(parent.prototype);//建立物件
    son.prototype = clone;      //指定物件
    clone.constructor = son;     //增強物件
}
function Parent(name){
    this.name = name;
    this.type = ['JS','HTML','CSS'];
}
Parent.prototype.Say=function(){
    console.log(this.name);
}
function Son(name){
    Parent.call(this,name);
}
JiSheng(Son,Parent);
son1 = new Son('張三');
son2 = new Son('李四');
son1.type.push('VUE');
son2.type.push('PHP');
console.log(son1.type);//['JS','HTML','CSS','VUE']
console.log(son2.type);//['JS','HTML','CSS','PHP']
son1.Say();//張三
son2.Say();//李四
複製程式碼

以上例子解釋:

①封裝一個函式JiSheng,兩個引數,引數1為子級建構函式,引數2為父級建構函式

②利用Object.create(),將父級建構函式原型克隆為副本clone

③將該副本作為子級建構函式的原型

④給該副本新增constructor屬性,因為③中修改原型導致副本失去預設的屬性

優缺點:

組合繼承優點、寄生繼承的有點,目前JS繼承中使用的都是這個繼承方法

相關文章