在軟件工程中,代碼重用的模式極爲重要,由於他們能夠顯著地減小軟件開發的成本。在那些主流的基於類的語言(好比Java,C++)中都是經過繼承(extend)來實現代碼複用,同時類繼承引入了一套類型規範。而JavaScript是一門弱類型的語言,歷來不須要類型裝換,在JavaScript中變量能夠指向任何類型的value(ES6規範中的類也只是語法糖,基於類的繼承本質上也是經過原型實現)。而基於原型的繼承模式能夠說提供了更加豐富的代碼重用模式(後面再詳細講解JavaScript中的經常使用繼承模式,本文只專一於JavaScript中的原型),一個對象能夠直接繼承另一個對象,從而得到新的方法和屬性。javascript
要理解JavaScript中的原型關係,首先必須弄清楚對象的基本概念。ECMAScript 5.1規範中描述的對象java
An object is a collection of properties and has a single prototype object. The prototype may be the null value.
直譯就是:對象是屬性的集合而且擁有一個原型對象。原型多是null(除非故意設置一個對象的原型爲null,不然只有Object.prototype的原型爲null)。咱們能夠簡單把對象想象成hash表。有一種說法是JavaScript中一切都是對象,這種說法並不許確。How is almost everything in Javascript an object?app
JavaScript的原型存在着諸多矛盾。它的某些複雜的語法看起來就像那些基於類的語言,這些語法的問題掩蓋了它的原型機制。它不直接讓對象從其餘對象繼承,反而插入一個多餘的間接層:經過構建器函數產生對象。——JavaScript語言精粹第5章節(繼承)
雖然能夠直接設置一個對象的原型爲另一個對象,從而得到新的方法和屬性。以下所示:ecmascript
// Generic prototype for all letters. let letter = { getNumber() { return this.number } } // 在ES6規範中,已經正式把__proto__屬性添加到規範中 // 也能夠經過Object.setPrototypeOf(obj, prototype) Object.getPrototypeOf(obj) // 設置和獲取對象的原型對象 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 )
對象之間的關係能夠用下圖來表示函數
但規範主要介紹瞭如何利用構造函數去構建原型關係。因此JavaScript語言精粹的做者Douglas Crockford纔會認爲:不讓對象直接繼承另一個對象,而經過中間層(構造函數)去實現顯得有些複雜並且存在一些弊端。調用構造器函數忘記new關鍵字,this將不會綁定到一個新對象上。悲劇的是,this將會綁定到全局對象上。詳情能夠閱讀JavaScript語言精粹繼承章節。ui
下面利用構造函數來實現上述一樣功能this
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 )
其中原型關係能夠下圖表示spa
prototype
和__proto__
屬性咱們看下規範中有關原型介紹的核心,更多詳情請閱讀ECMAScript 5.1 4.2.1章節 prototype
...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...設計
經過上面的描述咱們能夠得出如下結論
構造函數說到底本質上也是一個普通函數,只是該函數專門經過new
關鍵字來生成對象。因此JavaScript語言沒法肯定哪一個函數是打算用來作構造函數的。因此每一個函數都會獲得一個prototype屬性,該屬性值是一個包含constructor屬性且constructor屬性值爲該函數的對象,以下所示。
只有函數才擁有prototype屬性用來實現原型的繼承,其餘對象並無。對象擁有__proto__指向其原型對象,JavaScript引擎可經過內部屬性[[prptotype]]
獲取對象的原型對象。
關於這兩個屬性聯繫能夠用一句話歸納:__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.
若是你已經瞭解JavaScript原型,那咱們能夠來說講JavaScript語法爲何要設計構造函數。
首先來加深一遍概念:JavaScript是一門基於原型繼承的語言,這意味着對象能夠直接從其餘對象繼承屬性,該語言是無類型的。
然而這種設計是偏離主流方向的,當時主流語言JavaScript,C++都是經過 new Class 的語法來建立對象。JavaScript顯然對它的原型本質缺少信心,因此它提供了一套和class
語法相似的對象構建語法——也就是構造函數。經過instanceof
操做符來判斷對象是否屬於某一類型。
MDN介紹了其內部原理
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 操做符就是判斷構造函數的prototype屬性值是否能在object對象的原型鏈中被找到,對就是這麼簡單。這樣經過構造函數語法JavaScript引入了類的概念(僞類)。
知乎用戶wang z在其專欄中發佈一張有關JavaScript原型鏈圖,能夠說看懂了圖片也就清楚了JavaScript中的原型關係,感興趣的用戶能夠直接瀏覽詳情。以下圖所示
筆者就可能讀者遇到的問題備註以下:
若是你最後仍是沒有弄清楚JavaScript中的原型關係,能夠在評論中進行描述我將盡我所能幫你答疑解惑。
或許你也能夠看看參考文獻中的引用連接。