對原型鏈上不能使用物件的理解以及JS原型鏈的深刻探討
為什麼不能在原型鏈上使用物件?以及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中文網其他相關文章!

熱AI工具

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

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

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

Video Face Swap
使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

熱工具

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

SublimeText3漢化版
中文版,非常好用

禪工作室 13.0.1
強大的PHP整合開發環境

Dreamweaver CS6
視覺化網頁開發工具

SublimeText3 Mac版
神級程式碼編輯軟體(SublimeText3)

人臉偵測辨識技術已經是一個比較成熟且應用廣泛的技術。而目前最廣泛的網路應用語言非JS莫屬,在Web前端實現人臉偵測辨識相比後端的人臉辨識有優勢也有弱勢。優點包括減少網路互動、即時識別,大大縮短了使用者等待時間,提高了使用者體驗;弱勢是:受到模型大小限制,其中準確率也有限。如何在web端使用js實現人臉偵測呢?為了實現Web端人臉識別,需要熟悉相關的程式語言和技術,如JavaScript、HTML、CSS、WebRTC等。同時也需要掌握相關的電腦視覺和人工智慧技術。值得注意的是,由於Web端的計

如何使用JS和百度地圖實現地圖平移功能百度地圖是一款廣泛使用的地圖服務平台,在Web開發中經常用於展示地理資訊、定位等功能。本文將介紹如何使用JS和百度地圖API實作地圖平移功能,並提供具體的程式碼範例。一、準備工作使用百度地圖API前,首先需要在百度地圖開放平台(http://lbsyun.baidu.com/)上申請一個開發者帳號,並建立一個應用程式。創建完成

股票分析必備工具:學習PHP和JS繪製蠟燭圖的步驟,需要具體程式碼範例隨著網路和科技的快速發展,股票交易已成為許多投資者的重要途徑之一。而股票分析是投資人決策的重要一環,其中蠟燭圖被廣泛應用於技術分析。學習如何使用PHP和JS繪製蠟燭圖將為投資者提供更多直觀的信息,幫助他們更好地做出決策。蠟燭圖是一種以蠟燭形狀來展示股票價格的技術圖表。它展示了股票價格的

如何使用PHP和JS創建股票蠟燭圖股票蠟燭圖是股票市場中常見的技術分析圖形,透過繪製股票的開盤價、收盤價、最高價和最低價等數據,幫助投資者更直觀地了解股票的價格波動情形。本文將教你如何使用PHP和JS創建股票蠟燭圖,並附上具體的程式碼範例。一、準備工作在開始之前,我們需要準備以下環境:1.一台運行PHP的伺服器2.一個支援HTML5和Canvas的瀏覽器3

如何使用JS和百度地圖實現地圖點擊事件處理功能概述:在網路開發中,經常需要使用地圖功能來展示地理位置和地理資訊。而地圖上的點擊事件處理是地圖功能中常用且重要的一環。本文將介紹如何使用JS和百度地圖API來實現地圖的點擊事件處理功能,並給出具體的程式碼範例。步驟:匯入百度地圖的API檔案首先,要在HTML檔案中匯入百度地圖API的文件,可以透過以下程式碼實現:

如何使用JS和百度地圖實現地圖熱力圖功能簡介:隨著互聯網和行動裝置的快速發展,地圖成為了普遍的應用場景。而熱力圖作為一種視覺化的展示方式,能夠幫助我們更直觀地了解數據的分佈。本文將介紹如何使用JS和百度地圖API來實現地圖熱力圖的功能,並提供具體的程式碼範例。準備工作:在開始之前,你需要準備以下事項:一個百度開發者帳號,並建立一個應用,取得到對應的AP

隨著網路金融的快速發展,股票投資已經成為了越來越多人的選擇。而在股票交易中,蠟燭圖是常用的技術分析方法,它能夠顯示股票價格的變動趨勢,幫助投資人做出更精準的決策。本文將透過介紹PHP和JS的開發技巧,帶領讀者了解如何繪製股票蠟燭圖,並提供具體的程式碼範例。一、了解股票蠟燭圖在介紹如何繪製股票蠟燭圖之前,我們首先需要先了解什麼是蠟燭圖。蠟燭圖是由日本人

js和vue的關係:1、JS作為Web開發基石;2、Vue.js作為前端框架的崛起;3、JS與Vue的互補關係;4、JS與Vue的實踐應用。
