欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

分享JavaScript?中的幾種繼承方式

 更新時間:2022年05月26日 15:52:48   作者:??大力yy????  
這篇文章主要介紹了分享JavaScript?中的幾種繼承方式,JavaScript中的繼承主要是通過原型鏈實(shí)現(xiàn)的,具體實(shí)現(xiàn)需要的小伙伴可以參考下面文章詳細(xì)內(nèi)容

前言:

說到JavaScript中的繼承,與之密切相關(guān)的就是原型鏈了,JavaScript中的繼承主要是通過原型鏈實(shí)現(xiàn)的。但是簡單的原型鏈繼承方式也存在一定的缺陷,在此借著《JavaScript高級程序設(shè)計(jì)(第四版)》一書,聊聊JavaScript中的幾種繼承方式

一、原型鏈

ECMA-262 把原型鏈定義為ECMAScript的主要繼承方式,其基本思想就是通過原型繼承多個引用類型的屬性和方法。

在此回顧一下原型、構(gòu)造函數(shù)、實(shí)例之間的關(guān)系:

每個構(gòu)造函數(shù)都有一個原型對象,原型有一個屬性指回構(gòu)造函數(shù),實(shí)例有一個內(nèi)部指針指向原型。
有關(guān)原型和原型鏈的知識這里先不多說了,這里來談?wù)勗玩湹囊恍﹩栴}。

1.1 原型鏈的問題

  • 原型鏈主要問題出現(xiàn)在原型中包含引用值的時候。因?yàn)樵蜕系膶傩詴谒袑傩灾g共享,對于原型上的引用值,實(shí)例繼承的是指向該對象的引用,所以在實(shí)例中修改該屬性時,會影響原型上的屬性。
function Father() {
    this.colors = ['red'];
}
function Son() {}
Son.prototype = new Father();
let son1 = new Son();
console.log(son1.colors);  // ['red']
son1.colors.push('green');
console.log(son1.colors);  // ['red', 'green']
console.log(son1.hasOwnProperty('colors'));  // false
let son2 = new Son();
console.log(son2.colors);  // ['red', 'green']
console.log(Son.prototype.colors);  // ['red', 'green']

如上代碼,構(gòu)造函數(shù)的原型為new Father(),原型包含引用值屬性colorsSon對象實(shí)例自身并沒有colors屬性,而是繼承自原型,所以向colors中添加"green"影響到的原型上的colors。這就導(dǎo)致son2訪問colors屬性時值為['red', 'green']
所以,若原型上屬性為引用值時,在實(shí)例中對該屬性修改時會影響原型屬性。

但是需要注意下面這種情況:

function Father() {
    this.colors = ['red'];
}
function Son() {}
Son.prototype = new Father();
let son1 = new Son();
console.log(son1.colors);  // ['red']
son1.colors = [];
console.log(son1.colors);  // []
console.log(son1.hasOwnProperty('colors'));  // true
let son2 = new Son();
console.log(son2.colors);  // ['red']
console.log(Son.prototype.colors);  // ['red']

代碼中son1.colors = []并不是修改原型屬性colors[],而是在為實(shí)例son1添加新的屬性colors。

  • 原型鏈的另一個問題是,子類型在實(shí)例化時不能給父類型的構(gòu)造函數(shù)傳參。即不能在不影響其他對象實(shí)例的情況下傳遞參數(shù)給父類的構(gòu)造函數(shù)。

那上面的代碼來說就是,在創(chuàng)建Son對象實(shí)例的時候,不能指定colors的值。

綜上所述:由于引用值和傳參問題,原型鏈一般不會被單獨(dú)使用。

二、盜用構(gòu)造函數(shù)

為了解決原型包含引用值所導(dǎo)致的問題,出現(xiàn)了一種叫作"盜用構(gòu)造函數(shù)"(constructor stealing)的技術(shù)。

2.1 基本思想

在子類構(gòu)造函數(shù)中調(diào)用父類構(gòu)造函數(shù)。主要是通過callapply來實(shí)現(xiàn)。

function Father() {
    this.colors = ['red'];
}
function Son() {
    // 在此通過call()調(diào)用父類構(gòu)造函數(shù)
    Father.call(this);
}
let son1 = new Son();
console.log(son1.colors);  // ['red']
// 說明colors 是實(shí)例的自身屬性
console.log(son1.hasOwnProperty('colors'));  // true
son1.colors.push('green');
console.log(son1.colors);  // ['red', 'green']
let son2 = new Son();
console.log(son2.colors);  // ['red']

new運(yùn)算符調(diào)用構(gòu)造函數(shù)的過程可知,會將函數(shù)中的this指向新創(chuàng)建的實(shí)例。所以Father.call(this);相當(dāng)于實(shí)例調(diào)用了Father方法,然后添加了自身屬性colors。所以后續(xù)son1.colors.push('green');并不會影響到其他實(shí)例。

2.2 可向父類構(gòu)造函數(shù)傳參

盜用構(gòu)造函數(shù)的另外一個優(yōu)點(diǎn)在于,可以在子類構(gòu)造函數(shù)中向父類構(gòu)造函數(shù)傳參。

如下代碼:

function Father(name) {
    this.name = name;
}
function Son(name) {
    Father.call(this, name);
}
let son = new Son('dali');
console.log(son);  // Son?{name: 'dali'}

2.3 盜用構(gòu)造函數(shù)的問題

盜用構(gòu)造函數(shù)的主要問題如下:

  • 所有方法必須在構(gòu)造函數(shù)中定義,所以方法不能重用。(即:即使功能相同的方法,每個實(shí)例上對應(yīng)的該方法不是同一個函數(shù)對象)
function Father() {
    this.foo = function() {}
}
function Son() {
    Father.call(this);
}
let son1 = new Son();
let son2 = new Son();
console.log(son1.foo === son2.foo);  // false
  • 子類不能訪問到父類原型上的方法。因?yàn)樽宇悆H僅只是調(diào)用父類構(gòu)造函數(shù),并沒有設(shè)置原型指向父類實(shí)例。子類和父類之間并沒有建立原型關(guān)系。
let son = new Son();
console.log(son instanceof Father)  // false

綜上所述:單獨(dú)使用盜用構(gòu)造函數(shù)也是不可行的。

三、組合繼承(偽經(jīng)典繼承)

3.1 基本思想

組合繼承綜合了原型鏈和盜用構(gòu)造函數(shù),使用原型鏈繼承原型上的屬性和方法,通過盜用構(gòu)造函數(shù)繼承實(shí)例屬性。

function Father(name) {
    this.name = name;
    this.colors = ['red'];
}
Father.prototype.sayHello = function() {
    console.log('hello');
}
function Son(name) {
    // 繼承屬性
    Father.call(this, name);
}
// 構(gòu)建原型鏈,繼承方法
Son.prototype = new Father();
let son1 = new Son('dali');
console.log(son1);  // {name: 'dali', ['red']}
son1.colors.push('green');
console.log(son1);  // {name: 'dali', ['red', 'green']}
let son2 = new Son('haha');
console.log(son2);  // {name: 'haha', ['red']}
// 每個實(shí)例都有自身的 colors 屬性
console.log(son1.colors === son2.colors)  // false
// 實(shí)例間共享sayHello方法
console.log(son1.sayHello === son2.sayHello)  // true

通過調(diào)用父類構(gòu)造函數(shù),每個實(shí)例都有“自身”的原型屬性(例如:colors),所以通過引用修改對應(yīng)的對象時,不會影響其他實(shí)例,因?yàn)槊總€實(shí)例的引用值屬性指向的對象不同。此外,通過原型鏈也實(shí)現(xiàn)了所以實(shí)例之間共享同一方法。

3.2 組合繼承的問題

雖然組合繼承彌補(bǔ)了原型鏈和盜用構(gòu)造函數(shù)的不足,但是組合繼承也存在效率問題:

  • 父類的構(gòu)造函數(shù)會被調(diào)用兩次
    • 一次時在創(chuàng)建子類原型的時候被調(diào)用
    • 另一次是實(shí)例化子類對象時在子類構(gòu)造函數(shù)中被調(diào)用
  • 子類原型上存在不必要的屬性
console.log(Son.prototype);  // Father?{name: undefined, colors: Array(1)}

緊接著上述代碼,我們可以看到子類構(gòu)造函數(shù)的原型對象上有namecolors屬性,但是每個Son對象實(shí)例上都有自身的namecolors屬性,并不是繼承自原型。所以,子類構(gòu)造函數(shù)的原型對象上有namecolors屬性是多余的。

  • 子類構(gòu)造函數(shù)原型(prototype)上的constructor屬性丟失
console.log(Son.prototype.constructor === Son)  //  false

修改構(gòu)造函數(shù)的原型都會出現(xiàn)這種問題。

四、原型式繼承

4.1 基本思想

function object(o) {
    function F();
    F.prototype = o;
    return new F();
}

其實(shí)就是在創(chuàng)建一個對象時,指定該對象的原型。

4.2 Object.create()

在ECMAScript 5 中增加了Object.create()方法,對原型式繼承進(jìn)行了規(guī)范化

Object.create() 方法創(chuàng)建一個新對象,使用現(xiàn)有的對象來提供新創(chuàng)建的對象的__proto__。

(1)語法

Object.create(proto,[propertiesObject])
  • proto: 新創(chuàng)建對象的原型對象。
  • propertiesObject: 可選。需要傳入一個對象,該對象的屬性類型參照Object.defineProperties()的第二個參數(shù)。如果該參數(shù)被指定且不為 undefined,該傳入對象的自有可枚舉屬性(即其自身定義的屬性,而不是其原型鏈上的枚舉屬性)將為新創(chuàng)建的對象添加指定的屬性值和對應(yīng)的屬性描述符。
  • 返回值:一個新對象,帶著指定的原型對象和屬性。

(2)示例

o = Object.create(Object.prototype, {
  // foo會成為所創(chuàng)建對象的數(shù)據(jù)屬性
  foo: {
    writable:true,
    configurable:true,
    value: "hello"
  },
  // bar會成為所創(chuàng)建對象的訪問器屬性
  bar: {
    configurable: false,
    get: function() { return 10 },
    set: function(value) {
      console.log("Setting `o.bar` to", value);
    }
  }
});

(3)手動實(shí)現(xiàn)

function objectCreate(proto, propertiesObject=undefined){
    // 構(gòu)造函數(shù)
    function F() {
    }
    // 構(gòu)造函數(shù)原型 prototype 鏈接到proto對象
    F.prototype = proto;

    // 創(chuàng)建對象
    const obj = new F();
    // 若參數(shù) propertiesObject 被指定且不為 undefined
    if (propertiesObject !== undefined) {
        // 新創(chuàng)建的對象添加指定的屬性值和對應(yīng)的屬性描述符。
        Object.defineProperties(obj, propertiesObject);
    }
    return obj;
}

五、寄生式繼承

5.1 基本思想

創(chuàng)建一個實(shí)現(xiàn)繼承的函數(shù),以某種方式增強(qiáng)對象,然后返回這個對象。

function createAnother(original) {
    // 通過調(diào)用函數(shù)創(chuàng)建一個新對象
    let clone = Object(original);
    // 以某種方式增強(qiáng)這個對象
    clone.sayHi = function() {
        console.log('hi');
    };
    
    // 返回增強(qiáng)的對象
    return clone;
}

個人理解:寄生式繼承就是通過一個函數(shù),以當(dāng)前對象為基礎(chǔ),創(chuàng)建一個新的對象,并為新的對象添加新的方法。

let obj = {};
let anotherObj = createAnother(obj);
anotherObj.sayHi();  // hi

5.2 寄生式繼承

與盜用構(gòu)造函數(shù)類似,寄生式繼承中給對象新增的函數(shù)不能被重用。

六、寄生式組合繼承

針對第三節(jié)中組合繼承存在的問題,可以通過寄生式組合繼承來解決。

6.1 基本思想

不通過調(diào)用父類構(gòu)造函數(shù)給子類原型賦值,而是得到父類原型的一個副本。即使用寄生式繼承來繼承父類原型,然后將返回的新對象賦值給子類原型。

function inheritPrototype(subType, SuperType) {
    // 創(chuàng)建對象
    let prototype = Object(SuperType.prototype);
    // 增強(qiáng)對象(防止修改原型導(dǎo)致constructor丟失)
    prototype.constructor = subType;
    // 賦值對象
    subType.prototype = prototype
}
  • subType:子類構(gòu)造函數(shù)
  • SuperType:父類構(gòu)造函數(shù)

如上代碼:

  • 首先創(chuàng)建一個父類原型的副本
  • 在副本上添加constructor屬性,防止在修改原型時丟失了constructor屬性
  • 最后修改子類構(gòu)造函數(shù)的原型,實(shí)現(xiàn)繼承
function Father(name) {
    this.name = name;
    this.colors = ['red'];
    console.log('父類構(gòu)造函數(shù)調(diào)用了');
}
Father.prototype.sayHello = function() {
    console.log('hello');
}
function Son(name) {
    // 繼承屬性
    Father.call(this, name);
}
// 寄生式繼承原型
inheritPrototype(Son, Father)

// 父類構(gòu)造函數(shù)只在實(shí)例化時調(diào)用一次
let son = new Son('dali');  // 父類構(gòu)造函數(shù)調(diào)用了

// 子類構(gòu)造函數(shù)中不存在不必要的屬性
console.log(Son.prototype)  // {sayHello: ?, constructor: ?}
// 子類構(gòu)造函數(shù)的 constructor 屬性未丟失
console.log(Son.prototype.constructor === Son)  // true

如上代碼,寄生式組合繼承解決了組合繼承存在的一些問題。綜上,寄生式組合繼承可以算是引用類型繼承的最佳模式。

但是,關(guān)于寄生式組合需要注意的一點(diǎn)是:寄生式繼承函數(shù)在創(chuàng)建對象副本時,如果使用的是Object()函數(shù),對于Object()函數(shù)如果給定值是一個已經(jīng)存在的對象,則會返回這個已經(jīng)存在的值(相同地址)。所以函數(shù)中prototype.constructor = subType;會修改父類原型上的constructor屬性。

console.log(Father.prototype.constructor)  // ? Son(name) {// 繼承屬性 Father.call(this, name);}
console.log(Father.prototype.constructor === Father)  // false

但是,這并不會影響父類對象實(shí)例的創(chuàng)建

console.log(new Father('haha'))  // Father?{name: 'haha', colors: Array(1)}

到此這篇關(guān)于分享JavaScript 中的幾種繼承方式的文章就介紹到這了,更多相關(guān)JS繼承方式內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • countUp.js實(shí)現(xiàn)數(shù)字滾動效果

    countUp.js實(shí)現(xiàn)數(shù)字滾動效果

    這篇文章主要為大家詳細(xì)介紹了countUp.js實(shí)現(xiàn)數(shù)字滾動效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2019-10-10
  • 前端算法題解leetcode114二叉樹展開為鏈表

    前端算法題解leetcode114二叉樹展開為鏈表

    這篇文章主要為大家介紹了前端算法題解leetcode114二叉樹展開為鏈表,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-09-09
  • JavaScript懶加載與預(yù)加載原理與實(shí)現(xiàn)詳解

    JavaScript懶加載與預(yù)加載原理與實(shí)現(xiàn)詳解

    這篇文章主要介紹了JavaScript懶加載與預(yù)加載,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-09-09
  • ECharts柱狀圖過多添加滾動條的步驟(親測可用)

    ECharts柱狀圖過多添加滾動條的步驟(親測可用)

    這篇文章主要介紹了ECharts柱狀圖過多添加滾動條的步驟(親測可用),添加echarts柱狀圖滾動條,首先添加js用來判斷當(dāng)前視圖要顯示幾個及是否顯示滾動條,本文結(jié)合實(shí)例代碼介紹的非常詳細(xì),需要的朋友參考下吧
    2024-01-01
  • $$()函數(shù)應(yīng)用實(shí)例

    $$()函數(shù)應(yīng)用實(shí)例

    $$()函數(shù)應(yīng)用實(shí)例...
    2006-10-10
  • JavaScript中的常見繼承總結(jié)

    JavaScript中的常見繼承總結(jié)

    這篇文章主要介紹了JavaScript中的常見繼承總結(jié),繼承其實(shí)就是構(gòu)造函數(shù)和構(gòu)造函數(shù)之間的一種關(guān)系,更多相關(guān)介紹,需要的小伙伴可以參考下面文章內(nèi)容
    2022-09-09
  • 跟我學(xué)習(xí)javascript的undefined與null

    跟我學(xué)習(xí)javascript的undefined與null

    跟我學(xué)習(xí)javascript的undefined與null,從定義上理解null和undefined,告訴大家提高undefined性能的方法,感興趣的小伙伴們可以參考一下
    2015-11-11
  • JS判斷鍵盤是否按的回車鍵并觸發(fā)指定按鈕點(diǎn)擊操作的方法

    JS判斷鍵盤是否按的回車鍵并觸發(fā)指定按鈕點(diǎn)擊操作的方法

    下面小編就為大家?guī)硪黄狫S判斷鍵盤是否按的回車鍵并觸發(fā)指定按鈕點(diǎn)擊操作的方法。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-02-02
  • JavaScript中call、apply、bind實(shí)現(xiàn)原理詳解

    JavaScript中call、apply、bind實(shí)現(xiàn)原理詳解

    其實(shí)在很多文章都會寫call,apply,bind,但個人覺著如果不弄懂原理,是很難理解透的,所以這篇文章主要介紹了JavaScript中call、apply、bind實(shí)現(xiàn)原理的相關(guān)資料,需要的朋友可以參考下
    2021-06-06
  • js中的referrer返回上一頁使用介紹

    js中的referrer返回上一頁使用介紹

    js中的referrer想必大家并不陌生吧,在本文將為大家詳細(xì)介紹其是如何使用的,感興趣的朋友可以參考下,希望對大家有所幫助
    2013-09-09

最新評論