重新理解 Vue 的雙向綁定

PY
5 min readSep 20, 2019

--

經過昨天佛心公司洗禮後,發現自己對雙向綁定與 Virtual Dom 回答的還是五五六六的,因為只回答了 Vue 的雙向綁定原理是由 Object.defineProperty 去做資料劫持,被反問說既然你知道用 Object.defineProperty ,那這個有什麼缺點或限制嗎 ? 當下發現原來自己根本還只是在 Vue 皮毛階段,往下問就掛了,決定來惡補一下。

年初寫的文章結果還是忘了一半QQ

雙向綁定

首先 Vue 雙向綁定在 3.0 之前都是使用 Object.defineProperty

Object.defineProperty

Object.defineProperty 可直接修改、或定義現有屬性,提供三個參數

  1. 要定義或修改的 object
  2. 要定義或修改的屬性 key
  3. 目標屬性要擁有的特性
const object1 = {};Object.defineProperty(object1, 'property1', {
value: 42,
});
object1.property1 = 77;
// throws an error in strict mode
console.log(object1.property1);
// expected output: 42

第三點分為以下這幾種特性

  1. value (值)
  2. writable (可不可寫入)
  3. configurable (能不能被設定,預設false,如果第一次 define沒設定 true 的話則第二次將報錯)
  4. enumerable (能不能被 for…in 列舉)
  5. get (取值觸發此 function)
  6. set (賦值觸發此 function)

注意其中 get, set 無法與 value, writable 共用不然會報錯

let obj = {}Object.defineProperty(obj, 'a', {
value: '還我急急三比零',
get: function() { return 0xdeadbeef; }
});
// Error: Invalid property descriptor. Cannot both specify accessors and a value or writable attribute, #<Object>

所以當我們需要綁定 view 與 資料我們就能用 get, set 去做響應

var userInfo = {};
Object.defineProperty(userInfo, "nickName", {
get: function(){
return document.getElementById('nickName').innerHTML;
},
set: function(nick){
document.getElementById('nickName').innerHTML = nick;
}
});

當然這只是 Vue 響應式其中一小部分,更複雜的 get, set 後面還有 watcher 到directive 到改變 dom 結構,詳細在官網上就不偏題了

回到問題,那為何 Object.defineProperty 有限制呢?

  1. 在 Vue 中,無法監聽陣列變化

這個問題可以參考下面這篇回答,其實 Object.defineProperty 這方法是可以對陣列操作的,但是效能問題導致 Vue 沒有把這功能加入

2. 只能劫持物件屬性,如果物件有更深一層物件那就要在深度遍歷一次了,這造成很大的效能問題

因此在 Vue3.0 中,用 ES6 的 Proxy 取代了Object.defineProperty,可以看看以下投影片尤大介紹 Vue3.0

Proxy

Proxy 意思是代理,能夠用此對物件進行攔截

var proxy = new Proxy(target, handler);
  1. target 為要 proxy 的物件
  2. handler function 可以在此定義 get, set
var obj = new Proxy({}, {
get: function (target, key, receiver) {
console.log(`getting ${key}!`);
return Reflect.get(target, key, receiver);
},
set: function (target, key, value, receiver) {
console.log(`setting ${key}!`);
return Reflect.set(target, key, value, receiver);
}
});

Reflect 屬性

所以 Vue 用此來代替 Object.defineProperty 方法來對物件 get, set,他能夠對整個物件進行劫持並且返回一個新的對象

Proxy 進行雙向綁定

總結

  1. 在 Vue3.0 前使用Object.defineProperty 做雙向綁定資料劫持
  2. Vue3.0後使用 Proxy 代理(攔截) 物件的 get, set

--

--