Appearance
十二、computed内部实现原理
文档地址
定义的位置
初始化位置 src\core\instance\state.js -> initState
initState -> initComputed
js
if (opts.computed) initComputed(vm, opts.computed)
initComputed
js
const computedWatcherOptions = { lazy: true }
function initComputed (vm: Component, computed: Object) {
// $flow-disable-line
// 定义了一个私有属性,里面存储的是一个键值对的形式,键是计算属性的名字,值就是计算属性对应的function
const watchers = vm._computedWatchers = Object.create(null)
// computed properties are just getters during SSR
// 判断当前环境是否是服务端渲染的环境
const isSSR = isServerRendering()
// 遍历用户定义的计算属性 computed:{ a: function () {}, b: { get: ..., set: ...}}
// 值可能是函数,也可能是对象
for (const key in computed) {
// 获取计算属性的值
const userDef = computed[key]
// 判断是不是function,如果不是就调用其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
)
}
// 如果不是服务端渲染,就会创建一个watcher对象,并且记录到刚才的vm._computedWatchers变量中
if (!isSSR) {
// create internal watcher for the computed property.
// 第一个参数是Vue实例,第二个参数是计算属性对应的function,第三个参数侦听器里面用到的,第四个参数是开始的时候不立即执行
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上没有当前计算属性的名字,则在vm上定义该计算属性,否则如果是开发环境发送警告
// 在初始化计算属性的时候,已经初始化了props,datas,methods,如果上面有key就已经发生了冲突
if (!(key in vm)) {
// 没有就执行这个函数,并把vue实例,key和值都传进去
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 这个函数的使用
defineComputed
这个函数作用是把计算属性定义到Vue实例上
js
// 把计算属性定义到vue的实例上
export function defineComputed (
target: any,
key: string,
userDef: Object | Function
) {
// 如果不是服务端渲染的环境,应该就是去缓存的
const shouldCache = !isServerRendering()
// 判断用户传入的是对象还是function,用于去设置当前属性的描述符,get和set的值
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
)
}
}
// 给vue实例增加计算属性的名字
Object.defineProperty(target, key, sharedPropertyDefinition)
}
这里面有一个核心函数可以看到是createComputedGetter
createComputedGetter
js
function createComputedGetter (key) {
return function computedGetter () {
// 获取该计算属性对应的watcher对象
const watcher = this._computedWatchers && this._computedWatchers[key]
if (watcher) {
// 这个位置起到缓存的作用
// 第一次访问计算属性的时候,dirty为true执行evaluate获取计算属性的值,并把dirty设为false,
/**
* evaluate () {
this.value = this.get()
this.dirty = false
}
*/
// 当再次访问计算属性,没有发生变化,dirty的值如果依然为false,不执行evaluate,直接返回watcher.value
// 当数据改变之后会调用 watcher 的 update 方法,把dirty改变为true,下次访问就会访问新的值
/**
* update () {
if (this.lazy) {
this.dirty = true
} else if (this.sync) {
this.run()
} else {
queueWatcher(this)
}
}
*/
if (watcher.dirty) {
watcher.evaluate()
}
if (Dep.target) {
watcher.depend()
}
return watcher.value
}
}
}
这我们可以看到watcher的构造函数中,对dirty和evaluate的执行.
传了lazy属性,才有dirty,没有lazy属性,就不会有dirty属性.dirty的作用是为了实现计算属性缓存的作用.
计算属性和侦听器都是通过watcher实现的,