首頁  >  文章  >  web前端  >  對原型鏈上不能使用物件的理解以及JS原型鏈的深刻探討

對原型鏈上不能使用物件的理解以及JS原型鏈的深刻探討

php是最好的语言
php是最好的语言原創
2018-08-01 09:37:541515瀏覽

為什麼不能在原型鏈上使用物件?以及JS原型鏈的深層原理是什麼?

在剛接觸JS原型鏈的時候都會接觸到一個熟悉的名詞:prototype;如果你曾經深入過prototype,你會接觸到另一個名詞:__proto__(注意:兩邊各有兩條底線,不是一條)。以下將會圍繞著prototype__proto__這兩個名詞解釋

  一、為什麼不能在原型鏈上使用物件:

  先舉一個很簡單的例子,我有一個類別叫Humans(人類),然後我有一個物件叫Tom(一個人)和另一個物件叫Merry(另一個人),很明顯Tom和Merry都是由Humans這一類別實例化之後得到的,然後可以把這個例子寫成如下程式碼:

function Humans() {
    this.foot = 2;
}
Humans.prototype.ability = true;var Tom = new Humans();var Merry = new Humans();

console.log(Tom.foot);//结果:2console.log(Tom.ability);//结果:trueconsole.log(Merry.foot);//结果:2console.log(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;
console.log(Tom.foot);//结果:2console.log(Tom.ability);//结果:falseconsole.log(Merry.foot);//结果:2console.log(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米'};console.log(Tom.ability.run); //结果:'50米/10秒'console.log(Tom.ability.jump); //结果:'2米'console.log(Merry.ability.run); //结果:'100米/10秒'console.log(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米';console.log(Tom.ability.run); //结果:'50米/10秒'console.log(Tom.ability.jump); //结果:'2米'console.log(Merry.ability.run); //结果:'50米/10秒'console.log(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瀏覽器,那麼你可以嘗試使用console.log(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__#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米'};console.log(Tom.ability.run); //结果:'50米/10秒'console.log(Tom.ability.jump); //结果:'2米'console.log(Merry.ability.run); //结果:'100米/10秒'console.log(Merry.ability.jump); //结果:'3米'

当为Tom.ability赋予新的值后,再次访问Tom.ability时就不再指向Tom.__proto__.ability了,因为这时其实是为Tom添加了自有属性ability,可以就近取值了,你可以尝试用Chrome浏览器分别console.log(Tom.ability.run)和console.log(Tom.__proto__.ability.run),你会发现确实存在两个不同的值,再看完下面的图后,相信你会完全明白:
對原型鏈上不能使用物件的理解以及JS原型鏈的深刻探討于是可以有这样一个结论:当访问一个对象的属性或方法的时候,如果对象本身有这样一个属性或方法就会取其自身的属性或方法,否则会尝试到原型链(__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米';console.log(Tom.ability.run); //结果:'50米/10秒'console.log(Tom.ability.jump); //结果:'2米'console.log(Merry.ability.run); //结果:'50米/10秒'console.log(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实例化得到的对象,如下图:
對原型鏈上不能使用物件的理解以及JS原型鏈的深刻探討

希望上面所讲的内容足够清楚明白,下面通过类的继承对原型链作更进一步的深入:
先来看一个类的继承的例子,代码如下:

function Person() {
    this.hand = 2;    this.foot = 2;
}
Person.prototype.say = function () {
    console.log('hello');
}function Man() {
    Person.apply(this, arguments);//对象冒充
    this.head = 1;
}
Man.prototype = new Person();//原型链Man.prototype.run = function () {
    console.log('I am running');
};
Man.prototype.say = function () {
    console.log('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’,那么你会惊喜地发现console.log(man1.saySorry)是会弹出结果‘I am sorry’的)。

  以上就是原型链的深层原理,说难其实也算容易,如果细心研究,会发现原型链上有很多惊喜。

相关文章:

js中的作用域链和原型链以及原型继承

js的原型及原型链详解

相关视频:

JavaScript基本語法及基本語句影片教學

以上是對原型鏈上不能使用物件的理解以及JS原型鏈的深刻探討的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn