计算属性
computed 也是属于组件状态的一部分,所以它初始化同样是在 initState 函数中完成的,initState 函数定义在 src/core/instance/state.js
export function initState (vm: Component) {
// 定义一个Watcher数组,即用于收集观察者(依赖收集)
vm._watchers = []
// 获取vm实例的各种配置
const opts = vm.$options
...
// 判断当前实例是否存在 computed 选项
if (opts.computed) initComputed(vm, opts.computed)
// 判断当前是否存在watch选项且这个watch不是Firefox的Object原型上的watch
// 火狐的watch原型:nativeWatcher = ({}).watch
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch)
}
}
从 initState 中我们可以看出 computed 选项的初始化是定义在 initComputed 函数中,initComputed 函数同样定义在 src/core/instance/state.js
initComputed
function initComputed (vm: Component, computed: Object) {
// 创建一个空对象给当前实例的_computedWatchers
const watchers = vm._computedWatchers = Object.create(null)
// 调用isServerRendering 判断是否当前是服务端渲染,该函数返回 true or false
const isSSR = isServerRendering()
// 遍历 computed 选项
for (const key in computed) {
const userDef = computed[key]
// 获取当前 computed 属性对应的 get 方法
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.
// 初始化一个 computed wacher
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.
// 将当前computed属性挂在到当前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
export function defineComputed (
target: any,
key: string,
userDef: Object | Function
) {
// 一般都不是服务端渲染所以 shouldCache 为 true
const shouldCache = !isServerRendering()
if (typeof userDef === 'function') { // computed属性对应的get是function
// sharedPropertyDefinition的get都是通过 createComputedGetter()方法实现
sharedPropertyDefinition.get = shouldCache
? createComputedGetter(key)
: createGetterInvoker(userDef)
sharedPropertyDefinition.set = noop
} else { // computed属性对应的set是对象
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) {
// 如果开发环境,没有设置set,则会 warn 提示没有setter
sharedPropertyDefinition.set = function () {
warn(
`Computed property "${key}" was assigned to but it has no setter.`,
this
)
}
}
// 给此时计算属性的key添加getter、setter以便用于之后的数据驱动
Object.defineProperty(target, key, sharedPropertyDefinition)
}
createComputedGetter
function createComputedGetter (key) {
// 手动创建一个getter函数
return function computedGetter () {
const watcher = this._computedWatchers && this._computedWatchers[key]
if (watcher) {
// 如果wathcer发生变化
if (watcher.dirty) {
// 重新估算wathcer(即重新做依赖收集)
watcher.evaluate()
}
// 如果此时存在订阅者
if (Dep.target) {
// 为watcher添加依赖
watcher.depend()
}
// 返回watcher的值
return watcher.value
}
}
}
总结
在开发中需要注意的是,computed 是依赖于其他,但是并不是代表依赖发生变化,组件就一定会更新,而是应该是重新计算后的值与计算前的值不同时,才会触发组件的重新渲染。