溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

一文看懂JS繼承

發(fā)布時(shí)間:2020-07-19 16:50:13 來源:網(wǎng)絡(luò) 閱讀:194 作者:qq593e097eaab3c 欄目:web開發(fā)

繼承是OOP中大家最喜歡談?wù)摰膬?nèi)容之一,一般來說,繼承都兩種方式:接口繼承和實(shí)現(xiàn)繼承而JavaScript中沒有接口繼承需要的方法,因此只能依靠實(shí)現(xiàn)繼承。
在講繼承的實(shí)現(xiàn)之前,我們首先來回顧一下什么是繼承?繼承的作用是什么?

它可以使用現(xiàn)有類的所有功能,并在無需重新編寫原來的類的情況下對(duì)這些功能進(jìn)行擴(kuò)展。
通過繼承創(chuàng)建的新類稱為“子類”或“派生類”。
被繼承的類稱為“基類”、“父類”或“超類”。
在清楚繼承的作用之后,下面我們來探討一下JS中的幾種繼承實(shí)現(xiàn)的方式:
//混入式繼承(拷貝)//obj2繼承到obj1中的成員,可以直接將obj1中的成員拷貝到obj2中即可var obj1 = {name:"zs",age:10};var obj2 = {};// 將obj1中的成員拷貝到obj2中for (var key in obj1) {
obj2[key] = obj1[key];
}console.log(obj1);console.log(obj2);
最終得到的obj2中的成員和obj1中的成員完全一致,當(dāng)然,我們需要清除的是,此時(shí)的obj1和obj2是兩個(gè)不同的對(duì)象。
混入式繼承方式看似很簡單,但是存在共享數(shù)據(jù)安全的問題。
var obj1 = {name:"zs",age:10,car:{name:"mini"}};var obj2 = {};// 將obj1中的成員拷貝到obj2中for (var key in obj1) {
obj2[key] = obj1[key];
}//修改obj1對(duì)象中的car屬性
obj1.car.name = "bus";
console.log(obj1);console.log(obj2);
當(dāng)我們需要修改某些對(duì)象中的引用類型對(duì)象的時(shí)候,會(huì)造成其他相關(guān)的對(duì)象也被修改,這是我們不希望看到的。
原型式繼承的實(shí)現(xiàn)
回想一下,當(dāng)我們在訪問一個(gè)對(duì)象中的成員的時(shí)候,首先是在當(dāng)前對(duì)象中查找,如果找不到,就往上,在原型鏈中依次查找,如果在整個(gè)原型鏈中也不存在該成員,那么就返回undefined。
所以,我們想要在A對(duì)象中訪問到B對(duì)象中的成員,除了將B中的成員添加到A中,如:混入式,我們也可以考慮將B中成員添加到A的原型鏈中,實(shí)現(xiàn)對(duì)象成員的共享。
function Animal() {
}
Animal.prototype.name="animal";function Person() {
}//修改Person的原型對(duì)象
Person.prototype= Animal.prototype;

Person.prototype.useTool = function () {
console.log("use fire");
}var p = new Person();console.log(p);var ani = new Animal();console.log(ani);
畫圖分析:

  1. 最初,Animal和Person的兩個(gè)對(duì)象沒有任何關(guān)系,所以各自只能訪問各自的成員
    一文看懂JS繼承
  2. 現(xiàn)在,Person對(duì)象如果想要繼承Animal對(duì)象,只需要將Person的原型對(duì)象修改為Animal的原型對(duì)象即可
    一文看懂JS繼承
    這種方式實(shí)現(xiàn)的繼承稱之為原型繼承,實(shí)現(xiàn)也是比較方便的,當(dāng)時(shí)和混入式一樣,存在數(shù)據(jù)共享的問題。
    原型鏈繼承的實(shí)現(xiàn)
    原型式繼承
    在前面的課程中,我們講了原型式的繼承,這種繼承方式是修改子類對(duì)象原型指向父類對(duì)象的原型,如前面的MyArray執(zhí)行的是Array的原型對(duì)象。
    這種方式存在問題是,只能繼承父類對(duì)象原型上的成員,但無法繼承父類對(duì)象中的成員。
    function Animal() {
    this.color="red";
    }
    Animal.prototype.weight=100;function Person() {
    }
    Person.prototype = Animal.prototype;var p = new Person();console.log(p);
    Person.prototype = Animal.prototype;原型式的繼承
    此時(shí)創(chuàng)建出來的Person對(duì)象p可以訪問Animal中的weight屬性,但是無法訪問color屬性。看圖理解
    一文看懂JS繼承
    那么,如果我們既要繼承原型對(duì)象中的成員,也要繼承實(shí)例對(duì)象中的成員,就應(yīng)該使用這一節(jié)中的原型鏈繼承
    function Animal() {
    this.color="red";
    }
    Animal.prototype.weight=100;function Person() {
    }
    Person.prototype = new Animal();var p = new Person();console.log(p.color);//red

一文看懂JS繼承
使用原型鏈繼承方式能夠繼承到更多的成員。但是依然存在問題:

  1. 共享的問題
  2. 無法向構(gòu)造器傳參
    復(fù)雜的原型鏈繼承示例---
    Object-Animal-Person-Boy
    Object.create方法的基本使用
    在Object的構(gòu)造函數(shù)上存在一個(gè)create方法,該方法的作用是用來創(chuàng)建對(duì)象的。
    該方法可以接收的參數(shù)有一下兩種
  3. null

  4. 創(chuàng)建一個(gè)空對(duì)象,這個(gè)空對(duì)象中連最基本的原型對(duì)象都沒有的

  5. 對(duì)象

  6. 創(chuàng)建傳遞進(jìn)來的對(duì)象,并設(shè)置該對(duì)象的原型對(duì)象為當(dāng)前的參數(shù)

  7. 一文看懂JS繼承
    該方法的使用率比較低,要求大家知道存在這樣的一種創(chuàng)建對(duì)象的方式即可。
    call方法和apply方法的基本使用
    call和apply方法作用:

  8. 方法借用2. 設(shè)置方法中this的指向
    var obj1 = {
    name:"Neld",
    age:10,
    showInfo:function () {
    console.log("name:"+this.name,"age:"+this.age);
    }
    }var obj2 = {
    name:"Lily",
    age:12
    }
    obj1.showInfo();//name:Neld age:10
    obj2.showInfo();//obj2.showInfo is not a function
    obj1對(duì)象中有showInfo方法,而obj2對(duì)象中沒有,所以如果直接使用obj2調(diào)用showInfo方法的時(shí)候拋出錯(cuò)誤信息。
    如果我們臨時(shí)需要使用obj2調(diào)用showInfo方法來打印出name和age屬性的值,此時(shí)可以使用這里的call或者apply方法來實(shí)現(xiàn)。
    obj1.showInfo.call(obj2);//name:Lily age:12
    obj1.showInfo.apply(obj2);//name:Lily age:12
    這就是把obj1中的showInfo方法借用給obj2使用。
    同時(shí)我們觀察到,在showInfo方法中使用到了this關(guān)鍵字,在obj2借用該方法的時(shí)候,其中的this已經(jīng)指向了obj2對(duì)象,這就要達(dá)到修改方法中this關(guān)鍵字的指向的目的。
    call和apply方法的作用是完全一樣的,那么他們的區(qū)別是什么呢?繼續(xù)往下分析。
    var obj1 = {
    name:"Neld",
    age:10,
    add : function (a, b) {
    return a + b;
    }
    }var obj2 = {
    name:"Lily",
    age:12
    }console.log(obj1.add.call(obj2, 1, 2));//3//console.log(obj1.add.apply(obj2, 1, 2));//CreateListFromArrayLike called on non-objectconsole.log(obj1.add.apply(obj2, [2, 2]));//4
    在obj1中定義一個(gè)帶有兩個(gè)參數(shù)的方法,obj2中沒有,問題一樣,obj2也要使用到obj1中的add方法,此時(shí)使用call或者apply借用即可。
    此時(shí)新的問題是,在調(diào)用add方法的時(shí)候參數(shù)應(yīng)該如何傳遞?
    call方法:
    ? 將this指向的對(duì)象作為第一個(gè)參數(shù),其他參數(shù)依次傳遞即可
    apply方法:
    ? 將this指向的對(duì)象作為第一個(gè)參數(shù),其他參數(shù)封裝到數(shù)組中傳遞
    ? 如果沒有使用數(shù)組,程序報(bào)錯(cuò)。
    以上就是call和apply的基本使用,這兩個(gè)方法在后續(xù)的課程中會(huì)大量的使用到,所以必須引起重視。
    借用構(gòu)造函數(shù)繼承說明
    所謂借用構(gòu)造函數(shù),就是在子構(gòu)造函數(shù)中調(diào)用父構(gòu)造函數(shù),達(dá)到繼承并向父構(gòu)造函數(shù)傳參的目的。
    function SuperClass(name,age) {
    this.name = name;
    this.age = age;
    }
    SuperClass.prototype.fun1 = function () {
    console.log("name:",this.name,"age:",this.age);
    }function SubClass(color) {
    this.color = color;
    }
    SubClass.prototype = new SuperClass("xxx",10);//繼承父構(gòu)造函數(shù)并設(shè)置name和age的值
    SubClass.prototype.fun2 = function () {
    console.log("color:",this.color);
    }var sub = new SubClass("red", "Neld", 10);var sub2 = new SubClass("red", "Lily", 12);console.log(sub);console.log(sub2);
    上面是原型鏈的繼承,這種方式存在一個(gè)問題是,在創(chuàng)建不同對(duì)象的時(shí)候,無法為其繼承過來的成員賦值。
    這里的sub和sub2兩個(gè)對(duì)象的name和age屬性值都是“xxx”和10,很明顯是不滿足我們需求的。
    那么我們來看看借用構(gòu)造函數(shù)是否能解決這個(gè)問題呢?
    function SuperClass(name,age) {
    this.name = name;
    this.age = age;
    }
    SuperClass.prototype.fun1 = function () {
    console.log("name:",this.name,"age:",this.age);
    }function SubClass(color, name, age) {
    this.newMethod = SuperClass;//①
    this.newMethod(name, age);//②
    this.color = color;
    delete this.newMethod;//③
    }//SubClass.prototype = new SuperClass();
    SubClass.prototype.fun2 = function () {
    console.log("color:",this.color);
    }var sub = new SubClass("red", "Neld", 10);var sub2 = new SubClass("red", "Lily", 12);console.log(sub);console.log(sub2);
    ①、②、③處代碼是實(shí)現(xiàn)借用構(gòu)造函數(shù)的關(guān)鍵。下面一一作出解釋:
    ①:將父對(duì)象的構(gòu)造函數(shù)設(shè)置為子對(duì)象的成員
    ②:調(diào)用這個(gè)方法,想過類似于將父構(gòu)造函數(shù)的代碼放在子構(gòu)造函數(shù)中來執(zhí)行
    function SubClass(color, name, age) {
    this.newMethod = SuperClass;
    this.name = name;//父構(gòu)造函數(shù)中的代碼
    this.age = age;//父構(gòu)造函數(shù)中的代碼
    this.color = color;
    delete this.newMethod;
    }
    這樣看應(yīng)該更直觀一點(diǎn),執(zhí)行之后就是在為當(dāng)前創(chuàng)建出來的對(duì)象封裝name和age屬性。
    ③:在子構(gòu)造函數(shù)中,newMethod僅僅為了在②調(diào)用父構(gòu)造函數(shù)使用,用完之后也就沒了價(jià)值,所以,直接刪除該方法即可
    一文看懂JS繼承
    可以看到,借用構(gòu)造函數(shù)繼承方式解決了原型鏈及繼承的問題。
    下面再看看另外一種借用構(gòu)造函數(shù)的實(shí)現(xiàn)方式(使用call或apply):
    function SubClass(color, name, age) {
    //SuperClass.call(this,name,age);
    SuperClass.apply(this,[name,age]);
    this.color = color;
    }
    我們可以使用call或apply更簡單快捷的實(shí)現(xiàn)和上面一樣的效果。
    以上就是借用構(gòu)造函數(shù)繼承(也要對(duì)象冒充)的兩種實(shí)現(xiàn)方式。當(dāng)然,這種繼承方式都存在下面兩個(gè)問題:
  9. 如果父子構(gòu)造函數(shù)存在相同的成員,那么子構(gòu)造函數(shù)會(huì)覆蓋父構(gòu)造函數(shù)中的成員
  10. 不能繼承原型鏈中的成員
    組合繼承的基本結(jié)構(gòu)
    基于原型鏈繼承和借用構(gòu)造函數(shù)繼承兩種方式存在的問題,我們提出一個(gè)解決方案---組合繼承,就是兩種一起使用。
    function SubClass(color, name, age) {
    //SuperClass.call(this,name,age);
    SuperClass.apply(this,[name,age]);//繼承構(gòu)造函數(shù)中的成員
    this.color = color;
    }

SubClass.prototype = new SuperClass();//繼承原型鏈上的成員
總結(jié): ECMAScript 實(shí)現(xiàn)繼承的方式不止一種。這是因?yàn)?JavaScript 中的繼承機(jī)制并不是明確規(guī)定的,而是通過模仿實(shí)現(xiàn)的。這意味著所有的繼承細(xì)節(jié)并非完全由解釋程序處理。作為開發(fā)者,你有權(quán)決定最適用的繼承方式。
繪制完整的原型鏈結(jié)構(gòu)圖
這一節(jié)重點(diǎn)探討函數(shù)對(duì)象的原型鏈結(jié)構(gòu)。完整的結(jié)構(gòu)圖如下:
一文看懂JS繼承
說了這么多,其實(shí)核心只有一個(gè):屬性共享和獨(dú)立的控制,當(dāng)你的對(duì)象實(shí)例需要獨(dú)立的屬性,所有做法的本質(zhì)都是在對(duì)象實(shí)例里面創(chuàng)建屬性。若不考慮太多,你大可以在Person里面直接定義你所需要獨(dú)立的屬性來覆蓋掉原型的屬性。總之,使用原型繼承的時(shí)候,要對(duì)于原型中的屬性要特別注意,因?yàn)樗麄兌际菭恳话l(fā)而動(dòng)全身的存在。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI