先对比一段代码:
<template>
<div>{{reversedMessage()}}</div>
</template>
<script>
export default {
data () {
return {
message: 'NoCrossNoCrown'
}
},
methods: {
reversedMessage: function () {
return this.message.split('').reverse().join('') // 'nworCoNssorCoN'
}
}
}
</script>
这里是通过方法来获取反转字符串,在vue中,我们通过计算属性computed也可以得到同样的效果。
<template>
<div>{{reversedMessage}}</div>
</template>
<script>
export default {
data () {
return {
message: 'NoCrossNoCrown'
}
},
computed: {
reversedMessage () {
return this.message.split('').reverse().join('')
}
}
}
</script>
无论是使用方法来获取还是computed,都能实现相同的效果。不同的是,计算属性会基于它们的响应式进行缓存,也就是说,message变量如果不发生变化,则不会重现计算,直接取缓存数据。但是方法没有这种机制,都会执行。二者取一的话,优先使用计算属性。
那现在要看下计算属性computed在vue源码中是如何实现的。
计算属性变量是基于它内部响应式属性来计算,第一点肯定是要监听内部属性再计算,其次会进行缓存。
我们先找到computed api实现的文件:vue/src/core/instance/state.js
export function initState (vm: Component) {
vm._watchers = []
const opts = vm.$options
if (opts.props) initProps(vm, opts.props)
if (opts.methods) initMethods(vm, opts.methods)
if (opts.data) {
initData(vm)
} else {
observe(vm._data = {}, true /* asRootData */)
}
// 初始化computed
if (opts.computed) initComputed(vm, opts.computed)
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch)
}
}
初始化computed的方法initComputed()也在当前state.js文件中。
function initComputed (vm: Component, computed: Object) {
const watchers = vm._computedWatchers = Object.create(null)
// computed properties are just getters during SSR
const isSSR = isServerRendering()
// 遍历computed对象的属性
for (const key in computed) {
const userDef = computed[key]
// getter是一个函数
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) {
// 为computed属性创建内部监听
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.
if (!(key in vm)) {
defineComputed(vm, key, userDef)
} else if (process.env.NODE_ENV !== 'production') {
// computed属性不能被data、prop或methods重复定义
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)
} else if (vm.$options.methods && key in vm.$options.methods) {
warn(`The computed property "${key}" is already defined as a method.`, vm)
}
}
}
}
initComputed方法主要处理了将computed属性进行了内部监听,这样computed值将被缓存下来了,而对于computed属性值中可能存在的多个响应式变量,对它们进行监听在defineComputed函数中,如下:
export function defineComputed (
target: any,
key: string,
userDef: Object | Function
) {
// 非服务端渲染则被缓存
const shouldCache = !isServerRendering()
// 一般computed属性值我们都是定义成函数,这样主要就执行createComputedGetter
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
)
}
}
Object.defineProperty(target, key, sharedPropertyDefinition)
}
function createComputedGetter (key) {
return function computedGetter () {
const watcher = this._computedWatchers && this._computedWatchers[key]
if (watcher) {
if (watcher.dirty) {
watcher.evaluate()
}
if (Dep.target) {
watcher.depend()
}
return watcher.value
}
}
}
createComputedGetter函数中watcher.depend()会收集computed属性值中所有响应式数据,当它们发生变化时会触发computed属性值的变化。
说明: 以下基于Vue2.6版本。