用proxy實現一個更優雅的vue

前言

若是你有讀過Vue的源碼,或者有了解過Vue的響應原理,那麼你必定知道Object.defineProperty(), 那麼你也應該知道,Vue 2.x裏,是經過 遞歸 + 遍歷 data對象來實現對數據的監控的, 你可能還會知道,咱們使用的時候,直接經過數組的下標給數組設置值,不能實時響應,是由於Object.defineProperty() 沒法監控到數組下標的變化,而咱們日常所用的數組方法 push, pop, shift, unshift, splice, sort, reverse, 其實不是真正的數組方法,而是被修改過的,這些都是由於 Object.defineProperty() 提供的能力有限,沒法作到完美。javascript

網上看過不少關於Vue的源碼解讀或者實現一個簡易版的Vue的教程,還都是用 Object.defineProperty (大概是爲兼容性考慮吧), 而 Object.defineProperty() 確實存在諸多限制, 聽說Vue的3.x版本會改用Proxy,那麼今天咱們就先來嚐嚐鮮,用Proxy實現一個簡單版的Vuehtml

proxy 介紹

Proxy 用於修改某些操做的默認行爲,等同於在語言層面作出修改,因此屬於一種「元編程」(meta programming),即對編程語言進行編程。前端

Proxy 能夠理解成,在目標對象以前架設一層「攔截」,外界對該對象的訪問,都必須先經過這層攔截,所以提供了一種機制,能夠對外界的訪問進行過濾和改寫。Proxy 這個詞的原意是代理,用在這裏表示由它來「代理」某些操做,能夠譯爲「代理器」。vue

以上引用內容來自阮一峯的es6教程的Proxy章節 原文地址請戳這裏java

咱們來看看如何用Proxy去定義一個監聽數據的函數node

定義 observe

_observe (data){
    var that = this
    
    // 把代理器返回的對象存到 this.$data 裏面
    this.$data = new Proxy(data, {
    set(target,key,value){
      // 利用 Reflect 還原默認的賦值操做
      let res =  Reflect.set(target,key,value)
      // 這行就是監控代碼了
      that.handles[key].map(item => {item.update()})
      return res
    }
    })
}
複製代碼

當觸發set的時候,就會執行 that.handles[key].map(item => {item.update()}) ,這句代碼的做用就是執行 該屬性名下的全部 "監視器"git

那麼,監視器怎麼來的呢? 請繼續看如下代碼es6

定義 compile

_compile (root){
       const nodes = Array.prototype.slice.call(root.children)
       let data = this.$data
       nodes.map(node => {
         // 若是不是末尾節點,就遞歸
         if(node.children.length > 0) this._complie(node)
         // 處理 v-bind 指令
         if(node.hasAttribute('v-bind')) {
           let key = node.getAttribute('v-bind')
           this._pushWatcher(new Watcher(node,'innerHTML',data,key))
         }
         // 處理 v-model 指令
         if(node.hasAttribute('v-model')) {
           let key = node.getAttribute('v-model')
           this._pushWatcher(new Watcher(node,'value',data,key))
           node.addEventListener('input',() => {data[key] = node.value})
         }
         // 處理 v-click 指令
         if(node.hasAttribute('v-click')) {
           let methodName = node.getAttribute('v-click')
           let mothod = this.$methods[methodName].bind(data)
           node.addEventListener('click',mothod)
         }
       })
     }
複製代碼

上面這段代碼,看起來很長,但是實際上,只作了意見很簡單的事情, 就是 "編譯" html 模板,把有 v-bindv-modelv-click 都給加上對應的 通知監控github

  1. 通知 就是 this._pushWatcher(...) , 至關因而安裝一個監聽器,這樣只要 this.$data 有發生 set 操做的話,就會執行 this._pushWatcher 括號裏面傳的函數,來通知節點更新數據編程

  2. 監控 就是 node.addEventListener(...) 監聽相應的事件,而後執行對應的 watcher 或者 methods

this._pushWatcher 又作了什麼呢?

_pushWatcher (watcher) {
      if (!this._binding[watcher.key]) this._binding[watcher.key] = []
      this._binding[watcher.key].push(watcher)
    }
複製代碼

這個就更簡單了,若是 this._binding[watcher.key] 爲空,就初始化,而後向裏面添加一個 監聽器

最後,咱們再來看看,監聽器是怎麼實現的

定義 Watcher

class Watcher {
     constructor (node,attr,data,key) {
       this.node = node
       this.attr = attr
       this.data = data
       this.key = key
     }
     update () {
       this.node[this.attr] = this.data[this.key]
     }
   }
複製代碼

Watcher 是一個類,只有一個方法,就是更新數據,怎麼知道要更新哪一個節點,更新爲何數據呢? 在實例化(new)的時候,傳的參數就是定義這些的

這樣,咱們就實現初步的雙向綁定了,整個代碼大概只有50行。其實還能夠更少, 可是更少的話,就是繼續閹割功能了(雖然目前實現的也是嚴重閹割版的), 可是我以爲實現這些,恰好能夠很少很多幫我咱們理解vue的本質。

最後

本文最終實現代碼已經放在 github上,想要直接看效果的同窗,能夠上去直接copy,運行。

若是以爲本文對您有用,請給本文的github加個star,萬分感謝

另外,github上還有其餘一些關於前端的教程和組件,有興趣的童鞋能夠看看,大家的支持就是我最大的動力。

相關文章
相關標籤/搜索