1. 程式人生 > >理解前端數據雙向綁定原理——Object.defineProperty()

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

核心 操作 分享圖片 href 避免 time 定制 mode rda

Object.defineProperty與vue之間的關系

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

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

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

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。

enumerable

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

數據描述符可以包含以下可選鍵值對:

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下運行論證,輸出結果相同):

技術分享圖片

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