構造函數創建對象
我們首先使用構造函數來創建一個對象。
function A () { }; let a = new A(); a.value = 'a'; console.log(a.value); //輸出的結果為a.
prototype
每一個函數都會有一個prototype屬性(只有函數才具有的屬性),prototype屬性指向的是調用構造函數創建的實例的原型。原型指的是每一個javascript對象在創建的時候(null除外)都會與之關聯的另一個對象。而每一個對象都會從原型中繼承該屬性。
function A () { }; A.prototype.value = 'A'; let a1 = new A(); let a2 = new A(); a1.value = 'a1'; console.log(a1); //輸出a1 --來自實例。 console.log(a2); //輸出A --來自原型。 delete a1.value; console.log(a1); //輸出A --來自原型。
在以上例子中,需要訪問a1中的value時,會在這該對象中搜索名為value的屬性,因為value在該對象中的確存在,因此直接返回該值而不必再去搜索原型;同樣的因為在a2中不存在value,因此會向上搜索原型,結果在原型中查找出了value屬性。
當在對象中添加一個屬性時,這個屬性就會屏蔽在原型對象中保存的同名屬性(a1中的value屏蔽了A中的value)。也就是說添加了這個屬性阻止我們訪問原型中的那個屬性,但不會修改那個屬性。不過使用delete操作符則可以完全刪除實例的屬性,從而重新訪問到原型中的屬性。
用一張圖來表示構造函數和原型的關系:
_proto_
這是每一個對象都具有的屬性(null除外),叫做_proto_,會指向該對象的原型。
function A () { }; let a = new A(); console.log(a.__proto__ === A.prototype); //true
由此我們能夠更新一下關系圖得到:
constructor
既然構造函數和實例對象都能夠指向原型,那么原型是否也包含一個屬性來指向構造函數和實例對象的呢? 指向實例對象是沒有的,因為一個構造函數可以生成多個實例對象。但是指向構造函數的卻是有一個,這就要談到今天要說的第三個屬性constructor了。每一個原型都含有一個constructor屬性用來指向關聯的構造函數。
function A() { }; console.log(A.prototype.constructor === A); //true
所以再次更新關系圖:
綜上我們可以得出一個結論就是:
function A () { }; let a = new A(); console.log(a.__proto__ === A.prototype); //true console.log(A.prototype.constructor === A); //true
原型的由來
原型對象是通過Object構造函數生成的,結合之前所講的_proto_指向構造函數的prototype。
function A () { }; console.log(A.prototype.__proto__ === Object.prototype); //true
由此我們可以得出關系圖為:
接下來我們再看看Object.prototpye的原型來自什么。
console.log(Object.prototype.__proto__); //null
引用阮一峰老師的《undefined和null的區別》
null 表示“沒有對象”,即該處不應該有值。
所以 Object.prototype.__proto__ 的值為 null 跟 Object.prototype 沒有原型,其實表達了一個意思。
所以查找屬性的時候查到 Object.prototype 就可以停止查找了。
最后一張關系圖也可以更新為:
總結:
A.__proto__ === Function.prototype Function.prototype.__proto__ === Object.prototype Object.prototype.__proto__ === null A.protype__proto__ == Object.prototype