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