真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

徹底弄懂JS原型與繼承-創(chuàng)新互聯(lián)

本文由淺到深,循序漸進(jìn)的將原型與繼承的抽象概念形象化,且每個(gè)知識(shí)點(diǎn)都搭配相應(yīng)的例子,盡可能的將其通俗化,而且本文大的優(yōu)點(diǎn)就是:長(為了更詳細(xì)嘛)。

創(chuàng)新互聯(lián)公司主營舟曲網(wǎng)站建設(shè)的網(wǎng)絡(luò)公司,主營網(wǎng)站建設(shè)方案,app軟件定制開發(fā),舟曲h5微信平臺(tái)小程序開發(fā)搭建,舟曲網(wǎng)站營銷推廣歡迎舟曲等地區(qū)企業(yè)咨詢

一、原型

首先,我們先說說原型,但說到原型就得從函數(shù)說起,因?yàn)樵蛯?duì)象就是指函數(shù)所擁有的prototype屬性(所以下文有時(shí)說原型,有時(shí)說prototype,它們都是指原型)。

1.1 函數(shù)

說到函數(shù),我們得先有個(gè)概念:函數(shù)也是對(duì)象,和對(duì)象一樣擁有屬性,例如:

function F(a, b) {
    return a * b;
}

F.length   // 2  指函數(shù)參數(shù)的個(gè)數(shù)
F.constructor   // function Function() { [native code] }
typeof F.prototype  // "object"

從上面我們可以看出函數(shù)和對(duì)象一樣擁有屬性,我們重點(diǎn)說的就是prototype這個(gè)原型屬性。

prototype也是一個(gè)對(duì)象,為了更形象的理解,我個(gè)人是把上述理解為這樣的:

// F這個(gè)函數(shù)對(duì)象里有個(gè)prototype對(duì)象屬性
F = {
    prototype: {}
}

下面我們就說說這個(gè)prototype對(duì)象屬性。

1.2 prototype對(duì)象的屬性

prototype是一個(gè)對(duì)象,里面有個(gè)默認(rèn)屬性constructor,默認(rèn)指向當(dāng)前函數(shù),我們依舊使用F這個(gè)函數(shù)來說明:

F = {
    prototype: {
        constructor: F    // 指向當(dāng)前函數(shù)
    }
}

既然prototype是個(gè)對(duì)象,那我們也同樣可以給它添加屬性,例如:

F.prototype.name = 'BetterMan';

// 那F就變成如下:
F = {
    prototype: {
        constructor: F,
        name: 'BetterMan'
    }
}

prototype就先鋪墊到這,下面我們來說說對(duì)象,然后再把它們串起來。

1.3 創(chuàng)建對(duì)象

創(chuàng)建對(duì)象有很多種方式,本文針對(duì)的是原型,所以就說說使用構(gòu)造函數(shù)創(chuàng)建對(duì)象這種方式。上面的F函數(shù)其實(shí)就是一個(gè)構(gòu)造函數(shù)(構(gòu)造函數(shù)默認(rèn)名稱首字母大寫便于區(qū)分),所以我們用它來創(chuàng)建對(duì)象。

let f = new F();
console.log(f)  // {}

這時(shí)得到了一個(gè)“空”對(duì)象,下面我們過一遍構(gòu)造函數(shù)創(chuàng)建對(duì)象的過程:

  1. 創(chuàng)建一個(gè)新對(duì)象;
  2. 將構(gòu)造函數(shù)的作用域賦給新對(duì)象,即把this指向新對(duì)象(同時(shí)還有一個(gè)過程,新對(duì)象的proto屬性指向構(gòu)造函數(shù)的ptototype屬性,后面會(huì)解釋這塊)。
  3. 執(zhí)行函數(shù)內(nèi)代碼,即為新對(duì)象添加屬性。
  4. 返回新對(duì)象(不需要寫,默認(rèn)返回this,this就是指新對(duì)象)。

下面我們修改一下F構(gòu)造函數(shù):

function F(age) {
    this.age = age;
}

再用F來創(chuàng)建一個(gè)實(shí)例對(duì)象:

let f1 = new F(18);  // 18歲,別來無恙
console.log(f1); // {age: 18}

其實(shí)我們就得到了一個(gè)f1對(duì)象,里面有一個(gè)age屬性,但真的只有age屬性嗎?上面我們講到構(gòu)造函數(shù)創(chuàng)建對(duì)象的過程,這里的新建對(duì)象,然后給對(duì)象添加屬性,然后返回新對(duì)象,我們都是看得到的,還有一個(gè)過程,就是新對(duì)象的__proto__屬性指向構(gòu)造函數(shù)的ptototype屬性。

我們打印一下看看:

console.log(f1.__proto__);  // {constructor: F}

這不就是F構(gòu)造函數(shù)的prototype對(duì)象嗎?這個(gè)指向過程也就相當(dāng)于f1.__proto__ === F.prototype,理解這個(gè)很重要!

__proto__我們可稱為隱式原型(不是所有瀏覽器都支持這個(gè)屬性,所以谷歌搞起),這個(gè)就厲害了,既然它指向了構(gòu)造函數(shù)的原型,那我們獲取到它也就能獲取到構(gòu)造函數(shù)的原型了(但一般我們不用這個(gè)方法獲取原型,后面會(huì)介紹其他方法)。

前面我們說了構(gòu)造函數(shù)的prototype對(duì)象中的constructor屬性是指向自身函數(shù)的,那我們用__proto__來驗(yàn)證一下:

console.log(f1.__proto__.constructor);  // F(age) {this.age = age;}
// 因?yàn)閒1.__proto__ === F.prototype,所以上述就是指F.prototype.constructor

嗯,不錯(cuò)不錯(cuò),看來沒毛?。?/p>

目前來說應(yīng)該還是比較好理解的,那我們再看看:

console.log(f1.constructor);  // F(age) {this.age = age;}

額,這什么鬼?難道實(shí)例對(duì)象f1還有個(gè)constructor屬性和構(gòu)造函數(shù)原型的constructor一樣都是指向構(gòu)造函數(shù)?這就有點(diǎn)意思了。

其實(shí)不是,應(yīng)該是說f1的神秘屬性__proto__指向了F.prototype,這相當(dāng)于一個(gè)指向引用,如果要形象點(diǎn)的話可以把它理解為把F.prototype的屬性"共享"到了f1身上,但這是動(dòng)態(tài)的"共享",如果后面F.prototype改變的話,f1所"共享"到的屬性也會(huì)跟著改變。理解這個(gè)很重要!重要的事情說三遍!重要的事情說三遍!重要的事情說三遍!

那我們再把代碼"形象化":

F = {
    prototype: {
        constructor: F
    }
};

f1 = {
    age: 18,
    __proto__: {    // 既然我們已經(jīng)把這個(gè)形象化為"共享"屬性了,那就再形象一點(diǎn)
        constructor: F
    }
}

// 更形象化:
f1 = {
    age: 18,  // 這個(gè)是f1對(duì)象自身屬性
    constructor: F  // 這個(gè)是從原型上"共享"的屬性
}

既然我們說的是動(dòng)態(tài)"共享"屬性,那我們改一改構(gòu)造函數(shù)的prototype屬性看看f1會(huì)不會(huì)跟著改變:

// 沒改之前
console.log(f1.name);  // undefined

// 修改之后
F.prototype.name = 'BetterMan';
console.log(f1);   // {age: 18}
console.log(f1.name);  // 'BetterMan'

A(讀A第二調(diào))……,看來和想的一毛一樣啊,但是f1上面沒看到name屬性,那就是說我們只是可以從構(gòu)造函數(shù)的原型上拿到name屬性,而不是把name變?yōu)閷?shí)例對(duì)象的自身屬性。說到這里就得提提對(duì)象自身屬性和原型屬性(從原型上得來的屬性)了。

1.4 對(duì)象自身屬性和原型屬性

我們所創(chuàng)建的實(shí)例對(duì)象f1,有自身屬性age,還有從原型上找到的屬性name,我們可以使用hasOwnProperty方法檢測一下:

console.log(f1.hasOwnProperty('age'));  // true 說明是自身屬性
console.log(f1.hasOwnProperty('name')); // false 說明不是自身屬性

那既然是對(duì)象屬性,應(yīng)該就可以添加和刪除吧?我們試試:

delete f1.age;
console.log(f1.age); // undefined

delete f1.name;
console.log(f1.name); // 'BetterMan'

額,age屬性刪除成功了,但好像name沒什么反應(yīng),比較堅(jiān)挺,這就說明了f1對(duì)象可以掌控自身的屬性,愛刪刪愛加加,但name屬性是從原型上得到的,是別人的屬性,你可沒有權(quán)利去修改。

其實(shí)我們在訪問對(duì)象的name屬性時(shí),js引擎會(huì)依次查詢f1對(duì)象上的所有屬性,但是找不到這個(gè)屬性,然后就會(huì)去創(chuàng)建f1實(shí)例對(duì)象的構(gòu)造函數(shù)的原型上找(這就歸功于神秘屬性proto了,是它把實(shí)例對(duì)象和構(gòu)造函數(shù)的原型聯(lián)系了起來),然后找到了(如果再找不到的話,還會(huì)往上找,這就涉及到原型鏈了,后面我們會(huì)說到)。而找age屬性時(shí)直接就在f1上找到了,就不用再去其他地方找了。

到現(xiàn)在大家應(yīng)該對(duì)原型有了個(gè)大概的理解了吧,但它有什么用呢?
用處大大的,可以說我們無時(shí)無刻都在使用它,下面我們繼續(xù)。

二、繼承

講了原型,那肯定是離不開繼承這個(gè)話題的,說到繼承就很熱鬧了,什么原型模式繼承、構(gòu)造函數(shù)模式繼承、對(duì)象模式繼承、屬性拷貝模式繼承、多重繼承、寄生式繼承、組合繼承、寄生組合式繼承……這什么鬼?這么多,看著是不是很頭疼?

我個(gè)人就把它們分為原型方式、構(gòu)造函數(shù)方式、對(duì)象方式這三個(gè)方式,然后其他的繼承方式都是基于這三個(gè)方式的組合,當(dāng)然這只是我個(gè)人的理解哈,下面我們開始。

2.1 原型鏈

說到繼承,肯定得說原型鏈,因?yàn)樵玩準(zhǔn)抢^承的主要方法。

我們先來簡單的回顧一下構(gòu)造函數(shù)、原型和實(shí)例的關(guān)系:每個(gè)構(gòu)造函數(shù)都有一個(gè)原型對(duì)象,原型對(duì)象都包含一個(gè)指向構(gòu)造函數(shù)的指針(constructor),而實(shí)例偶讀包含一個(gè)指向原型對(duì)象的內(nèi)部指針(__proto__)。那么,假如我們讓原型對(duì)象等于另一個(gè)實(shí)例對(duì)象,結(jié)果會(huì)怎么樣呢?顯然,此時(shí)的原型對(duì)象將包含一個(gè)指向另一個(gè)原型的指針(__proto__),相應(yīng)的,另一個(gè)原型中也包含著一個(gè)指向另一個(gè)構(gòu)造函數(shù)的指針(constructor)。那假如另一個(gè)原型又是另一個(gè)對(duì)象實(shí)例,那么上述關(guān)系依然成立,如此層層遞進(jìn),就構(gòu)成了實(shí)例與原型的鏈條。這就是所謂的原型鏈,如圖:

到這里千萬不要亂,一定要理解了這段話再往下看,其實(shí)就是把別人的實(shí)例對(duì)象賦值給了我們的構(gòu)造函數(shù)的原型,這就是第一層,然后如果別人的實(shí)例對(duì)象的構(gòu)造函數(shù)的原型又是另一個(gè)人的實(shí)例對(duì)象的話,那不是一樣的道理嗎?這就是第二層,那如果再出現(xiàn)個(gè)第三者,那又是一層了,這就構(gòu)成了一個(gè)層層連起來的原型鏈。

好了,如果你看到了這里,說明已經(jīng)理解了上述"鏈情",那我們就開始搞搞繼承。

2.2 繼承方式

繼承有多重形式,我們一個(gè)個(gè)來,分別對(duì)比一下其中的優(yōu)缺點(diǎn)。

注:因?yàn)槎鄶?shù)繼承都依賴于原型及原型鏈,所以當(dāng)再依賴于其他方式時(shí),我就以這個(gè)方式來命名這個(gè)繼承方式,這樣看起來就不會(huì)那么復(fù)雜。

1. 基于構(gòu)造函數(shù)方式

我們先定義三個(gè)構(gòu)造函數(shù):

// 構(gòu)造函數(shù)A
function A() {
    this.name = 'A';
    this.say = function() {
        return this.name;
    };
};
// 構(gòu)造函數(shù)B
function B() {
    this.name = 'B';
};
// 構(gòu)造函數(shù)C
function C(width, height) {
    this.name = 'C';
    this.width = width;
    this.height = height;
    this.getArea = function() {
        return this.width * this.height;
    };
};

下面我們試試?yán)^承:

B.prototype = new A();
C.prototype = new B();

上述是不是有點(diǎn)熟悉,是不是就是前面所提的原型鏈的概念:B構(gòu)造函數(shù)的原型被賦上A構(gòu)造函數(shù)的實(shí)例對(duì)象,然后C的原型又被賦上B構(gòu)造函數(shù)的實(shí)例對(duì)象。

然后我們用C構(gòu)造函數(shù)來創(chuàng)建一個(gè)實(shí)例對(duì)象:

let c1 = new C(2, 6);
console.log(c1);   // {name: "C", width: 2, height: 6, getArea: ?}
console.log(c1.name);  // 'C'
console.log(c1.getArea()); // 12
console.log(c1.say());  // 'C'

c1居然有say方法了,可喜可賀,它是怎么做到的?讓我們來捋捋這個(gè)過程:

  • ①首先C新建了一個(gè)"空"對(duì)象;
  • ②然后this指向這個(gè)"空"對(duì)象;
  • ③c1.proto指向C.prototype;
  • ④給this對(duì)象賦值,這樣就有了name、widthheight、getArea這四個(gè)自身屬性;
  • ⑤返回this對(duì)象,此時(shí)我們就得到了c1實(shí)例對(duì)象;
  • ⑥然后打印console.log(c1)console.log(c1.name)console.log(c1.getArea())都好理解;
  • ⑦接著console.log(c1.say()),這就得去找say方法了,js引擎先在c1身上找,沒找到,然后c1.__proto__這個(gè)神秘鏈接是指向C構(gòu)造函數(shù)的原型的,然后就去C.prototype上找,然后我們是寫有C.prototype = new B()的,也就是說是去B構(gòu)造函數(shù)的實(shí)例對(duì)象上找,還是沒有,那繼續(xù),又通過new B().__proto__B的原型上找,然后我們是寫有B.prototype = new A();,那就是去A所創(chuàng)建的實(shí)例對(duì)象上找,沒有,那就又跑去A構(gòu)造函數(shù)的原型上找,OK!找到!

這個(gè)過程就相當(dāng)于這樣:
c1 —→ C.prototype —→ new B() —→ B.prototype —→ new A() —→ A.prototype

這就是上述的一個(gè)基于構(gòu)造函數(shù)方式的繼承過程,其實(shí)就是一個(gè)查找過程,但是大家有沒有發(fā)現(xiàn)什么?

上述方式存在兩個(gè)問題:第一個(gè)問題就是constructor的指向。

本來B.prototype中的constructor指向好好的,是指向B的,但現(xiàn)在B.prototype完全被new A()給替換了,那現(xiàn)在的B.prototype.constructor是指向誰的?我們看看:

console.log(B.prototype.constructor);  // ? A() {}
let b1 = new B();
console.log(b1.constructor);   // ? A() {}

此時(shí)我們發(fā)現(xiàn)不僅是B.prototype.constructor指向A,連b1也是如此,別忘了b1中的constructor屬性也是由B.prototype所共享的,所以老大(B)改變了,小弟(b1)當(dāng)然也會(huì)跟著動(dòng)態(tài)改變。

但現(xiàn)在它們?yōu)槭裁词侵赶?code>A的呢?因?yàn)?code>B.prototype被替換為了new A(),那new A()里有什么?我們再把B.prototypenew A()形象化來表示一下:

A = {
    prototype:{
        constructor: A
    }
};

new A() = {
    name: 'A',
    say: function() {
        return this.name;
    },
    constructor: A       // 由__proto__的指向所共享得到的
}

B = {
    prototype:{
        constructor: B
    }
};

// 這時(shí)把B.prototype換為new A(),那就變成了這樣:
B = {
    prototype:{
        name: 'A',
        say: function() {
            return this.name;
        },
        constructor: A   // 所以指向就變成了A
    }
};

所以我們要手動(dòng)修正B.prototype.constructor的指向,同理C.prototype.constructor的指向也是如此:

B.prototype = new A();
B.prototype.constructor = B;
C.prototype = new B();
C.prototype.constructor = C;

第一個(gè)問題解決了,到第二個(gè)問題:效率的問題。

當(dāng)我們用某一個(gè)構(gòu)造函數(shù)創(chuàng)建對(duì)象時(shí),其屬性就會(huì)被添加到this中去。并且當(dāng)別添加的屬性實(shí)際上是不會(huì)隨著實(shí)例改變時(shí),這種做法會(huì)顯得沒有效率。例如在上面的實(shí)例中,A構(gòu)造函數(shù)是這樣定義的:

function A() {
    this.name = 'A';
    this.say = function() {
        return this.name;
    };
};

這種實(shí)現(xiàn)意味著我們用new A()創(chuàng)建的每個(gè)實(shí)例都會(huì)擁有一個(gè)全新的name屬性和say屬性,并在內(nèi)存中擁有獨(dú)立的存儲(chǔ)空間。所以我們應(yīng)該考慮把這些屬性放到原型上,讓它們實(shí)現(xiàn)共享:

// 構(gòu)造函數(shù)A
function A() {};
A.prototype.name = 'A';
A.prototype.say = function() {
    return this.name;
};

// 構(gòu)造函數(shù)B
function B() {};
B.prototype.name = 'B';

// 構(gòu)造函數(shù)C
function C(width, height) {  // 此處的width和height屬性是隨參數(shù)變化的,所以就不需要改為共享屬性
    this.width = width;
    this.height = height;
};
C.prototype.name = 'C';
C.prototype.getArea = function() {
    return this.width * this.height;
};

這樣一來,構(gòu)造函數(shù)所創(chuàng)建的實(shí)例中一些屬性就不再是私有屬性了,而是在原型中能共享的屬性,現(xiàn)在我們來試試:

let test1 = new A();
let test2 = new A();
console.log(test1.say === test2.say);  // true 沒改為共享屬性前,它們是不相等的

雖然這樣做通常更有效率,但也只是針對(duì)實(shí)例中不可變屬性而言的,所以在定義構(gòu)造函數(shù)時(shí)我們也要考慮哪些屬性適合共享,哪些適合私有。

2. 基于原型的方式

正如上面所做的,處于效率考慮,我們應(yīng)當(dāng)盡可能的將一些可重用的屬性和方法添加到原型中去,這樣的話我們僅僅依靠原型就可以完成繼承關(guān)系的構(gòu)建了,由于原型上的屬性都是可重用的,這也意味著從原型上繼承比在實(shí)例上繼承要好得多,而且既然需要繼承的屬性都放在了原型上,又何必生成實(shí)例降低效率,然后又從所生成的實(shí)例中繼承不需要的私有屬性呢?所以我們直接拋棄實(shí)例,從原型上繼承:

B.prototype = A.prototype;
B.prototype.constructor = B;
C.prototype = B.prototype;
C.prototype.constructor = C;

嗯,這樣感覺效率高多了,也比較養(yǎng)眼,然后我們試試效果:

let c2 = new C();
console.log(c2.say());  // 'A'

(⊙o⊙)…不是應(yīng)該打印出C的嗎?怎么和我內(nèi)心的小完美不太一樣?

想必大家應(yīng)該都看出來了,上面的繼承方式其實(shí)就相當(dāng)于A、B、C全都共享了A的原型,那就造成了引用問題,要是C的原型屬性修改了,那AB的原型屬性豈不是都被修改了?想想就委屈,小弟居然管起大哥來了。

有沒有兩全其美的辦法,我又要效率,又不想委屈,啪!把這兩個(gè)方法結(jié)合起來不就行了嗎?!

3. 結(jié)合構(gòu)造函數(shù)方式和原型的方式

我既想快,又不想被小弟管,搞個(gè)第三者來解決怎么樣?(怎么感覺聽起來怪怪的)。我們在它們中間使用一個(gè)臨時(shí)構(gòu)造函數(shù)(所以也可稱為臨時(shí)構(gòu)造法)來做個(gè)橋梁,把小弟管大哥的關(guān)系斷掉(腿打斷),然后大家又可以高效率的合作:

let X = function() {};   // 新建一個(gè)"空"屬性的構(gòu)造函數(shù)
X.prototype = A.prototype;  // 將X的原型指向A的原型
B.prototype = new X();  // B的原型指向X創(chuàng)建的實(shí)例對(duì)象
B.prototype.constructor = B;  // 記得修正指向

// 同上
let Y = function() {};  
Y.prototype = B.prototype;
C.prototype = new Y();
C.prototype.constructor = C;

現(xiàn)在試試效果怎么樣:

let c3 = new C;
console.log(c3.say());  // A

穩(wěn)!這樣我們既不是直接繼承實(shí)例上的屬性,而是繼承原型所共享的屬性,而且還能通過XY這兩個(gè)"空"屬性構(gòu)造函數(shù)來把A和B上的非共享屬性過濾掉(因?yàn)?code>new X()比起new A()所生成的實(shí)例,因?yàn)?code>X是空的,所以不會(huì)生成的對(duì)象不會(huì)存在私有屬性,但是new A()可能會(huì)存在私有屬性,既然是私有屬性,所以也就是不需要被繼承,所以new A()會(huì)存在效率問題和多出不需要的繼承屬性)。

4. 基于對(duì)象的方式

這種基于對(duì)象的方式其實(shí)包括幾種方式,因?yàn)槎己蛯?duì)象相關(guān),所以我就統(tǒng)稱為對(duì)象方式了,下面一一介紹:

①以接收對(duì)象的方式

function create(o) {  // o是所要繼承的父對(duì)象
    function F() {};
    F.prototype = o;
    return new F();  // 返回一個(gè)實(shí)例對(duì)象
};
let a = {
    name: 'better'
};
console.log(create(a).name);  // 'better'

這種方式是接受一個(gè)父對(duì)象后返回一個(gè)實(shí)例,進(jìn)而達(dá)到繼承的效果,有沒有點(diǎn)似曾相識(shí)的感覺?這不就是低配版的Object.create()嗎?有興趣的可以多去了解了解。所以這個(gè)方式其實(shí)也應(yīng)該稱為"原型繼承法",因?yàn)橐彩且孕薷脑蜑榛A(chǔ)的,但又和對(duì)象相關(guān),所以我就把它歸為對(duì)象方式了,這樣比較好分類。

②以拷貝對(duì)象屬性的方式

// 直接將父原型的屬性拷貝過來,好處是Child.prototype.constructor沒被重置,但這種方式僅適用于只包含基本數(shù)據(jù)類型的對(duì)象,且父對(duì)象會(huì)覆蓋子對(duì)象的同名屬性
function extend(Child, Parent) {   // Child, Parent都為構(gòu)造函數(shù)
    let c = Child.prototype;
    let p = Parent.prototype;
    for (let i in p) {
        c[i] = p[i];
    }
};
// 這種直接拷貝屬性的方式簡單粗暴,直接復(fù)制傳入的對(duì)象屬性,但還是存在引用類型的問題
function extendCopy(p) {   // p是被繼承的對(duì)象
    let c = {};
    for (let i in p) {
        c[i] = p[i];
    }
    return c;
};
// 上面的extendCopy可稱為淺拷貝,沒有解決引用類型的問題,現(xiàn)在我們使用深拷貝,這樣就解決了引用類型屬性的問題,因?yàn)椴还苣阌卸嗌僖妙愋?,全都一個(gè)個(gè)拷過來
function deepCopy(p, c) {  // c和p都是對(duì)象
    c = c || {};
    for (let i in p) {
        if (p.hasOwnProperty[i]) {   // 排除繼承屬性
            if (typeof p[i] === 'object') {  // 解決引用類型
                c[i] = Array.isArray(p[i]) ? [] : {};
                deepCopy[p[i], c[i]];
            } else {
                c[i] = p[i];
            }
        }
    }
    return c;
}

③拷貝多對(duì)象屬性的方式

// 這種方式就可以一次拷貝多個(gè)對(duì)象屬性,也稱為多重繼承
function multi() {
    let n = {},
    stuff,
    j = 0,
    len = arguments.length;
    for (j = 0; j < len; j++) {
        stuff = arguments[j];
        for (let i in stuff) {
            if (stuff.hasOwnProperty(i)) {
                n[i] = stuff[i];
            }
        }
    }
    return n
};

④吸收對(duì)象屬性并擴(kuò)展的方式

這種方式其實(shí)應(yīng)該叫做"寄生式繼承",這名字乍看很抽象,其實(shí)也就那么回事,所以也把它分到對(duì)象方式里:

// 其實(shí)也就是在創(chuàng)建對(duì)象的函數(shù)中吸收了其它對(duì)象的屬性(寄生獸把別人的xx吸走),然后對(duì)其擴(kuò)展并返回
let parent = {
    name: 'parent',
    toString: function() {
        return this.name;
    }
};
function raise() {
    let that = create(parent);  // 使用前面我們寫過的create函數(shù)
    that.other = 'Once in a blue moon!'; // 今天學(xué)的,丑顯唄一下
    return that;
}

和對(duì)象相關(guān)的方式是不是有點(diǎn)多?但其實(shí)也都是圍繞著對(duì)象屬性的,理解這點(diǎn)就好理解了,下面繼續(xù)。

5. 構(gòu)造函數(shù)借用法

這個(gè)方式其實(shí)也可歸為構(gòu)造函數(shù)方式,但比較溜,所以單獨(dú)拎出來溜溜(這是最后一個(gè)了,我保證)。

我們再把之前定義的老函數(shù)A拿出來炒炒:

// 構(gòu)造函數(shù)A
function A() {
    this.name = 'A';
};
A.prototype.say = function() {
    return this.name;
};

// 構(gòu)造函數(shù)D
function D() {
    A.apply(this, arguments);  // 這里就相當(dāng)于借用A構(gòu)造函數(shù)把A中屬性創(chuàng)建給了D,即name和say屬性
};
D.prototype = new A();  // 這里負(fù)責(zé)拿到A原型上的屬性

這樣兩個(gè)步驟是不是就把A的自身屬性和原型屬性都搞定了?簡單完美!

等等,看起來好像有點(diǎn)不對(duì),A.apply(this, arguments)已經(jīng)完美的把A自身屬性變?yōu)榱?code>D的自身屬性,但是D.prototype = new A()又把A的自身屬性繼承了一次,真是多此一舉,既然我們只是單純的想要原型上的屬性,那直接拷貝不就完事了嗎?

// 構(gòu)造函數(shù)A
function A() {
    this.name = 'A';
};
A.prototype.say = function() {
    return this.name;
};

// 之前定義的屬性拷貝函數(shù)
function extend2(Child, Parent) {
    let c = Child.prototype;
    let p = Parent.prototype;
    for (let i in p) {
        c[i] = p[i];
    }
};

// 構(gòu)造函數(shù)D
function D() {
    A.apply(this, arguments);  // 這里就相當(dāng)于借用A構(gòu)造函數(shù)把A中屬性創(chuàng)建給了D,即name和say屬性
};
extend2(D, A);  // 這里就直接把A原型的屬性拷貝給了D原型

let d1 = new D();
console.log(d1.name);  // 'A'
console.log(d1.__proto__.name)  // undefined 這就說明了name屬性是新建的,而不是繼承得到的

(⊙o⊙)…,其實(shí)還有其它的繼承方法,還是不寫了,怕被打,但其實(shí)來來去去就是基于原型、構(gòu)造函數(shù)、對(duì)象這幾種方式搞來搞去,我個(gè)人就是這么給它們分類的,畢竟七秒記憶放不下,囧。

最后

寫到這里,終于咽下了最后一口氣,呸,松了一口氣。也感謝你看到了最后,希望對(duì)你有所幫助,有寫得不對(duì)的地方還請多多指教,順口廣告來一波:大家好!我是BetterMan, to be better, to be man, better關(guān)注BetterMan!

github源碼
掘金
博客園

另外有需要云服務(wù)器可以了解下創(chuàng)新互聯(lián)scvps.cn,海內(nèi)外云服務(wù)器15元起步,三天無理由+7*72小時(shí)售后在線,公司持有idc許可證,提供“云服務(wù)器、裸金屬服務(wù)器、高防服務(wù)器、香港服務(wù)器、美國服務(wù)器、虛擬主機(jī)、免備案服務(wù)器”等云主機(jī)租用服務(wù)以及企業(yè)上云的綜合解決方案,具有“安全穩(wěn)定、簡單易用、服務(wù)可用性高、性價(jià)比高”等特點(diǎn)與優(yōu)勢,專為企業(yè)上云打造定制,能夠滿足用戶豐富、多元化的應(yīng)用場景需求。


網(wǎng)站欄目:徹底弄懂JS原型與繼承-創(chuàng)新互聯(lián)
地址分享:http://weahome.cn/article/copjho.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部