文章出處

概述

由于剛開始學習 vue 源碼,而且水平有限,有理解或表述的不對的地方,還請不吝指教。

vue 主要通過 Watcher、Dep 和 Observer 三個類來實現響應式視圖。另外還有一個 scheduler 來進行調度,本次暫時不做討論。

Watcher 和 Dep 是訂閱者和發布者的關系,每個 Watcher 可以訂閱多個 Dep,而每個 Dep 也可以被多個 Watcher 訂閱。當 Observer 監聽的數據發生改變時,相應的 Dep 就會觸發其訂閱者 Watcher 更新視圖。

下面通過一個簡單的例子來說明其實現流程和原理:

  <div id="app">
      {{someVar}}
  </div>

  <script type="text/javascript">
      new Vue({
          el: '#app',

          data: {
              someVar: 'init'
          },

          mounted(){
              setTimeout(() => this.someVar = 'changed', 3000)
          }

      })
  </script>

頁面初始會顯示 "init" 字符串,3秒鐘之后,會更新為 "changed" 字符串。

為了便于理解,將整個流程分為三個階段:

  1. 初始化data,這個階段 vue 通過 Observer 監聽 data 對象,并將普通的 someVar 屬性代理為 get\set 屬性
  2. 初次掛載el,這個階段 vue 使用默認的 someVar 數據渲染視圖,并將 watcher 添加到 dep 的訂閱者列表
  3. someVar 更改觸發視圖更新,這個階段 someVar 被賦予了新值,vue 根據 watcher 和 dep 的訂閱關系觸發視圖的更新

下面我們來逐步分析這三個階段的流程。

第一階段

主要流程

new Vue(options) => vm._init(options) => initState(vm) => initData(vm) => observe(data) => new Observer(data) => defineReactive(data, key, value)

說明

初始化操作會監聽 data 對象,對其每一個屬性調用 defineReactive() 方法,將其改造為響應式屬性,代碼如下(去掉了不影響表述主流程的代碼,以便能更清晰的抓住重點):

/**
 * Define a reactive property on an Object.
 */
export function defineReactive (
  obj: Object,
  key: string,
  val: any
) {
  const dep = new Dep()

  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
    
      dep.depend()
      return val
    },
    set: function reactiveSetter (newVal) {
      if (newVal === val || (newVal !== newVal && val !== val)) {
        return
      }
      
      val = newVal
      dep.notify()
    }
  })
}

可以看到,當 get() 方法執行的時候,會調用 dep.depend() ;當 set() 方法執行時,會調用 dep.notify()。后面我們會看到這兩個方法的作用。

第二階段

主要流程

vm.$mount(el) => mountComponent(vm, el) => new Watcher(vm, updateComponent) => watcher.get() => updateComponent() => vm._update(vm._render())

說明

vm._render() 調用 vm.$options.render() 方法生成 vnode,vm._update() 方法根據 vnode 對視圖做更新。

vm.$options.render() 方法是在 $mount() 方法中生成的,生成后的代碼如下:

(function() {
    with (this) {
        return _c('div', {
            attrs: {
                "id": "app"
            }
        }, [_v("\n            " + _s(someVar) + "\n        ")])
    }
})

可以看到,代碼中會使用到 vm.someVar 屬性,而該屬性最終會代理到之前定義的響應式屬性上,從而調用其 get() 方法,進而調用 dep.depend() 方法將 watcher 添加到訂閱者列表。

dep.depend() => Dep.target.addDep(dep) => dep.addSub(watcher) => dep.subs.push(watcher)

dep.subs 就是 dep 的訂閱者列表,通過這個流程,就建立起了 dep 和 watcher 之間的訂閱關系。

其中,Dep.target 就是當前的 watcher,因為在 watcher.get() 方法執行時,有如下流程:

watcher.get() => pushTarget(watcher) => Dep.target = watcher

第三階段

3秒之后,vm.someVar 被賦予了新的值,從而最終會調用到響應式屬性的 set() 方法,進而調用 dep.notify(),觸發 watcher 更新視圖。

主要流程

dep.notify() => watcher.update() => watcher.run() => watcher.get() => watcher.getter.call(vm, vm) == updateComponent() => vm._update(vm._render())

說明

watcher 的 getter() 方法就是第二階段 new Watcher(vm, updateComponent) 中的 updateComponent 方法,可以看到,通過這個流程,視圖得到了更新。

以上就構成了一個響應式視圖模型,其核心是利用 defineProperty() 方法將普通屬性轉換為帶有鉤子的 set\get 屬性,從而實現了數據監聽。


文章列表


不含病毒。www.avast.com
全站熱搜
創作者介紹
創作者 大師兄 的頭像
大師兄

IT工程師數位筆記本

大師兄 發表在 痞客邦 留言(0) 人氣()