VUE實現雙向數據綁定的原理就是利用了 Object.defineProperty() 這個方法重新定義了對象獲取屬性值(get)和設置屬性值(set)的操作來實現的。那么Object.defineProperty究竟是該如何使用的呢?先看個例子
?
<!DOCTYPE html> <html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>Document</title> </head><body><div id="app"><h5 id="txtShow"></h5><input type="text" id="txt"></div><script>// mvvm 分為model(模型) view(視圖) viewModel(視圖模型)// model 用來存儲數據// view 用來展示數據// ViewModel 關聯數據,和model實現雙向綁定。// 通過Object.defineProperty 定義一個屬性// 通過此方法為對象設置屬性的時候 對象的屬性值會包含一個get和set方法// 當修改對象值得時候回觸發相應的函數調用// 參數一 需要定義屬性的對象// 參數二 屬性名字// 參數三 對象屬性的修飾內容 // 在set方法中做一些處理,執行頁面的刷新和回調var model = {}; //創建一個新對象var vm = '';Object.defineProperty(model, 'txt', {set: function (val) {console.log('設置屬性值');console.log(val);vm = val;txtShow.innerHTML = this.txt;},get: function () {console.log('獲取屬性值');console.log(this);return vm;},enumerable: true, //可枚舉,默認falseconfigurable: true //該屬性描述符能夠被改變,同時該屬性也能從對應的對象上被刪除。默認false })var txtShow = document.getElementById('txtShow'),txt = document.getElementById('txt');txt.onkeyup = function () {if (event.keyCode == 13) {model.txt = txt.value;}}</script> </body></html>
?
通過上面的代碼我們可以看到,在控制臺,無論是改變vm的值還是model.txt的值,所對應的的,對方的值也會相應的改變,由此,這里我們就實現了雙向綁定。
對于Object.defineProperty()
?,大家應該都見過,只是認知的程度有所不同。
根據MDN web docs 中解釋說:?Object.defineProperty()
?方法會直接在一個對象上定義一個新屬性,或者修改一個對象的現有屬性, 并返回這個對象。
語法
Object.defineProperty(obj, prop, descriptor)
參數
- 要在其上定義屬性的對象。
- 要定義或修改的屬性的名稱。
- 將被定義或修改的屬性描述符。
obj
prop
descriptor
返回值
? ? 被傳遞給函數的對象。
該方法允許精確添加或修改對象的屬性。通過賦值操作添加的普通屬性是可枚舉的,能夠在屬性枚舉期間呈現出來(for...in
?或?Object.keys
?方法),?這些屬性的值可以被改變,也可以被刪除。這個方法允許修改默認的額外選項(或配置)。默認情況下,使用?Object.defineProperty()
?添加的屬性值是不可修改的。
對象里目前存在的屬性描述符有兩種主要形式:數據描述符和存取描述符。數據描述符是一個具有值的屬性,該值可能是可寫的,也可能不是可寫的。存取描述符是由getter-setter函數對描述的屬性。描述符必須是這兩種形式之一;不能同時是兩者。
數據描述符和存取描述符均具有以下可選鍵值:
- 當且僅當該屬性的 configurable 為 true 時,該屬性
描述符
才能夠被改變,同時該屬性也能從對應的對象上被刪除。默認為 false。configurable
特性表示對象的屬性是否可以被刪除,以及除value
和writable
特性外的其他特性是否可以被修改。 - 當且僅當該屬性的
enumerable
為true
時,該屬性才能夠出現在對象的枚舉屬性中。默認為 false。enumerable
定義了對象的屬性是否可以在?for...in
?循環和?Object.keys()
?中被枚舉。 -
數據描述符同時具有以下可選鍵值:
- 該屬性對應的值。可以是任何有效的 JavaScript 值(數值,對象,函數等)。默認為?
undefined
。 - 當且僅當該屬性的
writable
為true
時,value
才能被賦值運算符改變。默認為?false。
value
writable
存取描述符同時具有以下可選鍵值:
- 一個給屬性提供 getter 的方法,如果沒有 getter 則為?
undefined
。當訪問該屬性時,該方法會被執行,方法執行時沒有參數傳入,但是會傳入this
對象(由于繼承關系,這里的this
并不一定是定義該屬性的對象)。 - 默認為?
undefined
。 - 一個給屬性提供 setter 的方法,如果沒有 setter 則為?
undefined
。當屬性值修改時,觸發執行該方法。該方法將接受唯一參數,即該屬性新的參數值。 - 默認為?
undefined
。
get
set
- 該屬性對應的值。可以是任何有效的 JavaScript 值(數值,對象,函數等)。默認為?
configurable
enumerable
? | ?configurable | ?enumerable | ?value | writable? | get? | ?set |
數據描述符 | ?YES | ?YES | ?YES | ?YES | ?NO | ?NO |
存取描述符 | ?YES | ?YES | ?NO | ?NO | ?YES | ?YES |
如果一個描述符不具有value,writable,get 和 set?任意一個關鍵字,那么它將被認為是一個數據描述符。如果一個描述符同時有(value或writable)和(get或set)關鍵字,將會產生一個異常。
記住,這些選項不一定是自身屬性,如果是繼承來的也要考慮。為了確認保留這些默認值,你可能要在這之前凍結?Object.prototype
,明確指定所有的選項,或者通過?Object.create(null)
將__proto__
屬性指向null
。
在日常運用中,那你需要明白:
var o = {};o.a = 1; // 等同于 : Object.defineProperty(o, "a", {value : 1,writable : true,configurable : true,enumerable : true });// 另一方面, Object.defineProperty(o, "a", { value : 1 }); // 等同于 : Object.defineProperty(o, "a", {value : 1,writable : false,configurable : false,enumerable : false });
下面的例子展示了如何實現一個自存檔對象。 當設置temperature
?屬性時,archive
?數組會獲取日志條目。
function Archiver() {var temperature = null;var archive = [];Object.defineProperty(this, 'temperature', {get: function() {console.log('get!');return temperature;},set: function(value) {temperature = value;archive.push({ val: temperature });}});this.getArchive = function() { return archive; }; }var arc = new Archiver(); arc.temperature; // 'get!' arc.temperature = 11; arc.temperature = 13; arc.getArchive(); // [{ val: 11 }, { val: 13 }]
看到這個例子,你心中是不是有了些許想法?
后面還有很多知識點,有需要的可以去MDN進行更加深入的了解,謝謝!