八小時實現迷你版vuejs五:實現Watcher

再貼一下之前的一張圖:
這裏寫圖片描述

我們已經實現了 Observer 和 Directive,並且自己實現了一個 v-on 的指令,那麼再實現 DepWatcher 就完整了。

這裏的 dep.js 其實就是一個記錄依賴關係的,他有一個內部的數組 subs 會把所有依賴的 watcher 記錄在裏面,然後 observer 在觀察到數據改變的時候,就告訴dep,它會負責遍歷 subs 並調用他們的 update 也就是通知所有相關的 watcher

所以代碼這裏就不貼了,大家可以直接去看源碼。這也是唯一一個我沒有自己實現而是從 vuejs 中複製過來的類。

那麼現在我們自己實現一個 Watcher 類

首先定義我們的scope,Watcher 到底要做什麼?
還記得上一篇我們的 Directive 類麼,它會拿到一個 descriptor 作爲參數,而他依賴watcher來知道什麼時候需要執行update。那麼什麼時候需要執行 update 呢?顯然是指令的表達式中的值更新了就需要執行 update

舉個栗子:

Hello <span v-text=“name”></span>

這裏通過 v-text 指令綁定了 this.name ,那麼當name更新的時候顯然需要更新DOM,如何更新DOM我們這裏不關心,這是 v-text 指令中實現的,我們關心的只是調用他的 update 方法。

所以,我們的watcher需要知道這些:

  1. vm, 也就是vue實例,因爲我們需要通過 vm.name 取值
  2. expression, 也就是 name 這個字符串,這樣我們才知道要取的是 name ,也能知道要觀察他的變動。
  3. callback, 也就是 directive.update
export default function Watcher (vm, expOrFn, cb) {
  vm._watchers.push(this)
  this.vm = vm
  this.expOrFn = expOrFn
  this.expression = expOrFn
  this.cb = cb
  this.id = ++uid // uid for batching
  this.deps = []
  this.depIds = new Set()

  // TODO: support expression, like: "'Hello' + user.name"
  this.getter = () => {
    return vm[expOrFn]
  }
  this.setter = (vm, value) => {
    return vm[expOrFn] = value
  }

  this.value = this.get()
}

Watcher.prototype.update = function () {
  this.run()
}
Watcher.prototype.run = function () {
  const value = this.get()
  const oldValue = this.value

  if (value !== oldValue) {
    this.cb.call(this.vm, value, oldValue)
  }
}
Watcher.prototype.get = function () {
  Dep.target = this
  const value = this.getter.call(this.vm, this.vm)
  Dep.target = null
  return value
}
Watcher.prototype.set = function (value) {
  return this.setter.call(this.vm, this.vm, value)
}
Watcher.prototype.addDep = function (dep) {
  if (!this.depIds.has(dep.id)) {
    this.deps.push(dep)
    this.depIds.add(dep.id)
    dep.addSub(this)
  }
}

有幾點需要注意的:

1, getter 和 setter

爲了方便起見,我們做了一個非常非常非常簡單的 getter 和 setter,所以我們不支持 v-text=“‘hello’ + name” 這樣的表達式。對表達式的支持,在vuejs中做了非常詳盡的處理,其中如何處理 people.name 這樣路徑,就是勾三股四那篇文章的圖示講的內容,有興趣可以看一下,其實是一個自動狀態機 http://jiongks.name/blog/vue-code-review/

2, addDep 是幹嘛的?
這裏是強調了很多遍的地方,addDep 是把自己加到 deps 的依賴裏的。這涉及到vuejs解析依賴的機制:
在vuejs中,對一個表達式比如 name + age , 他的依賴並不是通過解析這個表達式的時候獲取的,而是在計算他們的值得時候記錄的。也就是在 計算這個表達式的過程中,有哪些 watcher 正在執行 get,就會把他們記錄了爲對當前observer的依賴。

observer中下面代碼就是幹這個的:

Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
      var value = getter ? getter.call(obj) : val
      if (Dep.target) { //.. 當前有一個watcher正在執行 get 方法,那麼他肯定依賴這個observe
        dep.depend()
      }
      return value
    },

所以 watcher 的get函數開始的時候就會執行:

  Dep.target = this

get結束的時候會設置爲 null。

請務必理解並牢記,這是一個非常巧妙的設計。

3,關於 batcher

這裏我們省略了batcher相關的內容,其實細心的讀者應該會發現 update 裏面直接調用了 run 那麼爲什麼不直接寫成一個函數呢,其實這是因爲我們省略了一個非常重要的性能優化:batcher。他會把一個 tick 的變動全部合併執行,而不是每次改動都執行一次DOM的更新。
vuejs中是這麼實現的:

`
Watcher.prototype.update = function (shallow) {
if (this.lazy) {
this.dirty = true
} else if (this.sync || !config.async) {
this.run()
} else {
//….
pushWatcher(this)
}
}

只有 sync 模式的時候會執行 run,也就是更新DOM,默認的 async 模式下只是把 watcher 加入一個隊列,在 nextTick 的時候會統一把隊列中的watcher都執行。
也就是默認模式下(async) ,vuejs會在nextTick的時候纔會更新DOM,所以我們有數據更新之後立刻獲取DOM的值很可能是舊的哦。

這一點也很重要,請務必牢記。

到這裏爲止,我們不僅可以創建 directive,而且當 directive 表達式的值改變的時候,還會執行他的 update 函數,所以我們就能實現更多的指令了。

下一篇我們實現兩個常用的指令: v-text 和 v-on

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章