Vue之Computed依賴收集與更新原理分析
Computed依賴收集與更新原理
今天面了家小公司,上來直接問 computed 底層原理,面試官是這樣問的,data 中定義了 a 和 b 變量。
computed 里面定義了 c 屬性,c 的結(jié)果依賴與 a 和 b,模板中使用了變量 c。
假設(shè)改變了 a,請(qǐng)問底層是如何收集依賴,如何觸發(fā)更新的?
<div>{{ c }}</div>
data(){ return { a: 'foo', b: 'bar' } }, computed: { c() { return this.a + ' - ' + this.b; } }, mounted(){ setTimeout(() => { this.a = 'FOO' }, 1000) }
頁(yè)面效果:先顯示了 foo - bar,一秒之后顯示 FOO - bar
這里查源碼后,對(duì)computed原理簡(jiǎn)述
如下:
1、initState 函數(shù)中初始化了 initComputed
export function initState (vm: Component) { vm._watchers = [] const opts = vm.$options if (opts.props) initProps(vm, opts.props) if (opts.methods) initMethods(vm, opts.methods) if (opts.data) { initData(vm) } else { observe(vm._data = {}, true /* asRootData */) } if (opts.computed) initComputed(vm, opts.computed) if (opts.watch && opts.watch !== nativeWatch) { initWatch(vm, opts.watch) } }
2、initComputed 函數(shù)中遍歷 computed 中每一個(gè)屬性,并且 new Watcher(computed watcher),可以看到傳入 Watcher 構(gòu)造函數(shù)的 cb 是 noop,它是一個(gè)空函數(shù)。
并且 defineComputed
function initComputed (vm: Component, computed: Object) { // $flow-disable-line const watchers = vm._computedWatchers = Object.create(null) // computed properties are just getters during SSR const isSSR = isServerRendering() for (const key in computed) { const userDef = computed[key] const getter = typeof userDef === 'function' ? userDef : userDef.get if (process.env.NODE_ENV !== 'production' && getter == null) { warn( `Getter is missing for computed property "${key}".`, vm ) } if (!isSSR) { // create internal watcher for the computed property. watchers[key] = new Watcher( vm, getter || noop, noop, computedWatcherOptions ) } // component-defined computed properties are already defined on the // component prototype. We only need to define computed properties defined // at instantiation here. if (!(key in vm)) { defineComputed(vm, key, userDef) } else if (process.env.NODE_ENV !== 'production') { if (key in vm.$data) { warn(`The computed property "${key}" is already defined in data.`, vm) } else if (vm.$options.props && key in vm.$options.props) { warn(`The computed property "${key}" is already defined as a prop.`, vm) } else if (vm.$options.methods && key in vm.$options.methods) { warn(`The computed property "${key}" is already defined as a method.`, vm) } } } }
defineComputed 中使用了 Object.defineProperty 對(duì)屬性進(jìn)行劫持,獲取是返回對(duì)應(yīng)的 getter 即 createComputedGetter
export function defineComputed ( target: any, key: string, userDef: Object | Function ) { const shouldCache = !isServerRendering() if (typeof userDef === 'function') { sharedPropertyDefinition.get = shouldCache ? createComputedGetter(key) : createGetterInvoker(userDef) sharedPropertyDefinition.set = noop } else { sharedPropertyDefinition.get = userDef.get ? shouldCache && userDef.cache !== false ? createComputedGetter(key) : createGetterInvoker(userDef.get) : noop sharedPropertyDefinition.set = userDef.set || noop } if (process.env.NODE_ENV !== 'production' && sharedPropertyDefinition.set === noop) { sharedPropertyDefinition.set = function () { warn( `Computed property "${key}" was assigned to but it has no setter.`, this ) } } Object.defineProperty(target, key, sharedPropertyDefinition) } function createComputedGetter (key) { return function computedGetter () { const watcher = this._computedWatchers && this._computedWatchers[key] if (watcher) { if (watcher.dirty) { watcher.evaluate() } if (Dep.target) { watcher.depend() } return watcher.value } } }
看到這里,我們大致可以理解,Vue 組件在初始化時(shí),initState -> initComputed -> new Watcher() 計(jì)算watcher,傳入的 callback 是 computer 屬性的 getter,由于 computed 是 lazy: true watcher,所以 new Watcher 時(shí)并不會(huì)立即執(zhí)行 watcher 實(shí)例上的 get(), 而是在 defineComputed 函數(shù)里面調(diào)用 createComputedGetter 函數(shù),在 createComputedGetter 函數(shù)執(zhí)行了 watcher.evaluate() 進(jìn)而執(zhí)行了 watcher.get().
執(zhí)行 watcher.get() 首先 pushTarget(this),將 Dep.target 賦值為當(dāng)前的 computed watcher,然后執(zhí)行 this.getter
也就是執(zhí)行 computer 屬性配置的 getter,執(zhí)行g(shù)etter 就會(huì)訪問所依賴的每一個(gè)值,就會(huì)被 Object.defineProperty 劫持到進(jìn)入 get ,執(zhí)行 dep.depend() , 會(huì)為每一個(gè)屬性對(duì)應(yīng)的 dep 實(shí)例添加一個(gè) computed watcher,同時(shí)這個(gè) computed watcher 也會(huì)保存對(duì)應(yīng)的 dep。
說了這么多都在講 computed watcher,那修改 this.a 頁(yè)面為什么會(huì)發(fā)生更新呢?
答案:因?yàn)?this.a 的依賴中不僅有 computed watcher 還有一個(gè) render watcher
原因:
$mount 是會(huì)執(zhí)行 mountComponent,會(huì)創(chuàng)建一個(gè) render watcher,它會(huì)立即執(zhí)行 cb(目前 Dep.target 是 render watcher),調(diào)用 render 函數(shù)在底層編譯模板,最后訪問屬性計(jì)算屬性 c,訪問計(jì)算屬性 c 就必定會(huì)訪問 a,當(dāng)訪問 a 時(shí)會(huì)觸發(fā) defineComputed 中的 Object.defineProperty 進(jìn)而劫持調(diào)用 createComputedGetter,進(jìn)而調(diào)用 watcher.depend(),這個(gè) watcher 是 computed watcher
function createComputedGetter (key) { return function computedGetter () { const watcher = this._computedWatchers && this._computedWatchers[key] if (watcher) { if (watcher.dirty) { watcher.evaluate() } if (Dep.target) { watcher.depend() } return watcher.value } } }
// Watcher.js depend () { let i = this.deps.length while (i--) { this.deps[i].depend() } }
調(diào)用 watcher.depend() , this 指的是 computed watcher,會(huì)將 computed watcher 里面的 deps 保存在所有依賴調(diào)用 deps[i].depend(),進(jìn)而調(diào)用 Dep 類中的 Dep.target.addDep(this),使得 render watcher 中保存了當(dāng)前的 dep,dep 中同時(shí)保存了 render watcher。
dep 中同時(shí)保存了 render watcher。就可以看出,示例中的屬性 a 的 dep 中也會(huì)保存 render watcher,所以 a 屬性的 dep 中有兩個(gè) watcher: [computedWatcher, renderWatcher]
所以,修改 a 屬性的值,最后 notify 會(huì)清空這個(gè) 保存 watcher 的隊(duì)列,進(jìn)行頁(yè)面更新!Okay!
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
vue+node 實(shí)現(xiàn)視頻在線播放的實(shí)例代碼
這篇文章主要介紹了vue+node 實(shí)現(xiàn)視頻在線播放的實(shí)例代碼,代碼簡(jiǎn)單易懂,對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-10-10解決vue?eslint開發(fā)嚴(yán)格模式警告錯(cuò)誤的問題
這篇文章主要介紹了解決vue?eslint開發(fā)嚴(yán)格模式警告錯(cuò)誤的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-04-04Vue2.0基于vue-cli+webpack同級(jí)組件之間的通信教程(推薦)
下面小編就為大家?guī)硪黄猇ue2.0基于vue-cli+webpack同級(jí)組件之間的通信教程(推薦)。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-09-09關(guān)于Vue3中element-plus的el-dialog對(duì)話框無法顯示的問題及解決方法
最近今天在寫一個(gè)停車場(chǎng)管理系統(tǒng)的項(xiàng)目時(shí),在用vue3寫前端時(shí),在前端模板選擇上,我一時(shí)腦抽,突然決定放棄SpringBoot,選擇了以前幾乎很少用的element-plus,然后果不其然,錯(cuò)誤連連,下面給大家分享dialog對(duì)話框無法顯示的原因,感興趣的朋友一起看看吧2023-10-10vue如何設(shè)置動(dòng)態(tài)的柵格占位、水平偏移量、類名、樣式
這篇文章主要介紹了vue如何設(shè)置動(dòng)態(tài)的柵格占位、水平偏移量、類名、樣式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-09-09vue實(shí)現(xiàn)input框禁止輸入標(biāo)簽
這篇文章主要介紹了vue實(shí)現(xiàn)input框禁止輸入標(biāo)簽,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-04-04