Vue Computed底層原理深入探究
今天面了家小公司,上來直接問 computed 底層原理,面試官是這樣問的,data 中定義了 a 和 b 變量。computed 里面定義了 c 屬性,c 的結(jié)果依賴與 a 和 b,模板中使用了變量 c。假設(shè)改變了 a,請問底層是如何收集依賴,如何觸發(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)
}
頁面效果:先顯示了 foo - bar,一秒之后顯示 FOO - bar
這里查源碼后,對 computed 原理簡述如下:
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 中每一個屬性,并且 new Watcher(computed watcher),可以看到傳入 Watcher 構(gòu)造函數(shù)的 cb 是 noop,它是一個空函數(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 對屬性進(jìn)行劫持,獲取是返回對應(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 組件在初始化時,initState -> initComputed -> new Watcher() 計算watcher,傳入的 callback 是 computer 屬性的 getter,由于 computed 是 lazy: true watcher,所以 new Watcher 時并不會立即執(zhí)行 watcher 實例上的 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 就會訪問所依賴的每一個值,就會被 Object.defineProperty 劫持到進(jìn)入 get ,執(zhí)行 dep.depend() , 會為每一個屬性對應(yīng)的 dep 實例添加一個 computed watcher,同時這個 computed watcher 也會保存對應(yīng)的 dep。
說了這么多都在講 computed watcher,那修改 this.a 頁面為什么會發(fā)生更新呢?
答案:因為 this.a 的依賴中不僅有 computed watcher 還有一個 render watcher
原因:
$mount 是會執(zhí)行 mountComponent,會創(chuàng)建一個 render watcher,它會立即執(zhí)行 cb(目前 Dep.target 是 render watcher),調(diào)用 render 函數(shù)在底層編譯模板,最后訪問屬性計算屬性 c,訪問計算屬性 c 就必定會訪問 a,當(dāng)訪問 a 時會觸發(fā) defineComputed 中的 Object.defineProperty 進(jìn)而劫持調(diào)用 createComputedGetter,進(jìn)而調(diào)用 watcher.depend(),這個 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,會將 computed watcher 里面的 deps 保存在所有依賴調(diào)用 deps[i].depend(),進(jìn)而調(diào)用 Dep 類中的 Dep.target.addDep(this),使得 render watcher 中保存了當(dāng)前的 dep,dep 中同時保存了 render watcher。
dep 中同時保存了 render watcher。就可以看出,示例中的屬性 a 的 dep 中也會保存 render watcher,所以 a 屬性的 dep 中有兩個 watcher: [computedWatcher, renderWatcher]
所以,修改 a 屬性的值,最后 notify 會清空這個 保存 watcher 的隊列,進(jìn)行頁面更新!Okay!
到此這篇關(guān)于Vue Computed底層原理深入探究的文章就介紹到這了,更多相關(guān)Vue Computed內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
如何處理vue router 路由傳參刷新頁面參數(shù)丟失
這篇文章主要介紹了如何處理vue router 路由傳參刷新頁面參數(shù)丟失,對vue感興趣的同學(xué),可以參考下2021-05-05
vuex中store存儲store.commit和store.dispatch的用法
這篇文章主要介紹了vuex中store存儲store.commit和store.dispatch的用法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-07-07

