Welcome to Aaron Blog! 🎉

Watch & Computed

探索 Watch 和 Computed 有何區別,並探索源碼

Loading comments...

Watch 和 Computed 誰比較快執行?

最近朋友在面試時被問到這個問題,超有趣的!我想來好好研究一下 Vue 源碼中 Watch 和 Computed 的差異,看看到底誰比較快!

先搞懂 Vue 的響應式系統

在深入比較之前,我們得先知道 Vue 的響應式是怎麼回事。簡單說,Vue3 用 Proxy 來監聽數據變化,當數據更新時頁面也會跟著更新。

Proxy 基本上就是幫 Vue 攔截對象的讀取(get)和修改(set)操作。來看看官網的簡易實現:

function reactive(obj) {
  return new Proxy(obj, {
    get(target, key) {
      track(target, key) // 重點:追蹤誰在使用這個屬性
      return target[key]
    },
    set(target, key, value) {
      target[key] = value
      trigger(target, key) // 重點:通知依賴這個屬性的程式要更新了
    }
  })
}

function ref(value) {
  const refObject = {
    get value() {
      track(refObject, 'value') // 同樣的追蹤機制
      return value
    },
    set value(newValue) {
      value = newValue
      trigger(refObject, 'value') // 同樣的觸發機制
    }
  }
  return refObject
}

這裡有兩個關鍵概念,它們是理解 Watch 和 Computed 的基礎:

  • track: 記錄「誰」依賴於這個屬性(比如組件渲染函數或計算屬性)
  • trigger: 當屬性變化時,通知所有依賴者更新

看看真實源碼中的實現,你會發現同樣的 track 和 trigger 機制:

reactiveProxy Handler

理解了響應式系統後,我們就能更深入地理解 Watch 和 Computed 了!

Watch 是什麼?

簡單來說,Watch 就是一個「跟蹤者」。你告訴它要盯著哪個數據,當數據變化時,它會執行你給的回調函數。

Watch 的特點:

  • 明確追蹤特定數據源(可以是 ref、響應式對象或函數)
  • 數據變化時執行回調,通常會給你新值和舊值
  • 適合執行「副作用」,比如異步操作或複雜邏輯

來看看 Vue 源碼是如何實現 Watch 的:

export function watch(
  source: WatchSource | WatchSource[] | WatchEffect | object,
  cb?: WatchCallback | null,
  options: WatchOptions = EMPTY_OBJ,
): WatchHandle {
  // ... 省略前面部分 ...

  // 核心部分:創建 ReactiveEffect 來處理監聽
  effect = new ReactiveEffect(getter) // 重點:被觸發時,調度用戶的回調函數

  // ... 中間省略 ...

  // 初始執行
  if (cb) {
    if (immediate) {
      job(true) // 如果設定了 immediate: true,立即執行回調
    } else {
      oldValue = effect.run() // 否則先跑一次獲取初始值,不執行回調
    }
  } else if (scheduler) {
    scheduler(job.bind(null, true), true)
  } else {
    effect.run() // watchEffect 的情況
  }

  // ... 後面省略 ...
}

從源碼可以看出,Watch 的核心就是創建一個 ReactiveEffect 實例,當依賴變化時,它會執行你提供的回調函數。這就是為什麼 Watch 適合處理「副作用」——它的設計初衷就是在數據變化時執行操作,而不是計算新值。

Computed 是什麼?

Computed 則是基於現有數據「計算」出新的數據。它像是一個聰明的助手,會記住計算結果直到依賴的數據變化。

Computed 的特點:

  • 基於現有響應式數據計算新值
  • 有緩存機制,避免重複計算
  • 適合簡單操作,讓模板更乾淨

來看看 Computed 在源碼中的關鍵實現:

// ComputedRefImpl 類的部分方法

// 通知依賴更新時,不會立即重新計算
notify(): true | void {
  this.flags |= EffectFlags.DIRTY // 重點:只是標記為「髒」,不立即計算
  if (
    !(this.flags & EffectFlags.NOTIFIED) &&
    // 避免無限自我遞歸
    activeSub !== this
  ) {
    batch(this, true) // 批量處理更新
    return true
  } else if (__DEV__) {
    // 開發環境警告
  }
}

// 獲取值的方法
get value(): T {
  const link = __DEV__
    ? this.dep.track({
        target: this,
        type: TrackOpTypes.GET,
        key: 'value',
      })
    : this.dep.track()
  refreshComputed(this) // 重點:只有訪問且標記為髒時才重新計算
  // 同步版本
  if (link) {
    link.version = this.dep.version
  }
  return this._value // 返回計算結果
}

從源碼可以清楚看到 Computed 的兩個核心特性:

  1. 當依賴變化時,只是標記為「髒」(EffectFlags.DIRTY),不立即重新計算
  2. 只有當真正需要值時(有人讀取 .value),才會根據「髒」標記決定是否重新計算

這就是 Computed 高效的秘密——懶計算加緩存機制,避免不必要的重複計算,特別適合那些計算結果會被多次使用的場景。

Watch vs Computed:詳細比較

既然我們已經看過源碼實現了,讓我們來總結一下 Watch 和 Computed 的核心差異:

特性ComputedWatch
目的計算新數據執行副作用
返回值有返回值沒有返回值
緩存有緩存機制每次都執行
計算時機用到才計算(懶評估)數據變化就執行
副作用不適合(反模式)設計來做這個的
異步操作不適合完全適合
程式風格宣告式命令式
性能考量自動高效(緩存)開發者需自行優化

這些差異直接反映了它們在源碼中的實現方式:Computed 專注於有效率地產生和緩存值,而 Watch 則專注於在正確時機執行回調。

實際應用指南

基於源碼分析,這裡有一些實用的指導原則:

什麼時候用 Computed

  • 需要從其他數據計算出新數據時
  • 計算結果會被多次使用時(利用緩存提高性能)
  • 想讓模板更簡潔,把複雜邏輯移出來時
  • 需要在多處使用相同計算邏輯時

什麼時候用 Watch

  • 需要在數據變化時調用 API
  • 執行有「副作用」的操作時
  • 需要實現防抖或節流時(watch 支持 deepimmediate 等選項)
  • 需要比較數據變化前後的值時(watch 回調提供新舊值)
  • 需要監聽多個數據源並在任何一個變化時執行操作

誰比較快?源碼給出的答案

回到我們最初的問題:在源碼層面,Computed 通常會更快執行。為什麼呢?讓我們從源碼中找出答案:

  1. Computed 有緩存機制:從源碼中可以看到,Computed 使用 refreshComputed(this) 來判斷是否需要重新計算。只有當:
    • 依賴的數據變化了(標記為「髒」)
    • 確實有人訪問這個計算屬性 才會重新計算
  2. Watch 沒有緩存:Watch 的回調在每次依賴變化時都會執行,沒有跳過的機制(除非你自己實現)

但實際上,兩者的速度取決於你的應用場景:

  • 對於需要多次訪問的計算結果,Computed 肯定更快(得益於其緩存機制)
  • 對於異步操作或複雜副作用,Watch 更合適(雖然不一定更快)

源碼揭示的最佳實踐

從 Vue 的源碼設計中,我們可以學到一個很重要的原則:先考慮用 Computed(聲明式),只有在真正需要副作用或異步操作時才用 Watch(命令式)。

這不僅符合 Vue 的設計理念,也能讓你的應用獲得更好的性能和可維護性。Vue 源碼中 Computed 精心設計的緩存機制不僅能提高性能,還能「阻止不必要的下游更新」,這在複雜應用中尤為重要。

選對工具,讓你的 Vue 應用更高效!

Loading comments...