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

從源碼角度帶你深入理解Vue依賴收集的核心

 更新時間:2026年03月02日 08:39:11   作者:會員源碼網(wǎng)  
在?Vue?的響應式系統(tǒng)中,依賴收集是貫穿整個數(shù)據(jù)驅(qū)動視圖的核心環(huán)節(jié),本文將從?Vue?2?的源碼出發(fā),層層拆解依賴收集的完整流程,帶你從?“使用層”?走向?“原理層”,真正理解?Vue?響應式的底層邏輯

在 Vue 的響應式系統(tǒng)中,“依賴收集” 是貫穿整個數(shù)據(jù)驅(qū)動視圖的核心環(huán)節(jié)。很多開發(fā)者日常使用data、computed、watch時只知其然,卻不知其所以然 —— 為什么修改數(shù)據(jù)視圖會自動更新?為什么computed能精準緩存?這一切的背后,都是依賴收集機制在起作用。

本文將從 Vue 2 的源碼出發(fā),層層拆解依賴收集的完整流程,帶你從 “使用層” 走向 “原理層”,真正理解 Vue 響應式的底層邏輯。

一、先搞懂:什么是 “依賴”

在開始源碼分析前,我們先明確核心概念:

  • 依賴:本質(zhì)上是 “使用了某個響應式數(shù)據(jù)的執(zhí)行函數(shù)”,比如渲染組件的render函數(shù)、computed的計算函數(shù)、watch的回調(diào)函數(shù)。
  • 依賴收集:在響應式數(shù)據(jù)被讀取時,記錄下 “哪些函數(shù)依賴了這個數(shù)據(jù)”;當數(shù)據(jù)被修改時,找到這些記錄的函數(shù)并執(zhí)行,最終實現(xiàn) “數(shù)據(jù)變 → 視圖更”。

簡單來說,依賴收集的核心目標是:建立 “響應式數(shù)據(jù)” 與 “使用數(shù)據(jù)的函數(shù)” 之間的映射關系。

二、核心角色:依賴收集的 3 個關鍵模塊

Vue 2 的依賴收集主要依賴三個核心模塊,我們先認識它們:

模塊作用核心源碼位置
Observer將普通對象 / 數(shù)組轉(zhuǎn)為響應式(給屬性添加 get/set)src/core/observer/index.js
Dep依賴管理器:存儲某個響應式數(shù)據(jù)的所有依賴src/core/observer/dep.js
Watcher依賴的載體:封裝需要執(zhí)行的函數(shù)(如 render、computed)src/core/observer/watcher.js

三者的關系可以總結(jié)為:

Observer 給數(shù)據(jù)加 get/set 鉤子 → 讀取數(shù)據(jù)時觸發(fā) get,通過Dep收集Watcher → 修改數(shù)據(jù)時觸發(fā) set,通過Dep通知所有Watcher執(zhí)行。

三、源碼拆解:依賴收集的完整流程

3.1 第一步:響應式數(shù)據(jù)的初始化(Observer)

首先,Vue 會通過Observer類將data中的數(shù)據(jù)轉(zhuǎn)為響應式,核心是給每個屬性定義getter/setter。

核心源碼(簡化版):

// src/core/observer/index.js
class Observer {
  constructor(value) {
    this.value = value
    this.dep = new Dep() // 給對象/數(shù)組本身創(chuàng)建Dep
    def(value, '__ob__', this) // 給數(shù)據(jù)添加__ob__屬性,標記為響應式
    
    if (Array.isArray(value)) {
      // 處理數(shù)組的響應式(重寫push/pop等方法)
      this.observeArray(value)
    } else {
      // 處理對象的響應式:遍歷屬性并定義get/set
      this.walk(value)
    }
  }

  // 遍歷對象屬性,定義響應式
  walk(obj) {
    const keys = Object.keys(obj)
    for (let i = 0; i < keys.length; i++) {
      defineReactive(obj, keys[i])
    }
  }

  // 遍歷數(shù)組,給每個元素做響應式處理
  observeArray(items) {
    for (let i = 0, l = items.length; i < l; i++) {
      observe(items[i])
    }
  }
}

// 核心:給單個屬性定義get/set
export function defineReactive(
  obj,
  key,
  val,
  customSetter,
  shallow
) {
  // 每個響應式屬性都有一個專屬的Dep實例
  const dep = new Dep()

  const property = Object.getOwnPropertyDescriptor(obj, key)
  if (property && property.configurable === false) {
    return
  }

  // 保留原有的get/set
  const getter = property && property.get
  const setter = property && property.set

  // 遞歸處理子屬性,保證深層數(shù)據(jù)也是響應式
  let childOb = !shallow && observe(val)

  // 定義新的getter/setter
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    // 讀取屬性時觸發(fā):依賴收集的入口
    get: function reactiveGetter() {
      const value = getter ? getter.call(obj) : val
      
      // 關鍵:如果當前有活躍的Watcher,就收集依賴
      if (Dep.target) {
        dep.depend() // 1. 讓Dep記錄當前Watcher
        if (childOb) {
          // 2. 給對象/數(shù)組本身也收集依賴(處理數(shù)組/對象整體變更)
          childOb.dep.depend()
          if (Array.isArray(value)) {
            // 3. 數(shù)組特殊處理:遍歷子元素收集依賴
            dependArray(value)
          }
        }
      }
      return value
    },
    // 修改屬性時觸發(fā):通知依賴更新
    set: function reactiveSetter(newVal) {
      const value = getter ? getter.call(obj) : val
      // 新舊值相同則不處理
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return
      }
      
      if (setter) {
        setter.call(obj, newVal)
      } else {
        val = newVal
      }
      // 新值也要做響應式處理
      childOb = !shallow && observe(newVal)
      // 關鍵:通知所有依賴更新
      dep.notify()
    }
  })
}

核心要點

  • 每個響應式屬性都會創(chuàng)建一個Dep實例,專屬管理該屬性的依賴;
  • getter中觸發(fā)依賴收集,setter中觸發(fā)依賴更新;
  • 不僅處理單個屬性,還會遞歸處理子對象 / 數(shù)組,保證深層響應式。

3.2 第二步:依賴管理器(Dep)

Dep是依賴的 “容器”,核心作用是存儲和管理某個數(shù)據(jù)的所有Watcher,提供depend(收集)和notify(通知)兩個核心方法。

核心源碼(簡化版):

// src/core/observer/dep.js
export default class Dep {
  static target: ?Watcher; // 靜態(tài)屬性,存儲當前活躍的Watcher
  id: number; // 唯一標識
  subs: Array<Watcher>; // 存儲依賴的Watcher數(shù)組

  constructor() {
    this.id = uid++
    this.subs = []
  }

  // 添加一個Watcher到依賴列表
  addSub(sub: Watcher) {
    this.subs.push(sub)
  }

  // 移除一個Watcher
  removeSub(sub: Watcher) {
    remove(this.subs, sub)
  }

  // 核心:收集依賴(讓Dep和Watcher互相記錄)
  depend() {
    if (Dep.target) {
      // 調(diào)用當前Watcher的addDep方法,雙向綁定
      Dep.target.addDep(this)
    }
  }

  // 核心:通知所有Watcher更新
  notify() {
    // 復制一份依賴列表,避免更新過程中列表變化
    const subs = this.subs.slice()
    for (let i = 0, l = subs.length; i < l; i++) {
      // 調(diào)用Watcher的update方法
      subs[i].update()
    }
  }
}

// 全局唯一的Dep.target棧(處理嵌套Watcher,比如computed嵌套)
Dep.target = null
const targetStack = []

// 入棧:設置當前活躍的Watcher
export function pushTarget(target: ?Watcher) {
  targetStack.push(target)
  Dep.target = target
}

// 出棧:恢復上一個Watcher
export function popTarget() {
  targetStack.pop()
  Dep.target = targetStack[targetStack.length - 1]
}

核心要點

  • Dep.target是全局唯一的,始終指向 “當前正在執(zhí)行的 Watcher”;
  • depend()方法不是直接添加 Watcher,而是調(diào)用Watcher.addDep(),實現(xiàn) Dep 和 Watcher 的雙向記錄(避免重復收集);
  • 用棧結(jié)構(gòu)targetStack處理嵌套場景(比如組件嵌套、computed 嵌套)。

3.3 第三步:依賴載體(Watcher)

Watcher是 “依賴” 的具體載體,每個 Watcher 對應一個需要執(zhí)行的函數(shù)(比如組件的render函數(shù)、computed的計算函數(shù))。

核心源碼(簡化版):

// src/core/observer/watcher.js
export default class Watcher {
  constructor(
    vm: Component,
    expOrFn: string | Function,
    cb: Function,
    options?: ?Object,
    isRenderWatcher?: boolean
  ) {
    this.vm = vm
    if (isRenderWatcher) {
      vm._watcher = this
    }
    vm._watchers.push(this)
    
    // 處理配置項(比如lazy、deep、sync)
    if (options) {
      this.lazy = !!options.lazy // computed用
      this.deep = !!options.deep // 深度監(jiān)聽用
      this.sync = !!options.sync // 同步更新用
    } else {
      this.lazy = this.deep = this.sync = false
    }
    
    this.cb = cb // 更新回調(diào)
    this.id = uid++ // 唯一標識
    this.deps = [] // 存儲當前Watcher依賴的Dep
    this.newDeps = [] // 臨時存儲新依賴(用于依賴清理)
    this.depIds = new Set() // 去重
    this.newDepIds = new Set()
    
    // 解析表達式/函數(shù),得到最終要執(zhí)行的函數(shù)
    this.getter = typeof expOrFn === 'function' ? expOrFn : parsePath(expOrFn)
    
    // 非lazy模式(比如render、watch)立即執(zhí)行g(shù)et,觸發(fā)依賴收集
    this.value = this.lazy ? undefined : this.get()
  }

  // 核心:執(zhí)行g(shù)etter并收集依賴
  get() {
    // 1. 將當前Watcher入棧,設置為Dep.target
    pushTarget(this)
    let value
    const vm = this.vm
    try {
      // 2. 執(zhí)行g(shù)etter(比如render函數(shù)),觸發(fā)數(shù)據(jù)的getter
      // 此時數(shù)據(jù)的getter會檢測到Dep.target,從而收集當前Watcher
      value = this.getter.call(vm, vm)
    } catch (e) {
      // 錯誤處理
    } finally {
      // 3. 深度監(jiān)聽處理
      if (this.deep) {
        traverse(value)
      }
      // 4. 出棧,恢復Dep.target
      popTarget()
      // 5. 清理無用的依賴
      this.cleanupDeps()
    }
    return value
  }

  // 核心:添加Dep到Watcher(與Dep.depend()配合)
  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也記錄當前Watcher
        dep.addSub(this)
      }
    }
  }

  // 清理無用依賴(比如數(shù)據(jù)從視圖中移除后,不再監(jiān)聽)
  cleanupDeps() {
    // 省略清理邏輯...
  }

  // 核心:響應式數(shù)據(jù)更新時,觸發(fā)Watcher更新
  update() {
    if (this.lazy) {
      // computed:標記為臟值,下次訪問時重新計算
      this.dirty = true
    } else if (this.sync) {
      // 同步更新:立即執(zhí)行run
      this.run()
    } else {
      // 異步更新(Vue默認):加入隊列,批量更新
      queueWatcher(this)
    }
  }

  // 執(zhí)行g(shù)etter并觸發(fā)回調(diào)
  run() {
    const value = this.get()
    if (value !== this.value || this.deep) {
      const oldValue = this.value
      this.value = value
      // 執(zhí)行回調(diào)(比如watch的回調(diào)函數(shù))
      this.cb.call(this.vm, value, oldValue)
    }
  }

  // computed專用:計算并返回最新值
  evaluate() {
    this.value = this.get()
    this.dirty = false
  }

  // 重新收集依賴
  depend() {
    let i = this.deps.length
    while (i--) {
      this.deps[i].depend()
    }
  }
}

核心要點

Watcher.get()是觸發(fā)依賴收集的關鍵:先將自身設為Dep.target,再執(zhí)行getter(比如render函數(shù)),此時render中用到的所有響應式數(shù)據(jù)都會觸發(fā)getter,從而收集當前 Watcher;

不同類型的 Watcher 有不同的更新策略:

  • 渲染 Watcher(render):異步更新,加入隊列批量執(zhí)行;
  • 計算 Watcher(computed):懶更新(lazy: true),只有訪問時才重新計算;
  • 偵聽 Watcher(watch):可配置同步 / 異步,支持深度監(jiān)聽。

四、特殊場景的依賴處理

4.1 數(shù)組的依賴收集

數(shù)組的響應式處理和對象不同(因為數(shù)組的索引無法被Object.defineProperty攔截),Vue 重寫了push、pop、splice等 7 個數(shù)組方法,核心邏輯:

// src/core/observer/array.js
const arrayProto = Array.prototype
export const arrayMethods = Object.create(arrayProto)

// 重寫的7個方法
const methodsToPatch = [
  'push',
  'pop',
  'shift',
  'unshift',
  'splice',
  'sort',
  'reverse'
]

methodsToPatch.forEach(function (method) {
  // 保留原方法
  const original = arrayProto[method]
  def(arrayMethods, method, function mutator(...args) {
    // 執(zhí)行原方法
    const result = original.apply(this, args)
    // 獲取數(shù)組的__ob__(Observer實例)
    const ob = this.__ob__
    // 處理新增元素(push/unshift/splice),轉(zhuǎn)為響應式
    let inserted
    switch (method) {
      case 'push':
      case 'unshift':
        inserted = args
        break
      case 'splice':
        inserted = args.slice(2)
        break
    }
    if (inserted) ob.observeArray(inserted)
    // 關鍵:通知依賴更新
    ob.dep.notify()
    return result
  })
})

核心:數(shù)組的依賴收集在數(shù)組本身的__ob__.dep中,修改數(shù)組時調(diào)用ob.dep.notify()觸發(fā)更新。

4.2 computed 的依賴收集

computed的 Watcher 是 “懶 Watcher”(lazy: true),特點:

  • 初始化時不立即執(zhí)行get,只有首次訪問時才觸發(fā);
  • 依賴的數(shù)據(jù)更新時,只標記dirty: true,不立即重新計算;
  • 下次訪問computed屬性時,才重新計算并緩存結(jié)果。

核心邏輯:

// src/core/instance/state.js
function createComputedGetter(key) {
  return function computedGetter() {
    const watcher = this._computedWatchers && this._computedWatchers[key]
    if (watcher) {
      if (watcher.dirty) {
        // 只有臟值時才重新計算
        watcher.evaluate()
      }
      // 收集渲染W(wǎng)atcher到computed的依賴中
      if (Dep.target) {
        watcher.depend()
      }
      return watcher.value
    }
  }
}

五、常見問題與面試考點

為什么 Vue 不能檢測數(shù)組索引和長度的變化

  • 數(shù)組索引的get/set雖然能被Object.defineProperty攔截,但考慮到性能成本(數(shù)組元素可能很多),Vue 放棄了這種方式;
  • 數(shù)組長度的set也無法被有效攔截,且修改長度的場景較少;
  • 解決方案:使用 Vue 提供的變異方法(push/splice 等)或Vue.set。

為什么修改對象的新屬性視圖不更新

  • 因為對象初始化時,只有已定義的屬性被添加了get/set,新屬性沒有;
  • 解決方案:使用Vue.set(obj, key, value)this.$set,本質(zhì)是給新屬性添加get/set并觸發(fā)依賴更新。

依賴收集為什么要雙向記錄(Dep→Watcher 和 Watcher→Dep)

  • 避免重復收集:通過depIdsnewDepIds去重;
  • 方便依賴清理:組件銷毀時,Watcher 可以遍歷自己的deps,從 Dep 中移除自身;
  • 支持依賴更新:Watcher 可以通過deps重新收集依賴(比如computeddepend方法)。

六、總結(jié)

Vue 的依賴收集機制是響應式系統(tǒng)的靈魂,核心可以總結(jié)為 3 點:

  • 核心鏈路Observer給數(shù)據(jù)加get/set → 讀取數(shù)據(jù)時Dep收集Watcher → 修改數(shù)據(jù)時Dep通知Watcher執(zhí)行 → 視圖更新;
  • 核心角色Observer(響應式標記)、Dep(依賴容器)、Watcher(依賴載體)三者協(xié)同工作;
  • 性能優(yōu)化:通過懶更新(computed)、異步隊列(渲染 Watcher)、依賴清理等方式,保證響應式的高效性。

理解依賴收集,不僅能幫你解決日常開發(fā)中的響應式問題,更能讓你從底層理解 Vue 的設計思想。

以上就是從源碼角度帶你深入理解Vue依賴收集的核心的詳細內(nèi)容,更多關于Vue依賴收集的資料請關注腳本之家其它相關文章!

相關文章

  • vue如何動態(tài)給img賦值

    vue如何動態(tài)給img賦值

    這篇文章主要介紹了vue如何動態(tài)給img賦值,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-06-06
  • 使用vue3+vite導入圖片路徑錯亂問題排查及解決

    使用vue3+vite導入圖片路徑錯亂問題排查及解決

    使用vue3+vite開發(fā)的時候,導入svg圖片時,同一個文件夾下的文件,其中一個路徑正常解析,另一個不行,更改文件名之后,該圖片文件就可以正常解析了,本文給大家介紹了使用vue3+vite導入圖片路徑錯亂問題排查及解決,需要的朋友可以參考下
    2024-03-03
  • vue引用public目錄下文件的方式詳解

    vue引用public目錄下文件的方式詳解

    由于一些演示需要對一些簡單頁面進行配置,由于打包build后的vue項目基本已經(jīng)看不出原樣,因此需要創(chuàng)建一個文件,并在打包的時候不會進行編譯,所以文件放在public,下面這篇文章主要給大家介紹了關于vue引用public目錄下文件的相關資料,需要的朋友可以參考下
    2022-08-08
  • Vue生命周期實例分析總結(jié)

    Vue生命周期實例分析總結(jié)

    Vue的生命周期就是vue實例從創(chuàng)建到銷毀的全過程,也就是new Vue()開始就是vue生命周期的開始。Vue實例有?個完整的?命周期,也就是從開始創(chuàng)建、初始化數(shù)據(jù)、編譯模版、掛載Dom->渲染、更新->渲染、卸載等?系列過程,稱這是Vue的?命周期
    2022-10-10
  • 深入淺出分析vue2和vue3的區(qū)別

    深入淺出分析vue2和vue3的區(qū)別

    這篇文章主要介紹了vue2和vue3的區(qū)別,較為詳細的分析了vue2與vue3的常見區(qū)別與使用方法,需要的朋友可以參考下
    2023-06-06
  • Vue管理系統(tǒng)前端之組件拆分封裝詳解

    Vue管理系統(tǒng)前端之組件拆分封裝詳解

    這篇文章主要給大家介紹了關于Vue管理系統(tǒng)前端之組件拆分封裝的相關資料,文中通過實例代碼結(jié)束的非常詳細,對大家學習或者使用Vue具有一定的參考學習價值,需要的朋友們下面來一起學習學習吧
    2020-08-08
  • VUE3之Non-Props屬性的具體使用

    VUE3之Non-Props屬性的具體使用

    本文主要介紹了VUE3之Non-Props屬性的具體使用,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-01-01
  • vue element ui validate 主動觸發(fā)錯誤提示操作

    vue element ui validate 主動觸發(fā)錯誤提示操作

    這篇文章主要介紹了vue element ui validate 主動觸發(fā)錯誤提示操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-09-09
  • 使用Vite處理css?less及postcss示例詳解

    使用Vite處理css?less及postcss示例詳解

    這篇文章主要為大家介紹了使用Vite處理css?less及postcss的示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-07-07
  • Vue前端空值處理的實戰(zhàn)指南與避坑記錄

    Vue前端空值處理的實戰(zhàn)指南與避坑記錄

    本文旨在幫助同學們學會在前端真實業(yè)務項目里,到底該怎么寫空值處理(?.、??、||、if判斷、兜底邏輯),以及為什么這么選、會踩哪些高頻坑,順便幫大家拉直JS/TS空值、真值假值的基礎概念,助力你們寫出規(guī)范可維護的團隊級代碼
    2026-03-03

最新評論

大宁县| 门头沟区| 江西省| 株洲县| 武义县| 孙吴县| 石屏县| 宝鸡市| 正安县| 罗甸县| 赣榆县| 灵丘县| 栾川县| 湘潭县| 广宗县| 秦安县| 昭平县| 黄梅县| 河南省| 木兰县| 内丘县| 武冈市| 吉林市| 江城| 丰都县| 宝丰县| 措美县| 会同县| 中西区| 商洛市| 济南市| 靖边县| 上杭县| 昌平区| 栾川县| 绵阳市| 民勤县| 东丰县| 平和县| 美姑县| 宝坻区|