深入分析JS原型鏈以及為什么不能在原型鏈上使用對象


  在剛剛接觸JS原型鏈的時候都會接觸到一個熟悉的名詞:prototype;如果你曾經深入過prototype,你會接觸到另一個名詞:__proto__(注意:兩邊各有兩條下划線,不是一條)。以下將會圍繞prototype和__proto__這兩個名詞解釋為什么不能在原型鏈上使用對象以及JS原型鏈的深層原理

  一、為什么不能在原型鏈上使用對象:

  先舉一個非常簡單的例子,我有一個類叫Humans(人類),然后我有一個對象叫Tom(一個人)和另一個對象叫Merry(另一個人),很明顯Tom和Merry都是由Humans這一個類實例化之后得到的,然后可以把這個例子寫成如下代碼:

function Humans() {
    this.foot = 2;
} Humans.prototype.ability = true;

var Tom = new Humans(); var Merry = new Humans(); alert(Tom.foot);//結果:2 alert(Tom.ability);//結果:true alert(Merry.foot);//結果:2 alert(Merry.ability);//結果:true

  以上是一個非常簡單的面向對象的例子,相信都能看懂,如果嘗試修改Tom的屬性ability,則

function Humans() {
    this.foot = 2;
}
Humans.prototype.ability = true;

var Tom = new Humans(); var Merry = new Humans(); Tom.ability = false; alert(Tom.foot);//結果:2 alert(Tom.ability);//結果:false alert(Merry.foot);//結果:2 alert(Merry.ability);//結果:true

  以上可以看出Tom的ability屬性的值改變了,但並不影響Merry的ability屬性的值,這正是我們想要的結果,也是面向對象的好處,由同一個類實例化得到的各個對象之間是互不干擾的;OK,接下來給ability換成object對象又如何?代碼如下:

function Humans() {
    this.foot = 2;
}
Humans.prototype.ability = {
    run : '100米/10秒',
    jump : '3米'
};

var Tom = new Humans();
var Merry = new Humans();

Tom.ability = {
    run : '50米/10秒',
    jump : '2米'
};
alert(Tom.ability.run); //結果:'50米/10秒'
alert(Tom.ability.jump); //結果:'2米'
alert(Merry.ability.run); //結果:'100米/10秒'
alert(Merry.ability.jump); //結果:'3米'

   以上代碼就是在原型鏈上使用了對象,但從以上代碼可以看出Tom的ability屬性的改變依然絲毫不會影響Merry的ability的屬性,於是乎你會覺得這樣的做法並無不妥,為什么說不能在原型鏈上使用對象?接下來的代碼就會顯得很不一樣,並且可以完全表達出原型鏈上使用對象的危險性:

function Humans() {
    this.foot = 2;
}
Humans.prototype.ability = {
    run : '100米/10秒',
    jump : '3米'
};

var Tom = new Humans();
var Merry = new Humans();

Tom.ability.run = '50米/10秒';
Tom.ability.jump = '2米';

alert(Tom.ability.run); //結果:'50米/10秒'
alert(Tom.ability.jump); //結果:'2米'
alert(Merry.ability.run); //結果:'50米/10秒'
alert(Merry.ability.jump); //結果:'2米'

  沒錯,從以上代碼的輸出結果可以看出Tom的ability屬性的改變影響到Merry的ability屬性了,於是就可以明白在原型鏈上使用對象是非常危險的,很容易會打破實例化對象之間的相互獨立性,這就是為什么不能在原型鏈上使用對象的原因?是的,但我想說的可不只如此,而是其中的原理,看完后面JS原型鏈的深層原理之后,相信你會完全明白。

  在以下第二部份解釋JS原型鏈的深層原理之前,先來明確一個概念:原型鏈上的屬性或方法都是被實例化對象共用的,正因如此,上面的Tom.ability.run='50米/10秒',改動了原型連上的ability才導致另一個對象Merry受影響,既然如此,你可能會問Tom.ability = {......}不也是改動了原型鏈上的ability嗎,為什么Merry沒有受影響?答案是Tom.ability = {......}並沒有改動原型鏈上的ability屬性,而是為Tom添加了一個自有屬性ability,以后訪問Tom.ability的時候不再需要訪問原型鏈上的ability,而是訪問其自有屬性ability,這是就近原則;OK,如果你仍有疑問,可以用紙筆記下你的疑問,繼續往下看你會更加明白。

  二、JS原型鏈的深層原理:

  首先要引入一個名詞__proto__,__proto__是什么?在我的理解里,__proto__才是真正的原型鏈,prototype只是一個殼。如果你使用的是chrome瀏覽器,那么你可以嘗試使用alert(Tom.__proto__.ability.run),你發現這樣的寫法完全可行,而且事實上當只有原型鏈上存在ability屬性的時候,Tom.ability其實是指向Tom.__proto__.ability的;當然,如果你跑到IE瀏覽器里嘗試必然會報錯,事實上IE瀏覽器禁止了對__proto__的訪問,而chrome則是允許的,當然實際開發中,我並不建議直接就使用__proto__這一屬性,但它往往在我們調試代碼時發揮着重要作用。有人可能會問到底Tom.__proto__和Humans.prototype是什么關系,為了理清兩者的關系,下面先列出三條法則:

  1、對象是擁有__proto__屬性的,但沒有prototype;例如:有Tom.__proto__,但沒有Tom.prototype。

  2、類沒有__proto__屬性,但有prototype;例如:沒有Humans.__proto__,但有Humans.prototype(這里必須糾正一下,同時非常感謝‘川川哥哥’提出這一處錯處,確實是我在寫到這一點的時候沒有考慮清楚,事實上Humans也是Function的一個實例對象,因此Humans.__proto__===Function.prototype是絕對成立的,稍有特殊的是這時Function.prototype是指向一個Empty(空)函數,值得推敲)。

  3、由同一個類實例化(new)得到的對象的__proto__是引用該類的prototype的(也就是我們說的引用傳遞);例如Tom和Merry的__proto__都引用自Humans的prototype。

   OK,上面說過Tom.ability={......}其實並沒有改變原型鏈上的ability屬性,或者說並沒有改變Tom.__proto__.ability,而是為Tom添加了一個自有的ability屬性,為了說明這一點,我們再次回到以上的第三個代碼塊,其代碼如下:

function Humans() {
    this.foot = 2;
}
Humans.prototype.ability = {
    run : '100米/10秒',
    jump : '3米'
};

var Tom = new Humans();
var Merry = new Humans();

Tom.ability = {
    run : '50米/10秒',
    jump : '2米' };
alert(Tom.ability.run); //結果:'50米/10秒'
alert(Tom.ability.jump); //結果:'2米'
alert(Merry.ability.run); //結果:'100米/10秒'
alert(Merry.ability.jump); //結果:'3米'

  當為Tom.ability賦予新的值后,再次訪問Tom.ability時就不再指向Tom.__proto__.ability了,因為這時其實是為Tom添加了自有屬性ability,可以就近取值了,你可以嘗試用Chrome瀏覽器分別alert(Tom.ability.run)和alert(Tom.__proto__.ability.run),你會發現確實存在兩個不同的值,再看完下面的圖后,相信你會完全明白:

  於是可以有這樣一個結論:當訪問一個對象的屬性或方法的時候,如果對象本身有這樣一個屬性或方法就會取其自身的屬性或方法,否則會嘗試到原型鏈(__proto__)上尋找同名的屬性或方法。明白了這一點后,要解釋以上第四個代碼塊的原理也非常容易了,其代碼如下:

function Humans() {
    this.foot = 2;
}
Humans.prototype.ability = {
    run : '100米/10秒',
    jump : '3米'
};

var Tom = new Humans();
var Merry = new Humans();

Tom.ability.run = '50米/10秒';
Tom.ability.jump = '2米';

alert(Tom.ability.run); //結果:'50米/10秒'
alert(Tom.ability.jump); //結果:'2米'
alert(Merry.ability.run); //結果:'50米/10秒'
alert(Merry.ability.jump); //結果:'2米'

  當Tom.ability.run='50米/10秒'的時候,JS引擎會認為Tom.ability是存在的,因為有Tom.ability才會有Tom.ability.run,所以引擎開始尋找ability屬性,首先是會從Tom的自有屬性里尋找,在自有屬性里並沒有找到,於是到原型鏈里找,結果找到了,於是Tom.ability就指向了Tom.__proto__.ability了,修改Tom.ability.run的時候實際上就是修改了原型鏈上的ability了,因而影響到了所有由Humans實例化得到的對象,如下圖:

  希望上面所講的內容足夠清楚明白,下面通過類的繼承對原型鏈作更進一步的深入:

  先來看一個類的繼承的例子,代碼如下:

function Person() {
    this.hand = 2;
    this.foot = 2;
}
Person.prototype.say = function () {
    alert('hello');
}
function Man() {
    Person.apply(this, arguments);//對象冒充
    this.head = 1;
}
Man.prototype = new Person();//原型鏈
Man.prototype.run = function () {
    alert('I am running');
};
Man.prototype.say = function () {
    alert('good byte');
}
var man1 = new Man();

   以上代碼是使用對象冒充和原型鏈相結合的混合方法實現類的繼承,也是目前JS主流的實現類的繼承的方法,如果對這種繼承方法缺乏了解,可以看看這里

  接下來看看以上實現繼承后的原型鏈,可以運用prototype和__proto__來解釋其中的原理:

  1、從man1 = new Man(),可以知道man1的__proto__是指向Man.prototype的,於是有:

  公式一:man1.__proto__ === Man.prototype 為true

  2、從上面的代碼原型鏈繼承里面看到這一句代碼 Man.prototype = new Person(),作一個轉換,變成:Man.prototype = a,a = new Perosn();一個等式變成了兩個等式,於是由a = new Perosn()可以推導出a.__proto__ = Person.prototype,結合Man.prototype = a,於是可以得到:

  公式二:Man.prototype.__proto__ === Person.prototype 為true

  由公式一和公式二我們就得出了以下結論:

  公式三:man1.__proto__.__proto__ === Person.prototype 為true

  公式三就是上述代碼的原型鏈,有興趣的話,可以嘗試去推導多重繼承的原型鏈,繼承得越多,你會得到一個越長的原型鏈,而這就是原型鏈的深層原理;從公式三可以得出一個結論:當你訪問一個對象的屬性或方法時,會首先在自有屬性尋找(man1),如果沒有則到原型鏈找,如果在鏈上的第一環(第一個__proto__)沒找到,則到下一環找(下一個__proto__),直到找到為止,如果到了原型鏈的盡頭仍沒找到則返回undefined(這里必須補充一點:同時非常感謝深藍色夢想提出的疑問:盡頭不是到了Object嗎?是的,原型鏈的盡頭就是Object,如果想問為什么,不妨做一個小小的實驗:如果指定Object.prototype.saySorry = 'I am sorry',那么你會驚喜地發現alert(man1.saySorry)是會彈出結果‘I am sorry’的)。

  以上就是原型鏈的深層原理,說難其實也算容易,如果細心研究,會發現原型鏈上有很多驚喜。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM