vue源码分析系列之响应式数据(三)

49次阅读

共计 4207 个字符,预计需要花费 11 分钟才能阅读完成。

前言
上一节着重讲述了 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)
也就是如下的内容:
coding…

正文完
 0