Proxy實現vue MVVM實踐

vueconf(2018hangzhou)大會剛剛過去,vue做者尤大大向咱們展現了vue3.0的進展,並介紹vue3.0的一些改動,其中最令我期待的就是重寫數據監聽機制。javascript

回顧vue2.x的雙向數據綁定

談起vue的雙向數據綁定,咱們首先能想到的就是ES5中Object.defineProperty,利用重寫屬性的getset,咱們能夠完成數據劫持監聽,使用觀察者模式,在數據發生改變的時候,通知訂閱者更新狀態。
咱們就針對Observer觀察者部分寫了一個簡易的代碼以下:html

function Observer (data) {
  this.data = data
  this.walk(data)
}
Observer.prototype = {
  walk: function (data) {
    let me = this
    Object.keys(data).forEach(function (key) {
      me.convert(key, data[key])
    })
  },
  
  convert: function (key, val) {
    this.defineReactive(this.data, key, val)
  },

  defineReactive: function (data, key, val) {
    let dep = new Dep()
    let childObj = observe()

    Object.defineProperty(data, key, {
      enumerable: true,
      configurable: false,
      get: function () {
        if (Dep.target) {
            dep.depend()  // 添加訂閱者
        }
        return val
      },
      set: function (newVal) {
        if (newVal === val)  return

        val = newVal
        childObj = observe(newVal)
        dep.notify()  // 通知訂閱器
      }
    })
  }
}
function observe (value, vm) {
  if (!value || typeof value !== 'object') {
      return
  }

  return new Observer(value)
}

複製代碼

以上代碼中咱們定義了一個Observer構造函數,即觀察者。利用Object.defineProperty咱們將傳入對象的全部屬性(包含子屬性)所有進行數據監聽,並在get方法中,在訂閱器裏添加一條訂閱。一旦某屬性發生改變,通知到訂閱器。vue

Dep訂閱器,Compile指令,Watcher訂閱者的代碼就再也不分析,mvvm的整體結構能夠由下圖看出java

整個過程是一個觀察者、訂閱器、訂閱者、指令器四部分的事情,各司其職。

簡單介紹Proxy

Proxy是ES6中新增的構造函數,它能夠理解爲在目標對象以前架設一層「攔截」,外界對該對象的訪問,都必須經過這層攔截,所以提供了一種機制,能夠對外界的訪問進行過濾改寫。Proxy原意是代理,在這裏咱們能夠理解爲「代理」某些操做。ios

var obj = new Proxy({}, {
  get: function (target, key, receiver) {
    console.log(`proxy get ${key}`)
    return Reflect.get(target, key, receiver)
  },
  set: function (target, key, value, receiver) {
    console.log(`proxy set ${key}`)
    return Reflect.set(target, key, value, receiver)
  }
})
複製代碼

上面代碼對一個空對象架設了一層攔截,咱們能夠在Proxy的第二個參數中傳入一個handler對象,對象中能夠定義攔截行爲。
getset中,咱們都用到了ReflectReflect對象與Proxy對象同樣,也是ES6位了操做對象而提供的新API。Reflect對象的方法與Proxy對象的方法一一對應,好比Proxy方法攔截target對象的屬性賦值行爲。它採用Reflect.set方法將值賦值給對象的屬性,確保完成原有的行爲,而後再部署額外的功能。
根據以上代碼咱們寫一段測試代碼:git

obj.text = 'hello world!' 
// proxy set text
var _text = obj.text
// proxy get text
複製代碼

Proxy改寫觀察者

利用以上Proxy的一些特性,咱們修改代碼以下:es6

function observe (value, vm) {
  if (!value || typeof value !== 'object') {
    return
  }

  let dep = new Dep()
  return new Proxy(value, {
    get: function (target, key, receiver) {
      if (Dep.target) {
        dep.depend()
      }
      return Reflect.get(target, key, receiver)
    },
    set: function (target, key, value, receiver) {
      dep.notify()
      return Reflect.set(target, key, value, receiver)
    }
  })
}
複製代碼

咱們將傳入的對象直接替換爲Proxy對象,入參handlergetset中的添加訂閱者和通知訂閱器邏輯保持不變。
整個過程沒有作其餘多餘的判斷,因爲Vue3.0尚未發佈,沒有實際源碼能夠借鑑,因此以上只是我的實現的簡單版本(完整代碼)。將整個mvvm運用到html中,如下是運行後的效果(沒作gif,湊合看吧):github

重寫數據監聽機制的好處

  1. 放棄了Object.defineProperty,基於 Proxy 觀察者機制以知足全語言覆蓋及更好的性能。加上其它方法的優化改動,vue3.0能夠提速一倍/內存使用下降一半;
  2. Observer模塊將能夠單獨做爲一個庫來使用。

可能產生的問題

很遺憾的是,ES6的Proxy沒法被轉譯爲ES5,因此它將不被IE所支持。對於這個問題,Vue3.0將給出IE11的兼容方案,即在IE11下,仍是使用的Object.defineProperty機制。mvvm

參考資料

ECMAScript 6 入門(阮一峯):es6.ruanyifeng.com/#docs/proxy
vue 3.0 更新計劃:更快,更小,讓開發者更輕鬆: www.oschina.net/news/101906…
爲何Proxy能夠優化vue的數據監聽機制: juejin.im/post/5bfe33…函數

相關文章
相關標籤/搜索