理解前端數據雙向綁定原理——Object.defineProperty()

Object.defineProperty與vue之間的關係

vue是經過數據劫持的方式來作數據綁定的,最核心的方法是經過 Object.defineProperty()方法來實現對屬性的劫持,達到能監聽到數據的變更。要實現數據的雙向綁定。前端

理解前端數據雙向綁定原理:Object.defineProperty()

Object.definedProperty方法能夠在一個對象上直接定義一個新的屬性、或修改一個對象已經存在的屬性,最終返回這個對象。vue

Object.defineProperty 須要三個參數(object , propName , descriptor)數組

1 object 對象 => 被定義或修改屬性的對象;前端框架

2 propName 屬性名 => 要加的屬性的名字 【類型:String】框架

3 descriptor 屬性描述 => 加的這個屬性有什麼樣的特性【類型:Object】函數

返回值

函數將返回傳遞給他的obj對象自己。性能

描述符(descriptor)說明

該方法容許開發者精確的對對象屬性的定義和修改。經過正常賦值進行屬性添加而構建的屬性會被枚舉器方法(如for…in循環或Object.keys方法)獲取,從而致使屬性值被外部方法改變或刪除。而Object.defineProperty()能夠避免以上描述的狀況,默認的,經過Object.defineProperty()添加的屬性是默認不可改變的。
屬性描述參數(descriptor)主要由兩部分構成:數據描述符(data descriptor)和訪問器描述符(accessor descriptor)。數據描述符就是一個包含屬性的值,並說明這個值可讀或不可讀的對象;訪問器描述符就是包含該屬性的一對getter-setter方法的對象。一個完整的屬性描述(descriptor)必須是這二者之一,而且不能夠二者都有。測試

數據描述符和訪問器描述符各自都是對象,他們必須包含如下鍵值對:

configurable
僅當設置的屬性的描述符須要被修改或須要經過delete來刪除該屬性時,configurable屬性設置爲true。默認爲false。spa

enumerable
僅當設置的屬性須要被枚舉器(如for…in)訪問時設置爲true。默認爲false。prototype

數據描述符能夠包含如下可選鍵值對:

value
設置屬性的值,能夠是任何JavaScript值類型(number,object,function等類型)。默認爲undefined。

writable
僅當屬性的值能夠被賦值操做修改時設置爲true。默認爲false。

訪問器描述符能夠包含如下可選鍵值對:

get
屬性的getter方法,若屬性沒有getter方法則爲undefined。該方法的返回爲屬性的值。默認爲undefined。

set
屬性的setter方法,若屬性沒有setter方法則爲undefined。該方法接收惟一的參數,做爲屬性的新值。默認爲undefined。

使用示例

建立一個屬性

若是當前對象不存在咱們要設置的屬性,Object.defineProperty()會根據方法設置爲對象建立一個新的屬性。若是描述符參數缺失,則會被設置爲默認值。全部布爾型描述符屬性會被默認設置爲false。而value,get,set會被默認設置爲undefined。一個未設置get/set/value/writable的屬性被稱爲一個「原生屬性(generic)」,而且他的描述符(descriptor)會被「歸類」爲一個數據描述符(data descriptor)。

修改一個屬性

當某個屬性已經存在了,Object.defineProperty()會根據對象的屬性配置(configuration)和新設置的值來嘗試修改該屬性。若是該屬性的configurable被設置爲false,則該屬性沒法被修改(這種狀況下有個特殊狀況:若是以前的writable設置爲true,則咱們仍能夠將writable設置爲false,一旦這麼作以後,任何描述符屬性將變得不可設置)。若是屬性的configurable設置爲false,則咱們沒法將屬性的描述符在數據描述符和訪問器描述符之間轉換。
若是新設置的屬性和該屬性不一樣,而且該屬性的configurable被設置爲false,則一個類型錯誤(TypeError)會被拋出(除了上一段文字中說的特殊狀況)。若新舊屬性徹底相同,則什麼都不會發生。

可寫特性-writable

當一個屬性的writable被設置爲false,這個屬性就成爲「不可寫的(non-writable)」。該屬性不可被從新賦值。

可枚舉特性-enumerable

屬性的enumerable值定義對象的屬性是否會出如今枚舉器(for…in循環和Object.keys())中。

可配置特性-configurable

屬性的configurable值控制一個對象的屬性能否被delete刪除,同時也控制該屬性描述符的配置能否改變(除了前文所述在configurable爲false時,若writable爲true,則仍能夠進行一次修改將writable改變爲false)。

若是o.a屬性的configurable爲true,就不會有任何錯誤拋出,而且o.a在最後的delete操做中會被刪除。

添加屬性時的默認值

考慮描述符特性的默認值如何被應用是很是重要的。正以下面示例所示,簡單的使用"."符號來設置一個屬性和使用Object.defineProperty()是有很大區別的。

定製的Setters和Getters

下面的示例展現瞭如何實現一個「自存檔(self-archiving)」的對象。當temperature屬性被設置時,archive數組就會添加一個日誌記錄。

 

譯者注

Object.defineProperty()方法被許多現代前端框架(如Vue.js,React.js)用於數據雙向綁定的實現,當咱們在框架Model層設置data時,框架將會經過Object.defineProperty()方法來綁定全部數據,並在數據變化的同時修改虛擬節點,最終修改頁面的Dom結構。在這個過程當中有幾點須要注意:

延遲發生變化

現代框架爲了不密集的Dom修改操做,對綁定的數據修改後將會設置一個極小(一般爲1ms)的setTimeout延遲再應用變化。也就是說,虛擬節點和頁面Dom樹的變化和數據的變化中間會存在一個空閒期。注意到這一點的開發者就會意識到,若是咱們想實現一個功能:若是咱們當前對data進行了修改,指望model層當即發生變化並處於可操做的狀態,這是不可行的。固然,許多框架也爲咱們提供了許多應對方法,例如Vue的nextTick()方法等。

數組的變化

先讓咱們瞭解下Object.defineProperty()對數組變化的跟蹤狀況:

能夠看到,當a.b被設置爲數組後,只要不是從新賦值一個新的數組對象,任何對數組內部的修改都不會觸發setter方法的執行。這一點很是重要,由於基於Object.defineProperty()方法的現代前端框架實現的數據雙向綁定也一樣沒法識別這樣的數組變化。所以第一點,若是想要觸發數據雙向綁定,咱們不要使用arr[1]=newValue;這樣的語句來實現;第二點,框架也提供了許多方法來實現數組的雙向綁定。
對於框架如何實現數組變化的監測,大多數狀況下,框架會重寫Array.prototype.push方法,並生成一個新的數組賦值給數據,這樣數據雙向綁定就會觸發。做爲框架使用者,咱們須要知道的就是,這樣實現的數組修改會消耗更多的性能。

configurable和writable

原文中描述過一種特殊狀況:當configurable爲false時,咱們惟一仍能改變的屬性就是將設置爲true的writable設置爲false。對此譯者進行了如下測試(如下代碼在Chrome和IE下運行論證,輸出結果相同):

相關文章
相關標籤/搜索