媒介
上一节偏重报告了initData
中的代码,以及数据是怎样从data中到视图层的,以及data修改后怎样作用于视图。这一节重要纪录initComputed
中的内容。
正文
前情回忆
在demo示例中,我们定义了一个盘算属性。
computed:{
total(){
return this.a + this.b
}
}
本章节我们继承探讨这个盘算属性的相干流程。
initComputed
// initComputed(vm, opts.computed)
function initComputed (vm: Component, computed: Object) {
// 定义盘算属性相干的watchers.
const watchers = vm._computedWatchers = Object.create(null)
// 是不是是服务端衬着,这里赞不斟酌。
const isSSR = isServerRendering()
for (const key in computed) {
// 取得用户定义的盘算属性中的item,通常是一个要领
// 在示例顺序中,唯一一个key为total的盘算a+b的要领。
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.
// 为盘算属性建立一个内部的watcher。
// 个中computedWatcherOptions的值为lazy,意味着这个wacther内部的value,先不必盘算。
// 只要在须要的状况下才盘算,这里重假如在后期页面衬着中,天生假造dom的时刻才会盘算。
// 这时刻new Watcher只是走一遍watcher的组织函数,其内部value因为
// lazy为true,先设置为了undefined.同时内部的dirty = lazy;
watchers[key] = new Watcher(
vm,
getter || noop,
noop,
computedWatcherOptions // 上文定义过,值为{lazy: true}
)
}
// component-defined computed properties are already defined on the
// component prototype. We only need to define computed properties defined
// at instantiation here.
// 组件定义的属性只是定义在了组件上,这里只是把它翻译到实例中。即当前的vm对象。
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
const sharedPropertyDefinition = {
enumerable: true,
configurable: true,
get: noop,
set: noop
}
// defineComputed(vm, key, userDef)
export function defineComputed (
target: any,
key: string,
userDef: Object | Function
) {
// 是不是须要缓存。即非服务端衬着须要缓存。
// 因为本案例用的demo非服务端衬着,这里效果是true
const shouldCache = !isServerRendering()
if (typeof userDef === 'function') {
// userDef = total() {...}
sharedPropertyDefinition.get = shouldCache
// 依据key建立盘算属性的getter
? createComputedGetter(key)
: userDef
// 盘算属性是只读的,所以设置setter为noop.
sharedPropertyDefinition.set = noop
} else {
sharedPropertyDefinition.get = userDef.get
? shouldCache && userDef.cache !== false
? createComputedGetter(key)
: userDef.get
: noop
sharedPropertyDefinition.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
// 依据key建立盘算属性的getter
// createComputedGetter(key)
function createComputedGetter (key) {
return function computedGetter () {
// 非服务端衬着的时刻,在上述的initComputed中定义了vm._computedWatchers = {},并依据组件中的设定watchers[key] = new Watcher(..),这里只是依据key掏出了当时new的watcher
const watcher = this._computedWatchers && this._computedWatchers[key]
if (watcher) {
// watcher.dirty示意这个值是脏值,逾期了。所以须要从新盘算。
// new Watcher的时刻,这个total的watcher中,内部的dirty已被置为
// dirty = lazy = true;
// 那末这个值什么时刻会逾期,会脏呢。就是内部的依靠更新时刻,
// 比方我们的total依靠于this.a,this.b,当着两个值恣意一个变化时刻
// 我们的total就已脏了。须要依据最新的a,b盘算。
if (watcher.dirty) {
// 盘算watcher中的值,即value属性.
watcher.evaluate()
}
// 将依靠添加到watcher中。
if (Dep.target) {
watcher.depend()
}
// getter的效果就是返回getter中的值。
return watcher.value
}
}
}
initComputed小结
继initComputed以后,一切组件中的computed都被赋值到了vm实例的属性上,并设置好了getter和setter。在非服务端衬着的状况下,getter会缓存盘算效果。并在须要的时刻,才盘算。setter则是一个什么都不做的函数,预示着盘算属性只能被get,不能被set。即只读的。
接下来的题目就是:
- 这个盘算属性什么时刻会盘算,前文
{lazy:true}
预示着当时new Watcher获得的值是undefined。还没最先盘算。 - 盘算属性是怎样晓得它本身依靠于哪些属性的。以便晓得其什么时刻更新。
- vue官方文档的缓存盘算效果怎样明白。
接下来我们继承理会背面的代码。处置惩罚这里提到的三个题目。
用来天生vnode的render函数
下次再见到这个盘算属性total的时刻,已经是在依据el选项或许template模板中,天生的render函数,render函数上一小节也提到过。长这个模样。
(function anonymous() {
with (this) {
return _c('div', {
attrs: {
"id": "demo"
}
}, [_c('div', [_c('p', [_v("a:" + _s(a))]), _v(" "), _c('p', [_v("b: " + _s(b))]), _v(" "), _c('p', [_v("a+b: " + _s(total))]), _v(" "), _c('button', {
on: {
"click": addA
}
}, [_v("a+1")])])])
}
}
)
这里能够连系一下我们的html,看出一些特性。
<div id="demo">
<div>
<p>a:{{a}}</p>
<p>b: {{b}}</p>
<p>a+b: {{total}}</p>
<button @click="addA">a+1</button>
</div>
</div>
这里使用到盘算属性的重假如这一句
_v("a+b: " + _s(total))
那末关于我们来讲的症结就是_s(total)
。因为这个函数的with(this)
中,this
被设置为vm实例,所以这里就能够明白为_s(vm.total)
。那末这里就会触发之前定义的sharedPropertyDefinition.get
-> initComputed()
-> defineComputed()
-> Object.defineProperty(target, key, sharedPropertyDefinition)
也就是createComputedGetter
返回的函数中的内容,也就是:
watcher细说
const watcher = this._computedWatchers && this._computedWatchers[key]
if (watcher) {
// 因为初始化的时刻这个dirty为true,所以会举行watcher.evaluate()的盘算。
if (watcher.dirty) {
watcher.evaluate()
}
if (Dep.target) {
watcher.depend()
}
// getter的效果就是返回getter中的值。
return watcher.value
}
这里我们看下watcher.evaluate的部份。
// class Watcher内部
/**
* Evaluate the value of the watcher.
* This only gets called for lazy watchers.
*/
evaluate () {
this.value = this.get()
this.dirty = false
}
这里this.get
即获得了value的值,这就是第一个题目的答案。
1.盘算属性什么时候会盘算。
即用到的时刻会盘算,准确的说,就是在盘算vnode的时刻会用到它,从而盘算它。
关于第二个题目,盘算属性是怎样晓得它本身依靠于哪些属性的?则是在这个this.get
内。
// Dep相干逻辑,Dep Class用来网络依靠某个值的watcher
Dep.target = null
const targetStack = []
export function pushTarget (_target: Watcher) {
if (Dep.target) targetStack.push(Dep.target)
Dep.target = _target
}
export function popTarget () {
Dep.target = targetStack.pop()
}
// Watcher class 相干逻辑
get () {
// 将当前的watcher推到Dep.target中
pushTarget(this)
let value
const vm = this.vm
try {
// 这里的getter实际上就是对应total的函数体,
// 而这个函数体内藏有很大的猫腻,接下来我们仔细剖析这一段。
value = this.getter.call(vm, vm)
} catch (e) {
if (this.user) {
handleError(e, vm, `getter for watcher "${this.expression}"`)
} else {
throw e
}
} finally {
// "touch" every property so they are all tracked as
// dependencies for deep watching
if (this.deep) {
traverse(value)
}
popTarget()
this.cleanupDeps()
}
return value
}
当代码实行到this.getter.call
,实际上实行的是盘算属性的函数,也就是total() { return this.a + this.b}
;当代码实行到this.a时刻。就会触发上一节我们所讲的defineReactive
内部的代码。
//// 这里我们以接见this.a为例
export function defineReactive (
obj: Object, // {a:1,b:1}
key: string, // 'a'
val: any, // 1
customSetter?: ?Function,
shallow?: boolean
) {
const dep = new Dep()
const property = Object.getOwnPropertyDescriptor(obj, key)
if (property && property.configurable === false) {
return
}
// cater for pre-defined getter/setters
const getter = property && property.get
const setter = property && property.set
let childOb = !shallow && observe(val)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
const value = getter ? getter.call(obj) : val
// this.a会触发这里的代码。起首取得value,
// 因为watcher内部this.get实行total盘算属性时刻,已将
// total的watcher设置为Dep.target
if (Dep.target) {
// 所以这里最先网络依靠。
dep.depend()
if (childOb) {
childOb.dep.depend()
if (Array.isArray(value)) {
dependArray(value)
}
}
}
return value
},
set: function reactiveSetter (newVal) {
const value = getter ? getter.call(obj) : val
/* eslint-disable no-self-compare */
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
/* eslint-enable no-self-compare */
if (process.env.NODE_ENV !== 'production' && customSetter) {
customSetter()
}
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
childOb = !shallow && observe(newVal)
dep.notify()
}
})
}
上述代码中,this.a
触发了dep.depend()
。我们细看这里的代码。
class Dep {
//省略代码...
depend () {
// 因为这里的Dep.target此时对应的是total的watcher。
// 而这里的this.是指定义this.a时,天生的dep。
// 所以这里是关照total依靠于this.a
if (Dep.target) {
// 经由过程挪用addDep.让total的watcher晓得total依靠this.a
Dep.target.addDep(this)
}
}
}
class Watcher {
// ...省略代码
addDep (dep: Dep) {
// 此时的this是total的watcher
const id = dep.id
// 防备反复网络
if (!this.newDepIds.has(id)) {
// 将依靠的可视察对象纪录。
this.newDepIds.add(id)
this.newDeps.push(dep)
// 假如这个可视察对象没有纪录当前watcher,
if (!this.depIds.has(id)) {
// 则将当前的watcher到场到可视察对象中
// (轻易后续a变化后,示知total)
dep.addSub(this)
}
}
}
}
至此,上述的第二个题目,盘算属性是怎样晓得它本身依靠于哪些属性的?也有了答案。就是当天生假造dom的时刻,用到了total,因为获得total值的watcher是脏的,须要盘算一次,然后就将Dep.target的watcher设为total相干的watcher。并在watcher内实行了total函数,在函数内部,接见了this.a。this.a的getter中,经由过程dep.depend()
,将this.a的getter上方的dep,到场到total的watcher.dep中,再经由过程watcher中的dep.addSub(this)
,将total的watcher到场到了this.a的getter上方中的dep中。至此total晓得了它依靠于this.a。this.a也晓得了,total须要this.a。
当盘算属性的依靠变动时发生了什么
当点击页面按钮的时刻,会实行我们案例中绑定的this.a += 1的代码。此时会走
this.a的setter函数。我们看看setter中所做的事变。
set: function reactiveSetter (newVal) {
const value = getter ? getter.call(obj) : val
// 假如旧值与新值相称,什么都不做。直接返回。
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
// 无关代码,pass
if (process.env.NODE_ENV !== 'production' && customSetter) {
customSetter()
}
// 有定义过setter的话经由过程setter设置新值
if (setter) {
setter.call(obj, newVal)
} else {
// 不然的话直接设置新值
val = newVal
}
// 斟酌新值是对象的状况。
childOb = !shallow && observe(newVal)
// 关照视察了this.a的视察者。
// 这里实际上是有两个视察a的视察者
// 一个是上一篇讲的updateComponent。
// 一个是这节讲的total。
dep.notify()
}
这里我们看看dep.notify干了什么
class Dep {
// **** 其他代码
notify () {
// 这里的subs实在就是上述的两个watcher。
// 离别实行watcher的update
const subs = this.subs.slice()
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
}
class Watcher{
update () {
// 第一个watcher,即关于updateComponent的。
// 会实行queueWatcher。也就是会将处置惩罚放到守候行列里
// 守候行列中,而第二个watcher因为lazy为true,
// 所以只是将watcher标记为dirty。
// 因为行列这个比较复杂,所以单开话题去讲
// 这里我们只须要晓得它是一个异步的行列,末了效果就是
// 挨个实行行列中watcher的run要领。
if (this.lazy) {
this.dirty = true
} else if (this.sync) {
this.run()
} else {
queueWatcher(this)
}
}
run () {
if (this.active) {
const value = this.get()
if (
value !== this.value ||
// Deep watchers and watchers on Object/Arrays should fire even
// when the value is the same, because the value may
// have mutated.
isObject(value) ||
this.deep
) {
// set new value
const oldValue = this.value
this.value = value
if (this.user) {
try {
this.cb.call(this.vm, value, oldValue)
} catch (e) {
handleError(e, this.vm, `callback for watcher "${this.expression}"`)
}
} else {
this.cb.call(this.vm, value, oldValue)
}
}
}
}
}
当触发了依靠更新时刻,第一个watcher(关于total的)会将本身的dirty标记为true,第二个则会实行run要领,在个中运转this.get致使updateComponent实行,进而再次盘算vnode,这时会再次盘算this.total。则会再次触发total的getter,这时刻我们再温习一下之前讲过的这个computed的getter:
const watcher = this._computedWatchers && this._computedWatchers[key]
if (watcher) {
// watcher.dirty示意这个值是脏值,逾期了。所以须要从新盘算。
// new Watcher的时刻,这个total的watcher中,内部的dirty已被置为
// dirty = lazy = true;
// 那末这个值什么时刻会逾期,会脏呢。就是内部的依靠更新时刻,
// 比方我们的total依靠于this.a,this.b,当着两个值恣意一个变化时刻
// 我们的total就已脏了。须要依据最新的a,b盘算。
if (watcher.dirty) {
// 盘算watcher中的值,即value属性.
watcher.evaluate()
}
// 将依靠添加到watcher中。
if (Dep.target) {
watcher.depend()
}
// getter的效果就是返回getter中的值。
return watcher.value
}
至此,computed中total的更新流程也完毕了。
所以我们的第3个题目,vue官方文档的缓存盘算效果怎样明白?也就有了答案。也就是说盘算属性只要其依靠变动的时刻才会去盘算,依靠不更新的时刻,是不会盘算的。正文这一小节提到的,total的更新是因为this.a的更新致使其setter被触发,因而关照了其依靠,即total这个watcher。假如total的不依靠于this.a,则total相干的watcher的dirty就不会变成true,也就不会再次盘算了。
总结
本章节我们以示例顺序探讨了盘算属性,从initComputed
中,盘算属性的初始化到盘算属性的变动,对着代码做了进一步的诠释。团体流程能够归结为:
initComputed
定义了相干的盘算属性相干的watcher,以及watcher的getter。
在第一次盘算vnode的时刻趁便实行了盘算属性的盘算逻辑,趁便网络了依靠。本例中total网络到了依靠a,b;而且a,b也被示知total视察了他们。当a,b任何一个转变时的时刻,就会将total相干的watcher.dirty设置为true,下次须要更新界面时,盘算属性就会被从新盘算。固然,假如没有依靠于total的处所。那末total是不会盘算的,比方total基础没被界面或许js代码用到,就不会盘算total;假如total一切的依靠没有变动,其dirty为false,则也是无需盘算的。