最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Vue中的computed與watch底層實現(xiàn)原理最佳實踐

 更新時間:2026年01月21日 16:16:40   作者:LYFlied  
本文介紹Vue中computed和watch的底層實現(xiàn)原理,對比了核心概念、設計差異以及使用場景,通過分析computed的初始化、依賴追蹤、緩存機制,以及watch的初始化、深度監(jiān)聽、異步更新,本文提供了一個全面的對比總結,并給出了性能優(yōu)化和最佳實踐建議,感興趣的朋友一起看看吧

Vue的computed與watch底層實現(xiàn)原理

1. 核心概念與設計差異

1.1 設計哲學對比

計算屬性 (computed)

  • 目的:聲明式地描述一個派生值
  • 特性:基于依賴緩存,惰性求值,響應式依賴追蹤
  • 使用場景:模板中的復雜表達式、格式化數(shù)據(jù)、計算派生狀態(tài)

偵聽器 (watch)

  • 目的:響應式地執(zhí)行副作用
  • 特性:主動觀察,可執(zhí)行異步操作,獲取新舊值
  • 使用場景:數(shù)據(jù)變化時執(zhí)行異步請求、執(zhí)行復雜邏輯、調試數(shù)據(jù)變化

1.2 核心差異總結

// 計算屬性 - 聲明式、緩存、同步
computed: {
  fullName() {
    return this.firstName + ' ' + this.lastName
  }
}
// 偵聽器 - 命令式、主動、可異步
watch: {
  firstName(newVal, oldVal) {
    // 執(zhí)行副作用
    this.fetchUserData(newVal)
  }
}

2. computed底層實現(xiàn)原理

2.1 初始化過程

2.1.1 定義階段
// Vue內部處理computed選項
function initComputed(vm, computed) {
  const watchers = (vm._computedWatchers = Object.create(null))
  for (const key in computed) {
    const userDef = computed[key]
    const getter = typeof userDef === 'function' ? userDef : userDef.get
    // 為每個計算屬性創(chuàng)建專門的Watcher
    watchers[key] = new Watcher(
      vm,
      getter || noop,
      noop,
      { lazy: true }  // 關鍵:標記為惰性求值
    )
    // 將計算屬性代理到Vue實例
    defineComputed(vm, key, userDef)
  }
}
// 定義計算屬性的getter/setter
function defineComputed(target, key, userDef) {
  // 支持自定義setter
  const setter = userDef.set || noop
  Object.defineProperty(target, key, {
    get: createComputedGetter(key),
    set: setter,
    enumerable: true,
    configurable: true
  })
}
2.1.2 惰性求值實現(xiàn)
function createComputedGetter(key) {
  return function computedGetter() {
    const watcher = this._computedWatchers && this._computedWatchers[key]
    if (watcher) {
      // 關鍵:只有當dirty為true時才重新計算
      if (watcher.dirty) {
        watcher.evaluate()  // 執(zhí)行計算,并標記dirty為false
      }
      // 依賴收集:將當前渲染W(wǎng)atcher添加到計算屬性的依賴中
      if (Dep.target) {
        watcher.depend()
      }
      return watcher.value
    }
  }
}

2.2 響應式依賴追蹤機制

2.2.1 依賴收集過程
// Watcher類的關鍵方法
class Watcher {
  constructor(vm, expOrFn, cb, options) {
    this.vm = vm
    this.lazy = !!options.lazy  // 是否為計算屬性Watcher
    this.dirty = this.lazy      // 惰性求值標記
    // 計算屬性Watcher的getter是計算函數(shù)
    this.getter = expOrFn
    // 初始值
    this.value = this.lazy ? undefined : this.get()
  }
  get() {
    // 將當前Watcher推入棧頂
    pushTarget(this)
    let value
    try {
      // 執(zhí)行getter,觸發(fā)依賴收集
      value = this.getter.call(this.vm, this.vm)
    } finally {
      // 恢復之前的Watcher
      popTarget()
    }
    return value
  }
  evaluate() {
    // 計算屬性的求值方法
    this.value = this.get()
    this.dirty = false  // 標記為已計算
  }
  depend() {
    // 讓依賴自己的Watcher收集自己作為依賴
    let i = this.deps.length
    while (i--) {
      this.deps[i].depend()
    }
  }
  update() {
    // 計算屬性的更新策略
    if (this.lazy) {
      this.dirty = true  // 僅標記為臟,不立即重新計算
    } else {
      // 普通Watcher的更新邏輯...
    }
  }
}
2.2.2 依賴關系圖
模板渲染W(wǎng)atcher
       ↑
       | depend()
計算屬性Watcher (lazy: true, dirty: false)
       ↑
       | 在evaluate()中收集
依賴的響應式數(shù)據(jù) (firstName, lastName)

2.3 緩存機制與性能優(yōu)化

2.3.1 緩存實現(xiàn)
// 計算屬性的緩存生命周期
const computedWatcher = new Watcher(vm, computedFn, null, { lazy: true })
// 第一次訪問:計算并緩存
computedWatcher.dirty = true → evaluate() → 計算結果 → dirty = false
// 第二次訪問(依賴未變):直接返回緩存
computedWatcher.dirty = false → 返回緩存值
// 依賴變化時:標記為臟
firstName改變 → computedWatcher.update() → dirty = true
// 再次訪問時重新計算
computedWatcher.dirty = true → evaluate() → 重新計算 → dirty = false
2.3.2 多級計算屬性依賴
computed: {
  // 計算屬性之間可以相互依賴
  fullName() {
    return this.firstName + ' ' + this.lastName
  },
  greeting() {
    // 依賴另一個計算屬性
    return 'Hello, ' + this.fullName
  }
}
// 依賴鏈:
// greeting → fullName → (firstName, lastName)

3. watch底層實現(xiàn)原理

3.1 初始化與Watcher創(chuàng)建

3.1.1 初始化過程
function initWatch(vm, watch) {
  for (const key in watch) {
    const handler = watch[key]
    // 支持數(shù)組形式的多個handler
    if (Array.isArray(handler)) {
      for (let i = 0; i < handler.length; i++) {
        createWatcher(vm, key, handler[i])
      }
    } else {
      createWatcher(vm, key, handler)
    }
  }
}
function createWatcher(vm, expOrFn, handler, options) {
  // 處理對象形式的handler
  if (isPlainObject(handler)) {
    options = handler
    handler = handler.handler
  }
  // 支持字符串方法名
  if (typeof handler === 'string') {
    handler = vm[handler]
  }
  // 調用$watch API
  return vm.$watch(expOrFn, handler, options)
}
3.1.2 $watch API實現(xiàn)
Vue.prototype.$watch = function(expOrFn, cb, options) {
  const vm = this
  // 規(guī)范化參數(shù)
  if (isPlainObject(cb)) {
    return createWatcher(vm, expOrFn, cb, options)
  }
  options = options || {}
  options.user = true  // 標記為用戶watcher
  // 創(chuàng)建用戶Watcher
  const watcher = new Watcher(vm, expOrFn, cb, options)
  // 立即執(zhí)行選項
  if (options.immediate) {
    cb.call(vm, watcher.value)
  }
  // 返回取消監(jiān)聽函數(shù)
  return function unwatchFn() {
    watcher.teardown()
  }
}

3.2 深度監(jiān)聽實現(xiàn)

3.2.1 deep選項的實現(xiàn)
class Watcher {
  constructor(vm, expOrFn, cb, options) {
    // ...
    this.deep = !!options.deep  // 深度監(jiān)聽標記
    if (typeof expOrFn === 'function') {
      this.getter = expOrFn
    } else {
      // 解析表達式為getter函數(shù)
      this.getter = parsePath(expOrFn)
    }
    this.value = this.get()
  }
  get() {
    pushTarget(this)
    let value
    try {
      value = this.getter.call(vm, vm)
    } finally {
      // 深度監(jiān)聽:遞歸遍歷對象
      if (this.deep) {
        traverse(value)  // 遍歷對象所有屬性,觸發(fā)它們的getter
      }
      popTarget()
    }
    return value
  }
}
3.2.2 traverse遞歸遍歷
// 簡化的traverse實現(xiàn)
function traverse(val) {
  const seenObjects = new Set()
  function _traverse(val) {
    // 跳過非對象、VNode和凍結對象
    if (!val || typeof val !== 'object' || Object.isFrozen(val)) {
      return
    }
    // 避免循環(huán)引用
    if (val.__ob__) {
      const depId = val.__ob__.dep.id
      if (seenObjects.has(depId)) {
        return
      }
      seenObjects.add(depId)
    }
    // 遞歸遍歷數(shù)組和對象
    if (Array.isArray(val)) {
      for (let i = 0; i < val.length; i++) {
        _traverse(val[i])
      }
    } else {
      const keys = Object.keys(val)
      for (let i = 0; i < keys.length; i++) {
        _traverse(val[keys[i]])
      }
    }
  }
  _traverse(val)
}

3.3 異步更新與防抖控制

3.3.1 sync選項控制同步性
class Watcher {
  update() {
    if (this.sync) {
      // 同步執(zhí)行
      this.run()
    } else if (this.lazy) {
      // 計算屬性:僅標記為臟
      this.dirty = true
    } else {
      // 默認:推入異步隊列
      queueWatcher(this)
    }
  }
  run() {
    const value = this.get()
    // 對于user watcher,調用回調并傳入新舊值
    if (this.user) {
      try {
        this.cb.call(this.vm, value, this.value)
      } catch (e) {
        handleError(e, this.vm, `callback for watcher "${this.expression}"`)
      }
    } else {
      this.cb.call(this.vm, value, this.value)
    }
    this.value = value
  }
}
3.3.2 支持異步回調
// watch示例:支持異步操作
watch: {
  searchQuery: {
    handler(newVal, oldVal) {
      // 可以執(zhí)行異步操作
      this.fetchResults(newVal)
    },
    immediate: true,
    deep: false
  }
}
// 底層實現(xiàn)支持異步回調
const watcher = new Watcher(vm, 'searchQuery', function(newVal, oldVal) {
  // 回調函數(shù)可以是異步的
  setTimeout(() => {
    console.log('Async update:', newVal)
  }, 0)
}, { user: true })

4. 核心差異的詳細對比

4.1 設計模式差異

計算屬性(聲明式/響應式)

// 底層:依賴追蹤 + 惰性求值 + 緩存
// 類似:React的useMemo + 自動依賴追蹤
computed: {
  // 聲明"什么值",由Vue負責"如何計算"
  discountedPrice() {
    return this.price * (1 - this.discount)
  }
}

偵聽器(命令式/副作用)

// 底層:觀察者模式 + 主動執(zhí)行
// 類似:React的useEffect
watch: {
  // 聲明"當什么變化時,執(zhí)行什么操作"
  price(newPrice) {
    // 執(zhí)行副作用
    this.logPriceChange(newPrice)
    this.updateChart(newPrice)
  }
}

4.2 執(zhí)行時機差異

4.2.1 計算屬性執(zhí)行時機
// 場景1:模板中使用計算屬性
<template>
  <div>{{ discountedPrice }}</div>
</template>
// 執(zhí)行流程:
// 1. 模板編譯時發(fā)現(xiàn)discountedPrice
// 2. 觸發(fā)discountedPrice的getter
// 3. 如果dirty=true,執(zhí)行計算函數(shù)
// 4. 收集依賴(price, discount)
// 5. 緩存計算結果
4.2.2 偵聽器執(zhí)行時機
// 場景:監(jiān)聽price變化
watch: {
  price(newVal, oldVal) {
    // 執(zhí)行流程:
    // 1. price被修改,觸發(fā)setter
    // 2. price的dep通知所有watcher
    // 3. 找到對應的user watcher
    // 4. 推入異步更新隊列
    // 5. 下一個tick執(zhí)行回調
    console.log(`Price changed from ${oldVal} to ${newVal}`)
  }
}

4.3 依賴處理差異

4.3.1 計算屬性的自動依賴收集
computed: {
  userInfo() {
    // Vue自動追蹤以下依賴:
    // this.user.name, this.user.age, this.isVIP
    return {
      name: this.user.name,
      age: this.user.age,
      level: this.isVIP ? 'VIP' : 'Normal'
    }
  }
}
// 依賴關系自動建立,無需手動聲明
4.3.2 偵聽器的顯式依賴聲明
watch: {
  // 需要明確指定要監(jiān)聽的數(shù)據(jù)路徑
  'user.name': function(newName) {
    // 只監(jiān)聽user.name的變化
  },
  // 或者監(jiān)聽整個對象(需要deep選項)
  user: {
    handler(newUser) {
      // 監(jiān)聽user對象的任何屬性變化
    },
    deep: true
  }
}

5. 特殊場景與高級用法

5.1 計算屬性的setter

computed: {
  fullName: {
    // getter:計算邏輯
    get() {
      return this.firstName + ' ' + this.lastName
    },
    // setter:反向更新
    set(newValue) {
      const names = newValue.split(' ')
      this.firstName = names[0]
      this.lastName = names[names.length - 1]
    }
  }
}
// 底層實現(xiàn)支持setter
function defineComputed(target, key, userDef) {
  const getter = typeof userDef === 'function' ? userDef : userDef.get
  const setter = userDef.set || noop  // 支持自定義setter
  Object.defineProperty(target, key, {
    get: createComputedGetter(key),
    set: setter
  })
}

5.2 偵聽器的立即執(zhí)行與防抖

watch: {
  searchQuery: {
    handler: 'fetchResults',
    immediate: true,  // 立即執(zhí)行一次
    deep: false
  }
}
// 結合lodash防抖
import { debounce } from 'lodash'
export default {
  data() {
    return { searchQuery: '' }
  },
  created() {
    // 手動創(chuàng)建防抖的watcher
    this.debouncedFetch = debounce(this.fetchResults, 500)
    this.$watch('searchQuery', this.debouncedFetch)
  },
  methods: {
    fetchResults(query) {
      // API調用
    }
  }
}

5.3 偵聽器返回取消函數(shù)

export default {
  data() {
    return { pollingInterval: null }
  },
  mounted() {
    // $watch返回取消監(jiān)聽函數(shù)
    const unwatch = this.$watch('dataId', (newId) => {
      // 開始輪詢
      this.startPolling(newId)
    })
    // 在組件銷毀時取消監(jiān)聽
    this.$once('hook:beforeDestroy', () => {
      unwatch()
      this.stopPolling()
    })
  }
}

6. 性能優(yōu)化與最佳實踐

6.1 計算屬性的性能優(yōu)勢

6.1.1 緩存避免重復計算
// 低效:每次渲染都重新計算
template: `<div>{{ expensiveComputation() }}</div>`
methods: {
  expensiveComputation() {
    // 每次渲染都會執(zhí)行
    return heavyCalculation(this.data)
  }
}
// 高效:使用計算屬性緩存
computed: {
  computedResult() {
    // 只在依賴變化時重新計算
    return heavyCalculation(this.data)
  }
}
6.1.2 避免不必要的響應式
// 不推薦:在data中定義計算值
data() {
  return {
    // 這會使fullName成為響應式數(shù)據(jù),但實際應由其他數(shù)據(jù)計算得出
    fullName: this.firstName + ' ' + this.lastName
  }
}
// 推薦:使用計算屬性
computed: {
  fullName() {
    return this.firstName + ' ' + this.lastName
  }
}

6.2 偵聽器的性能注意事項

6.2.1 避免深度監(jiān)聽的性能開銷
// 謹慎使用:深度監(jiān)聽大型對象
watch: {
  largeObject: {
    handler() {
      // 處理變化
    },
    deep: true  // 遍歷整個對象,性能開銷大
  }
}
// 優(yōu)化:監(jiān)聽具體屬性或使用計算屬性
watch: {
  'largeObject.importantProp': function(newVal) {
    // 只監(jiān)聽關鍵屬性
  }
}
6.2.2 及時清理偵聽器
export default {
  data() {
    return { externalData: null }
  },
  created() {
    // 創(chuàng)建多個偵聽器
    this.unwatch1 = this.$watch('filter', this.onFilterChange)
    this.unwatch2 = this.$watch('sortBy', this.onSortChange)
  },
  beforeDestroy() {
    // 清理偵聽器,避免內存泄漏
    this.unwatch1()
    this.unwatch2()
  }
}

7. Vue 3中的改進

7.1 Composition API中的實現(xiàn)

import { ref, computed, watch, watchEffect } from 'vue'
export default {
  setup() {
    const firstName = ref('John')
    const lastName = ref('Doe')
    // 計算屬性(響應式引用)
    const fullName = computed(() => {
      return firstName.value + ' ' + lastName.value
    })
    // 偵聽器(支持多個源)
    watch(
      [firstName, lastName],
      ([newFirst, newLast], [oldFirst, oldLast]) => {
        console.log('Name changed:', newFirst, newLast)
      }
    )
    // 自動依賴追蹤的watchEffect
    watchEffect(() => {
      // 自動追蹤內部使用的響應式數(shù)據(jù)
      document.title = `${firstName.value} ${lastName.value}`
    })
    return { firstName, lastName, fullName }
  }
}

7.2 Vue 3的底層優(yōu)化

// Vue 3使用Proxy實現(xiàn)響應式
const reactiveData = reactive({ 
  firstName: 'John',
  lastName: 'Doe'
})
// 計算屬性的實現(xiàn)更高效
const fullName = computed(() => {
  return reactiveData.firstName + ' ' + reactiveData.lastName
})
// 偵聽器支持更多選項
watch(
  () => reactiveData.firstName,
  (newVal, oldVal) => {
    console.log('First name changed')
  },
  {
    immediate: true,
    flush: 'post'  // 在組件更新后執(zhí)行
  }
)

8. 總結

8.1 核心區(qū)別總結表

特性computedwatch
設計目的聲明派生狀態(tài)執(zhí)行副作用
緩存有緩存,依賴不變不重新計算無緩存,每次變化都執(zhí)行
執(zhí)行時機惰性求值,訪問時計算主動執(zhí)行,變化時觸發(fā)
返回值必須返回值不需要返回值
異步支持必須是同步計算支持異步操作
依賴聲明自動收集依賴需要顯式聲明監(jiān)聽目標
使用場景模板中的計算、數(shù)據(jù)格式化異步操作、復雜邏輯、調試

8.2 選擇指南

使用computed當:

  • 需要基于其他數(shù)據(jù)計算新值
  • 需要在模板中使用的派生數(shù)據(jù)
  • 需要緩存優(yōu)化性能
  • 計算邏輯是純函數(shù),無副作用

使用watch當:

  • 需要在數(shù)據(jù)變化時執(zhí)行異步操作
  • 需要執(zhí)行副作用(API調用、DOM操作等)
  • 需要知道數(shù)據(jù)變化前后的值
  • 需要響應非響應式數(shù)據(jù)的變化

8.3 最佳實踐建議

  1. 優(yōu)先使用computed:對于派生數(shù)據(jù),計算屬性通常是更好的選擇
  2. 避免在computed中執(zhí)行副作用:保持計算屬性的純函數(shù)特性
  3. 謹慎使用deep watch:深度監(jiān)聽有性能開銷,盡量監(jiān)聽具體路徑
  4. 及時清理watch:在組件銷毀時取消不需要的偵聽器
  5. 考慮使用watchEffect:在Vue 3中,對于自動依賴追蹤的場景更簡潔

理解computed和watch的底層實現(xiàn)原理,可以幫助開發(fā)者更好地使用Vue的響應式系統(tǒng),編寫出更高效、更可維護的代碼。

到此這篇關于Vue中的computed與watch底層實現(xiàn)原理最佳實踐的文章就介紹到這了,更多相關vue computed與watch底層原理內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • 基于vue開發(fā)的在線付費課程應用過程

    基于vue開發(fā)的在線付費課程應用過程

    這篇文章主要介紹了基于vue開發(fā)的在線付費課程應用過程,非常不錯,具有參考借鑒價值,需要的朋友可以參考下
    2018-01-01
  • 詳解Weex基于Vue2.0開發(fā)模板搭建

    詳解Weex基于Vue2.0開發(fā)模板搭建

    這篇文章主要介紹了詳解Weex基于Vue2.0開發(fā)模板搭建,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-03-03
  • vue2.x引入threejs的實例代碼

    vue2.x引入threejs的實例代碼

    這篇文章主要介紹了vue2.x引入threejs,如果在開發(fā)過程中出現(xiàn)threejs生成的canvas出現(xiàn)外邊框,只需要用css樣式修改,本文通過實例代碼給大家介紹的非常詳細,需要的朋友可以參考下
    2022-09-09
  • vue+elementUI配置表格的列顯示與隱藏

    vue+elementUI配置表格的列顯示與隱藏

    這篇文章主要為大家詳細介紹了vue+elementUI配置表格的列顯示與隱藏,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-04-04
  • vue中引用阿里字體圖標的方法

    vue中引用阿里字體圖標的方法

    這篇文章主要介紹了vue中引用阿里字體圖標出現(xiàn)錯誤問題的解決方法,感興趣的朋友跟隨腳本之家小編一起學習吧
    2018-02-02
  • 使用Vue.js 和Chart.js制作絢麗多彩的圖表

    使用Vue.js 和Chart.js制作絢麗多彩的圖表

    這篇文章主要介紹了使用Vue.js 和Chart.js制作絢麗多彩的圖表,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,,需要的朋友可以參考下
    2019-06-06
  • vue使用動態(tài)組件手寫Router View實現(xiàn)示例

    vue使用動態(tài)組件手寫Router View實現(xiàn)示例

    這篇文章主要為大家介紹了vue使用動態(tài)組件手寫RouterView實現(xiàn)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-06-06
  • vue中ref()和reactive()區(qū)別小結

    vue中ref()和reactive()區(qū)別小結

    本文主要介紹了vue中ref()和reactive()區(qū)別小結,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2025-07-07
  • 淺談Vue2.0父子組件間事件派發(fā)機制

    淺談Vue2.0父子組件間事件派發(fā)機制

    本篇文章主要介紹了淺談Vue2.0父子組件間事件派發(fā)機制,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-01-01
  • 為什么Vue3.0使用Proxy實現(xiàn)數(shù)據(jù)監(jiān)聽(defineProperty表示不背這個鍋)

    為什么Vue3.0使用Proxy實現(xiàn)數(shù)據(jù)監(jiān)聽(defineProperty表示不背這個鍋)

    這篇文章主要介紹了為什么Vue3.0使用Proxy實現(xiàn)數(shù)據(jù)監(jiān)聽?defineProperty表示不背這個鍋,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2019-10-10

最新評論

东平县| 东源县| 安阳县| 金川县| 平泉县| 广水市| 巩留县| 白城市| 靖安县| 崇明县| 运城市| 西贡区| 沧源| 当雄县| 昌吉市| 关岭| 正安县| 长丰县| 卓资县| 会东县| 昌都县| 沅江市| 霍州市| 石嘴山市| 崇文区| 桐梓县| 保靖县| 乳源| 晋宁县| 麻城市| 新昌县| 资源县| 山东省| 盐津县| 阿克| 柘荣县| 天津市| 罗定市| 闸北区| 信阳市| 乌拉特前旗|