前言
通过珠峰课程的学习来理解computed计算属性的原理
那首先呢,先回顾一下vue的响应式数据原理
Vue 在初始化数据时,会给 data 中的所有属性使用 Object.defineProperty 重新定义 setter 和 getter , 当页面获取到对应属性时,会触发 get 方法并进行依赖收集(收集当前组件的watcher) 如果属性发生变化会通知相关依赖进行更新操作 。
其实呢,它们本质上没有什么区别,在前章分享响应式数据原理的时候我们就略带过 computed 计算属性,好,接着往下看:
响应式数据我们知道有个初始化 data 的方法叫做 initData
, 那么计算属性当然也有自己的初始化叫做initComputed
, 我们走进源码:src/core/instance/state.js 169 行
function initComputed (vm: Component, computed: Object) { // 初始化计算属性
const watchers = vm._computedWatchers = Object.create(null) // 初始化 计算属性 watcher 列表
const isSSR = isServerRendering() // 判断是否是服务器渲染
for (const key in computed) {
const userDef = computed[key] //注意:获取用户定义的方法
// 就是计算属性 computed 里边定义的自变量函数 ↑
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) { // 非服务器渲染
watchers[key] = new Watcher( // 为每一个计算属性创建一个 watcher
vm,
getter || noop, // 将用户定义的方法传入
noop,
computedWatcherOptions // 注意:为计算属性options传入 { lazy:true }
)
}
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)
}
}
}
}
export function defineComputed ( // vm 初始化的实例 ↑
target: any,
key: string,
userDef: Object | Function
) {
const shouldCache = !isServerRendering()
if (typeof userDef === 'function') {
sharedPropertyDefinition.get = shouldCache
? createComputedGetter(key) // 创建计算属性的 getter 方法实体 ↓
: createGetterInvoker(userDef) // 调用计算属性的 getter 方法实体 ↓
sharedPropertyDefinition.set = noop
} else {
sharedPropertyDefinition.get = userDef.get
? shouldCache && userDef.cache !== false
? createComputedGetter(key) // 创建计算属性的 getter 方法实体 ↓
: createGetterInvoker(userDef.get) // 调用计算属性的 getter 方法实体 ↓
: 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
)
}
}
Object.defineProperty(target, key, sharedPropertyDefinition) // 响应式数据驱动更新
}
// 创建计算属性 getter 方法
function createComputedGetter (key) {
return function computedGetter () { // 取值的时候回调用此方法
const watcher = this._computedWatchers && this._computedWatchers[key]
if (watcher) {
if (watcher.dirty) { // 做了一个dirty 实现了缓存的机制 , 计算属性 调用对应的 evaluate ()
watcher.evaluate() // 如是计算属性,则调用 evaluate() ;
}
if (Dep.target) {
watcher.depend()
}
return watcher.value
}
}
}
// 调用计算属性的 getter 方法
function createGetterInvoker (fn) {
return function computedGetter () {
return fn.call(this, this)
}
}
总结
通过上边的代码我们可以了解到:initComputed
主要是为每一个计算属性创建一个watcher
并且在 options
里边传入 lazy:true
计算属性标识 , 然后再通过defineComputed
将计算属性定义在实例上去进行一个响应式数据观测 。
回顾
再次来到 watcher.js
中 src/observer/watcher.js 166行
,
update () { // 更新数据方法
/* istanbul ignore else */
if (this.lazy) { // 计算属性 依赖的数据发生变化了 会让计算属性的watcher的dirty变成true
this.dirty = true
} else if (this.sync) { // 同步watcher
this.run()
} else {
queueWatcher(this) // 将watcher放入队列
}
}
src/observer/watcher.js 212行
重点 , 重点 , 重点
evaluate () { // 计算属性时,被调用自身的 get 方法
this.value = this.get()
this.dirty = false
}