JavaScript 學習筆記 之 原型 (一)

[[Prototype]]簡介

關於[[prototype]](__proto__)和prototype對象的不同

  1. [[prototype]]幾乎所有對象都有的一個屬性,存放的是一個對其他對象的引用,在部分瀏覽器(不是所有)中可以用.__proto__(兩根下劃線)這個屬性來進行訪問,每個普通的對象的[[prototype]]鏈最後都會指向Object.prototype這個對象
  2. prototype是所有的函數會擁有的一個屬性,也是指向一個對象,而使用new操作符使用這個函數創建的每一個對象最終也會被[[prototype]]連接到這個.prototype對象,而這個.prototype對象有一個.constructor屬性,默認指向這個函數

然後我們來詳細介紹下[[prototype]]這個屬性

我們可以通過Object.create(..)來關聯對象的[[Prototype]]屬性

你可能會對這個屬性感到陌生,但事實上你可能經常間接地調用過這個屬性

比如在訪問對象的屬性時會觸發一個[[Get]]操作(比如 obj.a這個操作)

對於默認的[[Get]]操作來說,如果對象沒有這個屬性,會訪問這個對象完整的的[[Prototype]]鏈

直到找到這個屬性或者查找完整條[[Prototype]]鏈然後返回這個屬性或者一個undefined值

		var obj1 = {
			a: 1
		}
		var obj2 = Object.create(obj1); //將obj2的[[Prototype]]關聯到obj1
		console.log(obj2.hasOwnProperty("a")); //false,對象本身沒有這個屬性
		console.log(obj2.a); //1

使用for .. in遍歷對象時,原理和查找原型鏈類似,任何可以通過原型鏈訪問到的可枚舉的屬性都會被枚舉

使用in操作符的時候,同樣會查找對象的整條原型鏈(無論是否可枚舉)

		var obj1 = {
			a: 1
		}
		var obj2 = Object.create(obj1); //將obj2的[[Prototype]]關聯到obj1
		for(let i in obj2) {
			console.log(i); //a
		}
		console.log("a" in obj2); //true

因此,你通過各種語法進行屬性查找的時候都會遍歷整個原型鏈,一直到內置的Object.prototype

因爲普通對象的原型鏈頂層都是這個Object.prototype對象

所以這個對象也包含了JavaScript中許多通用的功能

比如.toString()或者.valueOf()

 

屬性設置和屏蔽

說完對象屬性的[[Get]]操作,我們來說說對應的[[Put]]操作

[[Put]]操作也遠不是簡單的添加一個新屬性或者修改一個屬性已有的值那麼簡單

比如 obj.a= "123" 這個過程

  1. 如果a這個屬性直接存在於obj中,那麼很簡單,只會修改obj中b這個屬性的值(如果原型鏈上也有這個屬性,那麼會發生屏蔽obj中的屬性會屏蔽掉原型鏈中的屬性,因爲obj.a總是會選擇原型鏈最底層的a屬性)
  2. 如果a不直接存在於obj中,那麼原型鏈就會被遍歷,如果遍歷完整個原型鏈都找不到,則在obj中添加一個a屬性並賦值
  3. 如果a不直接存在於obj中,但是原型鏈中又有這麼個屬性,那麼又需要另外分3種情況

當a不存在於obj中但是存在於原型鏈中時, obj.a= "123" 會發生的幾種情況

  1. 如果原型鏈上層存在名爲a的普通數據訪問屬性並且屬性描述符中writable沒有被標記爲false,那麼就會直接在obj中添加一個a屬性並賦值
  2. 如果原型鏈上層存在a,但是a的writable爲false,那麼obj中也無法被寫入a屬性,並且在嚴格模式下還會拋出一個錯誤
  3. 如果原型鏈上層存在a,並且它設置爲了一個setter,那麼a不會被添加到obj,也不會重新定義a這個setter,只會調用這個setter

如果需要在存在第二第三種情況下依舊屏蔽原型鏈上的a屬性,那麼不能使用=操作符來賦值,需要用Object.defineProperty(..)來添加這一屬性

似乎JavaScript這一機制很讓人難以理解,爲什麼一個對象會因爲另一個對象有一個屬性而沒辦法創建一個同名的屬性呢

其實這麼做是爲了模擬類屬性的繼承,你可以吧原型鏈上層中的屬性看做父類的屬性,他會被obj繼承(複製)

這麼一來obj中的a屬性也是隻讀,所以無法被賦值,也無法被創建

但是一定要注意,事實上JavaScript是不存在類似的繼承複製的!舉這個例子只是爲了方便理解!事實上發生的只是使用[[Prototype]]關聯到了另一個對象而已!

現在你可能會很好奇,爲什麼要關聯到另一個對象,這樣做有什麼好處?

首先我們要明確一個概念,JavaScript中不存在類!

其他面向類的語言會用類來作爲對象的抽象模式或者說藍圖,來描述對象的行爲

但是JavaScript中只有對象,由對象自己來定義自己的行爲

 

"構造函數"

new這個關鍵詞很容易讓人誤解Foo是一個"類"

調用 new Foo() 創建的每個對象都將最終被[[Prototype]]這個屬性鏈接到"Foo.prototype"這個對象

		function Foo() {
			this.name = "a";
		}
		var a = new Foo();
		console.log(
			Object.getPrototypeOf(a) === Foo.prototype //true
		)

在介紹this的綁定規則的時候我們介紹過new操作符的四個步驟

  1. 創建一個對象
  2. 設定這個對象的[[prototype]]
  3. 將函數中的this綁定到這個對象
  4. 如果函數中沒有返回其他對象,那麼把這個對象返回

其中第二步設定這個對象的[[prototype]]其實就是將a的[[prototype]]鏈接到了Foo.prototype指向的對象

在面對類的語言中,類可以被複制(實例化)很多次,就像用模具製作東西一樣

但是在JavaScript中沒有類似的複製機制,你不能創建一個類的多個實例

你只能創建多個對象,而他們的[[prototype]]鏈接的是同一個對象

比如上面例子中的 var a= new Foo()

new Foo()創建了一個新對象,這個對象的內部鏈接[[prototype]]關聯到了Foo.prototype對象(Object.create(..)是直接關聯到對應對象)

我們沒有從"類"中複製任何一個行爲到一個對象中,我們只是把兩個對象互相關聯

(事實上new Foo()這個函數調用實際上沒有直接創建關聯,這是一個間接的行爲,但是Object.create(..)是直接創建的關聯)

 

除了"構造函數"這個語義以外,Foo.prototype還有一個很迷惑人的屬性 .constructor

Foo.prototype默認(聲明時)有一個公有且不可枚舉的屬性.constructor

		function Foo() {
			this.name = "a";
		}
		var a = new Foo();
		console.log(
			Foo.prototype.constructor === Foo, //true
			a.constructor === Foo //true
		)

這個屬性引用的是對象關聯的函數(本例中是Foo),似乎表示"創建這個對象的函數"(但是這是錯誤的)

看起來似乎a.constructor===Foo意味着a有一個指向Foo的.constructor屬性

但事實上a並沒有這個屬性,在介紹[[Get]]操作的時候我們說過,[[Get]]操作存在一個遍歷原型鏈的行爲

a.constructor只是通過默認的[[Prototype]]委託指向Foo,這跟"構造"毫無關係

舉例來說,Foo.prototype的.constructor屬性指向Foo只是聲明時的默認屬性,如果你創建了一個新對象並替換掉了默認的.prototype引用

那麼新對象並不會自動改變並獲取.constructor屬性

		function Foo() {
			this.name = "a";
		}
		Foo.prototype = {};
		var a = new Foo();
		console.log(
			a.constructor === Foo, //false
			a.constructor === Object //true
		)

這個例子中Foo.prototype被指向了一新對象,這個對象中並沒有.constructor屬性,所以它會繼續委託,一直到頂端的Object.prototype

所以事實上它調用的是Object.prototype.constructor屬性,指向內置的Object()函數

事實上Foo.prototype對象有一個.constructor屬性默認指向一個函數(Foo),這個函數也有一個叫做.prototype的引用指向這個對象

僅此而已,並不代表着constructor所指向的函數構造了這個對象

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章