【vue】用圖告訴你響應式原理

前言

若是本身去實現數據驅動的模式,如何解決一下幾個問題:html

  • 經過什麼手段去知道個人數據變了?
  • 經過什麼東西去同步更新視圖?

數據劫持——obvserver

咱們須要知道數據的獲取和改變,數據劫持是最基礎的手段。在Obeserver中,咱們能夠看到代碼以下:vue

Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
      // ...
    },
    set: function reactiveSetter (newVal) {
      // ...
    }
  })
複製代碼

經過Object.defineProperty這個方法,咱們能夠在數據發生改變或者獲取的時候,插入一些自定義操做。同理,vue也是在這個方法中作依賴收集和派發更新的。react

綁定和更新視圖——watcher

從初始化開始,咱們渲染視圖的時候,便會生成一個watcher,他是監視視圖中參數變化以及更新視圖的。代碼以下:設計模式

// 在mount的生命鉤子中
new Watcher(vm, updateComponent, noop, {
    before () {
      if (vm._isMounted && !vm._isDestroyed) {
        callHook(vm, 'beforeUpdate')
      }
    }
}, true /* isRenderWatcher */)
複製代碼

固然,咱們能夠保留疑問:數組

  • watcher是怎麼去更新視圖的
  • 數據又是怎麼和watcher聯動起來的

具體的綁定和更新的流程,咱們到後續的依賴收集中講解。app

咱們先來說講響應式系統中涉及到的設計模式。異步

發佈訂閱模式

在發佈訂閱模式中,發佈者和訂閱者之間多了一個發佈通道;一方面從發佈者接收事件,另外一方面向訂閱者發佈事件;訂閱者須要從事件通道訂閱事件oop

以此避免發佈者和訂閱者之間產生依賴關係 post

vue的響應式流程

vue的響應式系統借鑑了數據劫持和發佈訂閱模式。性能

Vue用Dep做爲一箇中間者,解藕了Observer和Watcher之間的關係,使得二者的職能更加明確。

那具體是如何來完成依賴收集和訂閱更新的呢?

依賴收集過程

  • 依賴收集的流程

舉個例子

<div id="app">
    {{ message }}
    {{ message1 }}
    <input type="text" v-model="message">
    <div @click="changeMessage">改變message</div>        
</div>
複製代碼
var app = new Vue({
    el: '#app',
    data: {
        message: '1',
        message1: '2',
    },
    methods: {
        changeMessage() {
            this.message = '2'
        }
    },
    watch: {
        message: function(val) {
            this.message1 = val
        }
    }
})
複製代碼

依賴收集流程圖:

如何看懂這個依賴收集流程?關鍵在watcher代碼中:

get () {
    pushTarget(this)
    let value
    const vm = this.vm
    try {
      value = this.getter.call(vm, vm)
    } catch (e) {
      // 省略
    } finally {
      if (this.deep) {
        traverse(value)
      }
      popTarget()
      this.cleanupDeps()
    }
    return value
  }
複製代碼

調用的這個this.getter有兩種,一種是key值的getter方法,還有一種是expOrFn,好比mounted中傳入的updateComponent。

  • 如何防止重複收集

咱們不妨想一想什麼纔算是重複收集了?

筆者想到一種狀況:就是dep數組中,出現了多個同樣的watcher。

好比renderWatch就容易被重複收集,由於咱們在html模版中,會重複使用data中的某個變量。那他是如何去重的呢?

一、只有watch在執行get時,觸發的取數操做,纔會被收集

Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
      const value = getter ? getter.call(obj) : val
      if (Dep.target) {
        dep.depend()
        // ...
      }
      return value
    },
    set: function reactiveSetter (newVal) {
      // ...
      dep.notify()
    }
  })
複製代碼

當只有Dep.target這個存在的時候才進行依賴收集。Dep.target這個值只有在watcher執行get方法的時候纔會存在。

二、在dep.depend的時候會判斷watch的id

depend () {
    if (Dep.target) {
      Dep.target.addDep(this)
    }
}
複製代碼
addDep (dep: Dep) {
    const id = dep.id
    if (!this.newDepIds.has(id)) {
      this.newDepIds.add(id)
      this.newDeps.push(dep)
      if (!this.depIds.has(id)) {
        dep.addSub(this)
      }
    }
 }
複製代碼

咱們會發現,在depend過程當中,會有一個newDepIds去記錄已經存入的dep的id,當一個watcher已經被該dep存過期,便再也不會進行依賴收集操做。

派發更新過程

收集流程講完了,不妨在聽聽更新流程。

  • 訂閱更新的流程 老例子
<div id="app">
    {{ message }}
    {{ message1 }}
    <input type="text" v-model="message">
    <div @click="changeMessage">改變message</div>        
</div>
複製代碼
var app = new Vue({
    el: '#app',
    data: {
        message: '1',
        message1: '2',
    },
    methods: {
        changeMessage() {
            this.message = '3'
        }
    },
    watch: {
        message: function(val) {
            this.message1 = val
        }
    }
})
複製代碼

依賴收集的最終結果:

當觸發click事件的時候,便會觸發訂閱更新流程。

訂閱更新流程圖:

當renderWatch執行更新的時候,回去調用beforeUpdate生命鉤子,而後執行patch方法,進行視圖的變動。

  • 如何防止重複更新

如何去防止重複更新呢?renderWatch會被不少dep進行收集,若是視圖屢次渲染,會形成性能問題。

其實問題的關在在於——queueWatcher

在queueWatcher中有兩個操做:去重和異步更新。

function queueWatcher (watcher) {
  const id = watcher.id
  if (has[id] == null) {
    has[id] = true
    queue.push(watcher)
    // ...
    if (!waiting) {
      waiting = true
      // ...
      nextTick(flushSchedulerQueue)
    }
  }
}
複製代碼

其實queueWatcher很簡單,將全部watch收集到一個數組當中,而後去重。

這樣至少能夠避免renderWatch頻繁更新。

好比上述例子中的,message和message1都有一個renderWatch,可是隻會執行一次。

異步更新也能夠保證當一個事件結束以後,纔會觸發視圖層的更新,也能防止renderWatch重複更新

結尾

文章講述了響應式流程的緣由,代碼細節並未深刻,若是喜歡瞭解源碼的,能夠翻看筆者其餘的文章:

Observer源碼解析

Watcher源碼解析(未完成)

相關文章
相關標籤/搜索