生活随笔
收集整理的這篇文章主要介紹了
vue源码-对于「计算属性」的理解
小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.
vue源碼-對(duì)于「計(jì)算屬性」的理解
這是我最近學(xué)習(xí)vue源碼的一個(gè)個(gè)人總結(jié)和理解,所以可能并不適合每一位讀者
本文的整體脈絡(luò)如下,首先盡可能去掉細(xì)節(jié),對(duì)計(jì)算屬性源碼的大致實(shí)現(xiàn)有一個(gè)了解,然后舉一例子,分別談?wù)動(dòng)?jì)算屬性依賴收集和派發(fā)更新的流程。
- 計(jì)算屬性的源碼實(shí)現(xiàn)
- 舉例來(lái)說(shuō),談?wù)勴?yè)面初次渲染時(shí),整個(gè)依賴收集的過(guò)程
- 舉例來(lái)說(shuō),計(jì)算屬性的依賴被修改時(shí),派發(fā)更新的過(guò)程
另外推薦2個(gè)開(kāi)源的vue源碼分析集合
- https://ustbhuangyi.github.io...
- http://hcysun.me/vue-design/a...
計(jì)算屬性的源碼實(shí)現(xiàn)
- _init() --> initState() --> initComputed()
- 1.遍歷computed選項(xiàng),2.實(shí)例化computed watcher 3.defineComputed()
- defineComputed()核心就是把計(jì)算屬性用Object.defineProperty包裝成響應(yīng)式對(duì)象,而getter就是把用戶傳入的函數(shù)作為getter
- 但是準(zhǔn)確的說(shuō),是用戶傳遞的fn的返回值是作為計(jì)算屬性getter的return值,但是除此之外計(jì)算屬性在getter時(shí)還做了一些其他的操作
- 1是watch.depend() 2.return watch.evaluate()。 也就是1.收集依賴2.把值返回
this._init() : 重點(diǎn)關(guān)注重點(diǎn)init方法中initState initLifecycle(vm)initEvents(vm)initRender(vm)callHook(vm, 'beforeCreate')initInjections(vm) // resolve injections before data/propsinitState(vm)initProvide(vm) // resolve provide after data/propscallHook(vm, 'created')
initState() 重點(diǎn)關(guān)注這一句 if (opts.computed) initComputed(vm, opts.computed) export function initState (vm: Component) {vm._watchers = []const opts = vm.$optionsif (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() 核心就是遍歷computed,每次循環(huán)都實(shí)例化一個(gè)computed watch,并且用defineComputed把計(jì)算屬性包裝成響應(yīng)式對(duì)象 function initComputed (vm: Component, computed: Object) {// $flow-disable-lineconst watchers = vm._computedWatchers = Object.create(null)// computed properties are just getters during SSRconst isSSR = isServerRendering()for (const key in computed) {const userDef = computed[key]const getter = typeof userDef === 'function' ? userDef : userDef.getif (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)}}}
}
defineComputed() 核心就是Object.defineProperty ,大段代碼都是在給它設(shè)置get和set export function defineComputed (target: any,key: string,userDef: Object | Function
) {const shouldCache = !isServerRendering()if (typeof userDef === 'function') {sharedPropertyDefinition.get = shouldCache? createComputedGetter(key): userDefsharedPropertyDefinition.set = noop} else {sharedPropertyDefinition.get = userDef.get? shouldCache && userDef.cache !== false? createComputedGetter(key): userDef.get: noopsharedPropertyDefinition.set = userDef.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)
}
createComputedGetter : 計(jì)算屬性的getter就是這個(gè)computedGetter,做了2件事情,1.收集render watcher作為自己的依賴,2.調(diào)用用戶的那個(gè)函數(shù)作為返回值
function createComputedGetter (key) {return function computedGetter () {const watcher = this._computedWatchers && this._computedWatchers[key]if (watcher) {watcher.depend()return watcher.evaluate()}}
}
到目前為止就結(jié)束了,也就是說(shuō)初始化computed watcher都沒(méi)有求值
直到render時(shí),才會(huì)觸發(fā)computed watcher的getter
舉例來(lái)說(shuō),談?wù)勴?yè)面初次渲染時(shí),整個(gè)依賴收集的過(guò)程
比如我們有一個(gè)計(jì)算屬性,并且fullName是渲染在模板中的。
computed: {fullName(){return this.firstName + this.lastName}
}
那么頁(yè)面初次渲染時(shí),整個(gè)依賴收集的過(guò)程如下
- render函數(shù)執(zhí)行時(shí),會(huì)讀取計(jì)算屬性fullName,那么會(huì)觸發(fā)fullName的getter,那么就會(huì)執(zhí)行到watch.depend()和return watch.evaluate()
- 這個(gè)computed watcher的depend()會(huì)把render watcher作為依賴收集到它的subs里。
- 這個(gè)computed watcher的evaluate()主要是把調(diào)用了用戶給的那個(gè)函數(shù),求值并返回
- 最后值得注意的是,調(diào)用用戶的函數(shù),也就是執(zhí)行了this.firstName + this.lastName ,那么也會(huì)觸發(fā)他們的getter,所以他們也會(huì)把computed watcher作為依賴,收集到subs里,將來(lái)如果被修改的話,用通知subs里的watch調(diào)用update,也就是去派發(fā)更新
舉例來(lái)說(shuō),計(jì)算屬性的依賴被修改時(shí),派發(fā)更新的過(guò)程
- 當(dāng)this.firstName或者this.lastName被修改時(shí),會(huì)觸發(fā)他們的setter,setter就干兩個(gè)事情。1是賦值 2是派發(fā)更新
- 所以會(huì)通知他們的subs里的watch去調(diào)用自己的update方法。其中也包括computed watcher,
- 那么computed watcher在update方法跟普通的user watcher的update存在區(qū)別,computed watcher并不是直接推入異步更新隊(duì)列,而是 this.dep.notify()發(fā)出之前計(jì)算屬性所收集的依賴去派發(fā)更新,其中就包括render watcher,調(diào)用render watcher的update就會(huì)實(shí)現(xiàn)視圖更新了
- 注意this.getAndInvoke的作用,就是如果this.lastName和this.firstName變化了,但是經(jīng)過(guò)計(jì)算之后,計(jì)算屬性的值不變,那么也不會(huì)觸發(fā)notify的,也就不會(huì)更新視圖
update () {if (this.computed) {if (this.dep.subs.length === 0) {this.dirty = true} else {this.getAndInvoke(() => {this.dep.notify()})}} else if (this.sync) {this.run()} else {queueWatcher(this)}
}
這也是為什么,我們說(shuō)計(jì)算屬性的依賴屬性不被修改的話,計(jì)算屬性就不會(huì)變化。因?yàn)間etter就是你那個(gè)函數(shù)嘛,而且其實(shí)就算依賴變化了,只要計(jì)算之后的計(jì)算屬性變,也不會(huì)觸發(fā)視圖更新。
總結(jié)
以上是生活随笔為你收集整理的vue源码-对于「计算属性」的理解的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。