摘要:如下所示在規(guī)范中,已經(jīng)正式把屬性添加到規(guī)范中也可以通過(guò)設(shè)置和獲取對(duì)象的原型對(duì)象對(duì)象之間的關(guān)系可以用下圖來(lái)表示但規(guī)范主要介紹了如何利用構(gòu)造函數(shù)去構(gòu)建原型關(guān)系。
前言
在軟件工程中,代碼重用的模式極為重要,因?yàn)樗麄兛梢燥@著地減少軟件開(kāi)發(fā)的成本。在那些主流的基于類的語(yǔ)言(比如Java,C++)中都是通過(guò)繼承(extend)來(lái)實(shí)現(xiàn)代碼復(fù)用,同時(shí)類繼承引入了一套類型規(guī)范。而JavaScript是一門(mén)弱類型的語(yǔ)言,從來(lái)不需要類型裝換,在JavaScript中變量可以指向任何類型的value(ES6規(guī)范中的類也只是語(yǔ)法糖,基于類的繼承本質(zhì)上也是通過(guò)原型實(shí)現(xiàn))。而基于原型的繼承模式可以說(shuō)提供了更加豐富的代碼重用模式(后面再詳細(xì)講解JavaScript中的常用繼承模式,本文只專注于JavaScript中的原型),一個(gè)對(duì)象可以直接繼承另外一個(gè)對(duì)象,從而獲得新的方法和屬性。
適合人群對(duì)JavaScript原型有一定了解,希望深入了解原型。
具有JavaScript相關(guān)開(kāi)發(fā)經(jīng)驗(yàn)
不適合剛接觸JavaScript人員
對(duì)象要理解JavaScript中的原型關(guān)系,首先必須弄清楚對(duì)象的基本概念。ECMAScript 5.1規(guī)范中描述的對(duì)象
An object is a collection of properties and has a single prototype object. The prototype may be the null value.
直譯就是:對(duì)象是屬性的集合并且擁有一個(gè)原型對(duì)象。原型可能是null(除非故意設(shè)置一個(gè)對(duì)象的原型為null,否則只有Object.prototype的原型為null)。我們可以簡(jiǎn)單把對(duì)象想象成hash表。有一種說(shuō)法是JavaScript中一切都是對(duì)象,這種說(shuō)法并不準(zhǔn)確。How is almost everything in Javascript an object?
原型JavaScript的原型存在著諸多矛盾。它的某些復(fù)雜的語(yǔ)法看起來(lái)就像那些基于類的語(yǔ)言,這些語(yǔ)法的問(wèn)題掩蓋了它的原型機(jī)制。它不直接讓對(duì)象從其他對(duì)象繼承,反而插入一個(gè)多余的間接層:通過(guò)構(gòu)建器函數(shù)產(chǎn)生對(duì)象。——JavaScript語(yǔ)言精粹第5章節(jié)(繼承)
雖然可以直接設(shè)置一個(gè)對(duì)象的原型為另外一個(gè)對(duì)象,從而獲得新的方法和屬性。如下所示:
// Generic prototype for all letters. let letter = { getNumber() { return this.number } } // 在ES6規(guī)范中,已經(jīng)正式把__proto__屬性添加到規(guī)范中 // 也可以通過(guò)Object.setPrototypeOf(obj, prototype) Object.getPrototypeOf(obj) // 設(shè)置和獲取對(duì)象的原型對(duì)象 let a = { number: 1, __proto__: letter } let b = { number: 2, __proto__: letter } // ... let z = { number: 26, __proto__: letter } console.log( a.getNumber(), // 1 b.getNumber(), // 2 z.getNumber() // 26 )
對(duì)象之間的關(guān)系可以用下圖來(lái)表示
但規(guī)范主要介紹了如何利用構(gòu)造函數(shù)去構(gòu)建原型關(guān)系。所以JavaScript語(yǔ)言精粹的作者Douglas Crockford才會(huì)認(rèn)為:不讓對(duì)象直接繼承另外一個(gè)對(duì)象,而通過(guò)中間層(構(gòu)造函數(shù))去實(shí)現(xiàn)顯得有些復(fù)雜而且存在一些弊端。調(diào)用構(gòu)造器函數(shù)忘記new關(guān)鍵字,this將不會(huì)綁定到一個(gè)新對(duì)象上。悲劇的是,this將會(huì)綁定到全局對(duì)象上。詳情可以閱讀JavaScript語(yǔ)言精粹繼承章節(jié)。
下面利用構(gòu)造函數(shù)來(lái)實(shí)現(xiàn)上述同樣功能
function Letter(number) { this.number = number } Letter.prototype.getNumber = function() { return this.number } let a = new Letter(1) let b = new Letter(2) let z = new Letter(26) console.log( a.getNumber(), // 1 b.getNumber(), // 2 z.getNumber() // 26 )
其中原型關(guān)系可以下圖表示
prototype和__proto__屬性我們看下規(guī)范中有關(guān)原型介紹的核心,更多詳情請(qǐng)閱讀ECMAScript 5.1 4.2.1章節(jié)
...Each constructor is a function that has a property named “prototype” that is used to implement prototype-based inheritance and shared properties...Every object created by a constructor has an implicit reference (called the object’s prototype) to the value of its constructor’s “prototype” property. Furthermore, a prototype may have a non-null implicit reference to its prototype, and so on; this is called the prototype chain...
通過(guò)上面的描述我們可以得出以下結(jié)論
構(gòu)造函數(shù)就是一個(gè)函數(shù),函數(shù)中包含prototype屬性用來(lái)實(shí)現(xiàn)基于原型的繼承和共享屬性。通過(guò)Function.prototype.bind方法構(gòu)造出來(lái)的函數(shù)是個(gè)例外,它沒(méi)有prototype屬性。
通過(guò)被構(gòu)造函數(shù)創(chuàng)建的對(duì)象都有一個(gè)隱式的引用指向構(gòu)造函數(shù)的prototype屬性。
構(gòu)造函數(shù)的prototype屬性值同樣也是普通一個(gè)對(duì)象,它也有一個(gè)隱式的引用(non-null)指向它的原型對(duì)象。這樣才形成了原型鏈,所以通過(guò)原型鏈去查找屬性值時(shí)候,并不會(huì)訪問(wèn)prototype屬性,而是obj.__proto__.__proto__...這樣一層一層去尋找。
構(gòu)造函數(shù)說(shuō)到底本質(zhì)上也是一個(gè)普通函數(shù),只是該函數(shù)專門(mén)通過(guò)new關(guān)鍵字來(lái)生成對(duì)象。所以JavaScript語(yǔ)言無(wú)法確定哪個(gè)函數(shù)是打算用來(lái)做構(gòu)造函數(shù)的。所以每個(gè)函數(shù)都會(huì)得到一個(gè)prototype屬性,該屬性值是一個(gè)包含constructor屬性且constructor屬性值為該函數(shù)的對(duì)象,如下所示。
只有函數(shù)才擁有prototype屬性用來(lái)實(shí)現(xiàn)原型的繼承,其他對(duì)象并沒(méi)有。對(duì)象擁有__proto__指向其原型對(duì)象,JavaScript引擎可通過(guò)內(nèi)部屬性[[prptotype]]獲取對(duì)象的原型對(duì)象。
關(guān)于這兩個(gè)屬性聯(lián)系可以用一句話概括:__proto__ is the actual object that is used in the prototype chain to resolve field,methods, etc. prototype is the object that is used to build __proto__ when you create an object with new.
為什么要設(shè)計(jì)構(gòu)造函數(shù)如果你已經(jīng)了解JavaScript原型,那我們可以來(lái)講講JavaScript語(yǔ)法為什么要設(shè)計(jì)構(gòu)造函數(shù)。
首先來(lái)加深一遍概念:JavaScript是一門(mén)基于原型繼承的語(yǔ)言,這意味著對(duì)象可以直接從其他對(duì)象繼承屬性,該語(yǔ)言是無(wú)類型的。
然而這種設(shè)計(jì)是偏離主流方向的,當(dāng)時(shí)主流語(yǔ)言JavaScript,C++都是通過(guò) new Class 的語(yǔ)法來(lái)創(chuàng)建對(duì)象。JavaScript顯然對(duì)它的原型本質(zhì)缺乏信心,所以它提供了一套和class語(yǔ)法類似的對(duì)象構(gòu)建語(yǔ)法——也就是構(gòu)造函數(shù)。通過(guò)instanceof操作符來(lái)判斷對(duì)象是否屬于某一類型。
MDN介紹了其內(nèi)部原理
The instanceof operator tests whether the prototype property of a constructor appears anywhere in the prototype chain of an object.
instanceof操作符的語(yǔ)法
object instanceof constructor
簡(jiǎn)單來(lái)說(shuō):instanceof 操作符就是判斷構(gòu)造函數(shù)的prototype屬性值是否能在object對(duì)象的原型鏈中被找到,對(duì)就是這么簡(jiǎn)單。這樣通過(guò)構(gòu)造函數(shù)語(yǔ)法JavaScript引入了類的概念(偽類)。
最后的彩蛋知乎用戶wang z在其專欄中發(fā)布一張有關(guān)JavaScript原型鏈圖,可以說(shuō)看懂了圖片也就清楚了JavaScript中的原型關(guān)系,感興趣的用戶可以直接瀏覽詳情。如下圖所示
筆者就可能讀者遇到的問(wèn)題備注如下:
Object.__proto__=== Function.prototype。Object本質(zhì)上是一個(gè)built-in的全局構(gòu)造函數(shù),也是Function構(gòu)造函數(shù)的實(shí)例。所以O(shè)bject.__proto__ === Function.prototype.
Number,Date,Array等built-in構(gòu)造函數(shù)都和Object構(gòu)造函數(shù)一樣。
Function.prototype本質(zhì)也是對(duì)象,所以其__proto__指向Object.prototype
最后如果你最后還是沒(méi)有弄清楚JavaScript中的原型關(guān)系,可以在評(píng)論中進(jìn)行描述我將盡我所能幫你答疑解惑。
或許你也可以看看參考文獻(xiàn)中的引用鏈接。
JavaScript. The Core: 2nd Edition
ecma-262
JavaScript Prototype in Plain Language
proto VS. prototype in JavaScript
Javascript語(yǔ)言精粹第五章節(jié)
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/92859.html
摘要:我們用一張圖表示構(gòu)造函數(shù)和實(shí)例原型之間的關(guān)系好了構(gòu)造函數(shù)和實(shí)例原型之間的關(guān)系我們已經(jīng)梳理清楚了,那我們?cè)趺幢硎緦?shí)例與實(shí)例原型,也就是或者和之間的關(guān)系呢。 開(kāi)篇: 在Brendan Eich大神為JavaScript設(shè)計(jì)面向?qū)ο笙到y(tǒng)的時(shí)候,借鑒了Self 和Smalltalk這兩門(mén)基于原型的語(yǔ)言,之所以選擇基于原型的面向?qū)ο笙到y(tǒng),并不是因?yàn)闀r(shí)間匆忙,它設(shè)計(jì)起來(lái)相對(duì)簡(jiǎn)單,而是因?yàn)閺囊婚_(kāi)始B...
摘要:我們用一張圖表示構(gòu)造函數(shù)和實(shí)例原型之間的關(guān)系好了構(gòu)造函數(shù)和實(shí)例原型之間的關(guān)系我們已經(jīng)梳理清楚了,那我們?cè)趺幢硎緦?shí)例與實(shí)例原型,也就是或者和之間的關(guān)系呢。 開(kāi)篇: 在Brendan Eich大神為JavaScript設(shè)計(jì)面向?qū)ο笙到y(tǒng)的時(shí)候,借鑒了Self 和Smalltalk這兩門(mén)基于原型的語(yǔ)言,之所以選擇基于原型的面向?qū)ο笙到y(tǒng),并不是因?yàn)闀r(shí)間匆忙,它設(shè)計(jì)起來(lái)相對(duì)簡(jiǎn)單,而是因?yàn)閺囊婚_(kāi)始B...
摘要:深入之繼承的多種方式和優(yōu)缺點(diǎn)深入系列第十五篇,講解各種繼承方式和優(yōu)缺點(diǎn)。對(duì)于解釋型語(yǔ)言例如來(lái)說(shuō),通過(guò)詞法分析語(yǔ)法分析語(yǔ)法樹(shù),就可以開(kāi)始解釋執(zhí)行了。 JavaScript深入之繼承的多種方式和優(yōu)缺點(diǎn) JavaScript深入系列第十五篇,講解JavaScript各種繼承方式和優(yōu)缺點(diǎn)。 寫(xiě)在前面 本文講解JavaScript各種繼承方式和優(yōu)缺點(diǎn)。 但是注意: 這篇文章更像是筆記,哎,再讓我...
摘要:深入系列的第一篇,從原型與原型鏈開(kāi)始講起,如果你想知道構(gòu)造函數(shù)的實(shí)例的原型,原型的原型,原型的原型的原型是什么,就來(lái)看看這篇文章吧。讓我們用一張圖表示構(gòu)造函數(shù)和實(shí)例原型之間的關(guān)系在這張圖中我們用表示實(shí)例原型。 JavaScript深入系列的第一篇,從原型與原型鏈開(kāi)始講起,如果你想知道構(gòu)造函數(shù)的實(shí)例的原型,原型的原型,原型的原型的原型是什么,就來(lái)看看這篇文章吧。 構(gòu)造函數(shù)創(chuàng)建對(duì)象 我們先...
摘要:探索是如何判斷的表達(dá)式如果函數(shù)的顯式原型對(duì)象在對(duì)象的隱式原型鏈上,返回,否則返回是通過(guò)自己產(chǎn)生的實(shí)例案例案例重要注意的顯示原型和隱式原型是一樣的。面試題測(cè)試題測(cè)試題報(bào)錯(cuò)對(duì)照下圖理解 原型與原型鏈深入理解(圖解) 原型(prototype) 函數(shù)的 prototype 屬性(圖) 每個(gè)函數(shù)都有一個(gè)prototype屬性,它默認(rèn)指向一個(gè)Object空對(duì)象(即稱為:原型對(duì)象) 原型對(duì)象中有...
閱讀 3266·2021-09-29 09:34
閱讀 3617·2021-09-10 10:51
閱讀 2013·2021-09-10 10:50
閱讀 7019·2021-08-12 13:31
閱讀 3059·2019-08-30 15:54
閱讀 1681·2019-08-30 15:44
閱讀 1484·2019-08-29 12:26
閱讀 2713·2019-08-26 18:36