為什麼不能在原型鏈上使用物件?以及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.
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),你会发现确实存在两个不同的值,再看完下面的图后,相信你会完全明白: 于是可以有这样一个结论:当访问一个对象的属性或方法的时候,如果对象本身有这样一个属性或方法就会取其自身的属性或方法,否则会尝试到原型链(
__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实例化得到的对象,如下图:
希望上面所讲的内容足够清楚明白,下面通过类的继承对原型链作更进一步的深入:
先来看一个类的继承的例子,代码如下:
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原型鏈的深刻探討的詳細內容。更多資訊請關注PHP中文網其他相關文章!

JavaScript在Web開發中的主要用途包括客戶端交互、表單驗證和異步通信。 1)通過DOM操作實現動態內容更新和用戶交互;2)在用戶提交數據前進行客戶端驗證,提高用戶體驗;3)通過AJAX技術實現與服務器的無刷新通信。

理解JavaScript引擎內部工作原理對開發者重要,因為它能幫助編寫更高效的代碼並理解性能瓶頸和優化策略。 1)引擎的工作流程包括解析、編譯和執行三個階段;2)執行過程中,引擎會進行動態優化,如內聯緩存和隱藏類;3)最佳實踐包括避免全局變量、優化循環、使用const和let,以及避免過度使用閉包。

Python更適合初學者,學習曲線平緩,語法簡潔;JavaScript適合前端開發,學習曲線較陡,語法靈活。 1.Python語法直觀,適用於數據科學和後端開發。 2.JavaScript靈活,廣泛用於前端和服務器端編程。

Python和JavaScript在社區、庫和資源方面的對比各有優劣。 1)Python社區友好,適合初學者,但前端開發資源不如JavaScript豐富。 2)Python在數據科學和機器學習庫方面強大,JavaScript則在前端開發庫和框架上更勝一籌。 3)兩者的學習資源都豐富,但Python適合從官方文檔開始,JavaScript則以MDNWebDocs為佳。選擇應基於項目需求和個人興趣。

從C/C 轉向JavaScript需要適應動態類型、垃圾回收和異步編程等特點。 1)C/C 是靜態類型語言,需手動管理內存,而JavaScript是動態類型,垃圾回收自動處理。 2)C/C 需編譯成機器碼,JavaScript則為解釋型語言。 3)JavaScript引入閉包、原型鍊和Promise等概念,增強了靈活性和異步編程能力。

不同JavaScript引擎在解析和執行JavaScript代碼時,效果會有所不同,因為每個引擎的實現原理和優化策略各有差異。 1.詞法分析:將源碼轉換為詞法單元。 2.語法分析:生成抽象語法樹。 3.優化和編譯:通過JIT編譯器生成機器碼。 4.執行:運行機器碼。 V8引擎通過即時編譯和隱藏類優化,SpiderMonkey使用類型推斷系統,導致在相同代碼上的性能表現不同。

JavaScript在現實世界中的應用包括服務器端編程、移動應用開發和物聯網控制:1.通過Node.js實現服務器端編程,適用於高並發請求處理。 2.通過ReactNative進行移動應用開發,支持跨平台部署。 3.通過Johnny-Five庫用於物聯網設備控制,適用於硬件交互。

我使用您的日常技術工具構建了功能性的多租戶SaaS應用程序(一個Edtech應用程序),您可以做同樣的事情。 首先,什麼是多租戶SaaS應用程序? 多租戶SaaS應用程序可讓您從唱歌中為多個客戶提供服務


熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

AI Hentai Generator
免費產生 AI 無盡。

熱門文章

熱工具

MinGW - Minimalist GNU for Windows
這個專案正在遷移到osdn.net/projects/mingw的過程中,你可以繼續在那裡關注我們。 MinGW:GNU編譯器集合(GCC)的本機Windows移植版本,可自由分發的導入函式庫和用於建置本機Windows應用程式的頭檔;包括對MSVC執行時間的擴展,以支援C99功能。 MinGW的所有軟體都可以在64位元Windows平台上運作。

Dreamweaver CS6
視覺化網頁開發工具

WebStorm Mac版
好用的JavaScript開發工具

ZendStudio 13.5.1 Mac
強大的PHP整合開發環境

記事本++7.3.1
好用且免費的程式碼編輯器