首頁 >web前端 >Vue.js >Vue的響應式原理是什麼?

Vue的響應式原理是什麼?

醉折花枝作酒筹
醉折花枝作酒筹轉載
2021-07-29 17:42:193060瀏覽

最近看了不少Vue原理的文章,在這些文章的幫助下,我也多次嘗試自己理解Vue的原始碼。終於,我覺得是時候自己輸出內容了,希望可以從不同於其他文章的角度帶大家來熟悉Vue。

Vue的響應式原理是什麼?

Dep

var Dep = function Dep() {
  this.id = uid++
  this.subs = []
}

Dep 的意思,自然就是 dependency(也就是依賴,一個電腦領域的名詞)。

就像是寫 node.js 程序,常常會使用 npm 倉庫的依賴。在 Vue 中,依賴具體指的是響應式處理後的資料。後面會提到,響應式處理的關鍵函數之一是在許多 Vue 原理文章都會提到的 defineReactive。

Dep 與每個響應式資料綁定後,該響應式資料就會變成依賴(名詞),以下介紹Watcher 時會提到,響應式資料可能會被watch、computed、在範本中使用3 種情況依賴(動詞)。

subs

Dep 物件下有一個 subs 屬性,是一個數組,很容易猜出,就是 subscriber(訂閱者)列表的意思咯。訂閱者可能是 watch 函數、computed 函數、視圖更新函數。

Watcher

Watcher 是 Dep 裡提到的訂閱者(不要和後面的 Observer 觀察者搞混)。

因為 Watcher 的功能在於及時回應 Dep 的更新,就像一些 App 的訂閱推送,你(Watcher)訂閱了某些資訊(Dep),資訊更新時會提醒你閱讀。

deps

與 Dep 擁有 subs 屬性類似,Watcher 物件也有 deps 屬性。這樣構成了 Watcher 和 Dep 就是一個多對多的關係,互相記錄的原因是當一方被清除的時候可以及時更新相關物件。

Watcher 如何產生

上面多次提到的watch、computed、渲染模板產生Watcher,在Vue 原始碼裡都有簡潔易懂的體現:

  • mountComponent 的vm._watcher = new Watcher(vm, updateComponent, noop);
  • initComputed 的watchers[key] = new Watcher(vm, getter || noop, noop, computedWatcherOptions)
  • $watcher 的var watcher = new Watcher(vm, expOrFn, cb, options);

Observer

Observer 是觀察者,他負責遞歸地觀察(或處理)響應式物件(或陣列)。在列印出來的實例裡,可以注意到響應式的物件都會帶著一個 __ob__,這是已經被觀察的證明。觀察者沒有上面的 Dep 和 Watcher 重要,稍微了解一下就可以了。

walk

Observer.prototype.walk 是Observer 初始化時遞歸處理的核心方法,不過此方法用於處理對象,另外還有Observer.prototype.observeArray處理數組。

核心流程

依照上面幾個概念的關係,如何搭配,該如何實現資料響應式更新?

首先定下我們的目標:自然是在資料更新時,自動刷新視圖,顯示最新的資料。

這就是上面提到的 Dep 和 Watcher 的關係,資料是 Dep,而 Watcher 觸發的是頁面渲染函數(這是最重要的 watcher)。

但新問題隨之而來,Dep 怎麼知道有什麼 Watcher 依賴他?

Vue 採用了一個很有趣的方法:

  • 在運行Watcher 的回呼函數前,先記下目前Watcher 是什麼(透過Dep.target)

  • 運行回呼函數中用到響應式數據,那麼必然會呼叫響應式數據的getter 函數

  • 在響應式數據的getter 函數中就能記下目前的Watcher,建立Dep 和Watcher 的關係

  • 之後,在響應式資料更新時,必然會呼叫響應式資料的setter 函數

  • #基於先前建立的關係,在setter 函數中就能觸發對應Watcher 的回呼函數了

程式碼

上述邏輯就在defineReactive 函數中。這個函數入口不少,這裡先講比較重要的 observe 函數。

在 observe 函數中會 new Observer 對象,其中使用 Observer.prototype.walk 對對像中的值進行逐個響應式處理,使用的就是 defineReactive 函數。

因為 defineReactive 函數太重要了,而且也不長,所以直接貼到這邊講比較方便。

function defineReactive(obj, key, val, customSetter, shallow) {
  var dep = new Dep()
  depsArray.push({ dep, obj, key })
  var property = Object.getOwnPropertyDescriptor(obj, key)
  if (property && property.configurable === false) {
    return
  }

  // cater for pre-defined getter/setters
  var getter = property && property.get
  var setter = property && property.set

  var childOb = !shallow && observe(val)
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter() {
      var value = getter ? getter.call(obj) : val
      if (Dep.target) {
        dep.depend()
        if (childOb) {
          childOb.dep.depend()
          if (Array.isArray(value)) {
            dependArray(value)
          }
        }
      }
      return value
    },
    set: function reactiveSetter(newVal) {
      var value = getter ? getter.call(obj) : val
      // 后半部分诡异的条件是用于判断新旧值都是 NaN 的情况
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return
      }
      // customSetter 用于提醒你设置的值可能存在问题
      if ('development' !== 'production' && customSetter) {
        customSetter()
      }
      if (setter) {
        setter.call(obj, newVal)
      } else {
        val = newVal
      }
      childOb = !shallow && observe(newVal)
      dep.notify()
    },
  })
}

首先每個響應式的值都是一個「依賴",所以第一步我們先借閉包的能力給每個值造一個 Dep。 (到Vue 3 就不需要閉包啦)

接著看核心的三個參數:

  • obj 目前需要響應式處理的值所在的物件

  • key 值的key

  • val 目前的值

##這個值可能之前也定義了自己的getter、setter,所以在做Vue 的響應式處理時先處理原本的getter、setter。

上面在核心流程中提到在 getter 函數會建立 Dep 和 Watcher 的關係,具體來說依賴的是 dep.depend()。

下面貼一下 Dep 和 Watcher 互相呼叫的幾個方法:

Dep.prototype.depend = function depend() {
  if (Dep.target) {
    Dep.target.addDep(this)
  }
}
Watcher.prototype.addDep = function addDep(dep) {
  var id = dep.id
  if (!this.newDepIds.has(id)) {
    this.newDepIds.add(id)
    this.newDeps.push(dep)
    if (!this.depIds.has(id)) {
      dep.addSub(this)
    }
  }
}
Dep.prototype.addSub = function addSub(sub) {
  this.subs.push(sub)
}

通过这几个函数,可以领略到了 Dep 和 Watcher 错综复杂的关系……不过看起来迂回,简单来说,其实做的就是上面说的互相添加到多对多列表。

你可以在 Dep 的 subs 找到所有订阅同一个 Dep 的 Watcher,也可以在 Watcher 的 deps 找到所有该 Watcher 订阅的所有 Dep。

但是里面还有一个隐藏问题,就是 Dep.target 怎么来呢?先放一放,后会作出解答。先接着看看 setter 函数,其中的关键是 dep.notify()。

Dep.prototype.notify = function notify() {
  // stabilize the subscriber list first
  var subs = this.subs.slice()
  for (var i = 0, l = subs.length; i < l; i++) {
    subs[i].update()
  }
}

不难理解,就是 Dep 提醒他的订阅者列表(subs)里的所有人更新,所谓订阅者都是 Watcher,subs[i].update() 调用的也就是 Watcher.prototype.update。

那么来看一下 Watcher 的 update 做了什么——

Watcher.prototype.update = function update() {
  if (this.lazy) {
    this.dirty = true
  } else if (this.sync) {
    this.run()
  } else {
    queueWatcher(this)
  }
}

在这里我觉得有两个点比较值得展开,所以挖点坑

以上是Vue的響應式原理是什麼?的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:segmentfault.com。如有侵權,請聯絡admin@php.cn刪除