Vue Computed底層原理深入探究
今天面了家小公司,上來(lái)直接問(wèn) computed 底層原理,面試官是這樣問(wèn)的,data 中定義了 a 和 b 變量。computed 里面定義了 c 屬性,c 的結(jié)果依賴與 a 和 b,模板中使用了變量 c。假設(shè)改變了 a,請(qǐng)問(wèn)底層是如何收集依賴,如何觸發(fā)更新的?
<div>{<!--{cke_protected}{C}%3C!%2D%2D%20%2D%2D%3E-->{ 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)述如下:
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) } }
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ì)訪問(wèn)所依賴的每一個(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。
說(shuō)了這么多都在講 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ù)在底層編譯模板,最后訪問(wèn)屬性計(jì)算屬性 c,訪問(wèn)計(jì)算屬性 c 就必定會(huì)訪問(wèn) a,當(dāng)訪問(wèn) 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!
到此這篇關(guān)于Vue Computed底層原理深入探究的文章就介紹到這了,更多相關(guān)Vue Computed內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Vue3+axios+Mock.js實(shí)現(xiàn)登錄功能的示例代碼
本文主要介紹了Vue3+axios+Mock.js實(shí)現(xiàn)登錄功能的示例代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-05-05Vue3+Element-Plus實(shí)現(xiàn)左側(cè)菜單折疊與展開(kāi)功能示例
本文主要介紹了Vue3+Element-Plus實(shí)現(xiàn)左側(cè)菜單折疊與展開(kāi)功能示例,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-04-04vue動(dòng)態(tài)路由刷新失效以及404頁(yè)面處理辦法
作為一個(gè)前端新手,項(xiàng)目中遇到權(quán)限處理時(shí),通常會(huì)采用動(dòng)態(tài)添加路由的方法來(lái)實(shí)現(xiàn),下面這篇文章主要給大家介紹了關(guān)于vue動(dòng)態(tài)路由刷新失效以及404頁(yè)面處理辦法的相關(guān)資料,需要的朋友可以參考下2023-11-11vue實(shí)現(xiàn)過(guò)渡動(dòng)畫Message消息提示組件示例詳解
這篇文章主要為大家介紹了vue實(shí)現(xiàn)過(guò)渡動(dòng)畫Message消息提示組件示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-07-07element table列表根據(jù)數(shù)據(jù)設(shè)置背景色
在使用elementui中的el-table時(shí),需要將表的背景色和字體顏色設(shè)置為新顏色,本文就來(lái)介紹一下element table列表根據(jù)數(shù)據(jù)設(shè)置背景色,感興趣的可以了解一下2023-08-08vue使用iview的modal彈窗嵌套modal出現(xiàn)格式錯(cuò)誤的解決
這篇文章主要介紹了vue使用iview的modal彈窗嵌套modal出現(xiàn)格式錯(cuò)誤的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-09-09