开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第6天,点击查看活动详情
在Vue应用场景中经常会有这些应用场景:某个数据变量和另外一个或者多个变量有关,而且另外的变量什么时候发生改变我们不知道。或者当某个数据变量改变的时候我们需要某些操作,至于这些变量什么时候改变我们也不知道。对于这些不确定什么时间改变的场景,我们就可以使用计算属性或者监听属性。这篇文章我们主要介绍计算属性
计算属性
我们一般是在模板中放置的都是一些基础变量或者一些简单的运算,但是对于一些复杂的表达式是不建议写在模板中的,比如这个变量依赖于别的变量,这时候如果将所有的逻辑都放在模板里面就不利于维护也不利于复用,这时候就可以使用计算属性了。简单地说计算属性就是要用的属性不存在,需要通过已有的属性计算得来的。
基本用法
<template>
<div class="hello">
<input type="text" v-model="firstName" />
<input type="text" v-model="lastName" />
<span>{{fullName}}</span>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
data () {
return {
firstName: '',
lastName: ''
}
},
computed: {
fullName: {
get: function () {
console.log('你正在获取fullName的值')
return this.firstName + '-' + this.lastName
},
set: function (value) {
let arr = value.split('-')
this.firstName = arr[0]
this.lastName = arr[1]
}
}
},
methods: {
}
}
</script>
从上面的例子中可以看出,get函数在初次读取时会执行一次。当依赖的数据发生改变的时候就会被再次调用。
计算属性最终也会出现在vm上,直接读取使用就可以。如果计算属性需要被修改,就需要定义一个set函数去响应修改
缓存
还是上面的例子,如果我们在模板中多次使用到fullName,那么会发生什么呢?
<template>
<div class="hello">
<input type="text" v-model="firstName" />
<input type="text" v-model="lastName" />
<p>{{fullName}}</p>
<p>{{fullName}}</p>
<p>{{fullName}}</p>
<p>{{getFullName()}}</p>
<p>{{getFullName()}}</p>
<p>{{getFullName()}}</p>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
data () {
return {
firstName: '',
lastName: ''
}
},
computed: {
fullName: {
get: function () {
console.log('你正在获取fullName的值')
return this.firstName + '-' + this.lastName
},
set: function (value) {
let arr = value.split('-')
this.firstName = arr[0]
this.lastName = arr[1]
}
}
},
methods: {
getFullName () {
console.log('你在使用method得到fullName')
return this.firstName + '-' + this.lastName
}
}
}
</script>
从上面的例子可以看出,不论你使用了多少次fullName,计算属性都只会执行一次,而methods里定义的方法是会执行多次的,这样就说明计算属性是会有缓存的,它是基于它们的响应式依赖进行缓存的。如果所依赖的属性没有改变,那么就不会触发计算属性中get函数的调用。
原理
从我们对计算属性的get和set方法就能看出这个和Object.defineProperty()很像,对,确实是这样,计算属性的底层原理就是借助了Object.defineProperty()方法提供的getter和setter。下面我们来看一下源码
function initComputed (vm: Component, computed: Object) {
const watchers = vm._computedWatchers = Object.create(null)
// computed properties are just getters during SSR
const isSSR = isServerRendering()
for (const key in computed) {
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.
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') {
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)
}
}
}
}
这段代码是用来初始化关于计算属性的配置项的,核心代码就是其中遍历的部分,这块主要做了三件事:
- 为computed[key]创建watcher实例,默认是懒执行(new Watcher)
- 代理computed[key]到vm实例,也就是说可以在实例上获取计算属性(defineComputed)
- 判断computed中的key有没有和data, prop中的属性重复,如果重复的话就打印提示信息
export function defineComputed (
target: any,
key: string,
userDef: Object | Function
) {
const shouldCache = !isServerRendering()
if (typeof userDef === 'function') {
sharedPropertyDefinition.get = shouldCache
? createComputedGetter(key)
: userDef
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)
}
这段代码是代理computed的key到vm,主要核心就是使用到了Object.defineProperty()方法。
简写形式
如果计算属性中不需要用到set函数,那么就可以使用简写形式。
// 完整写法
computed: {
fullName: {
get: function () {
console.log('你正在获取fullName的值')
return this.firstName + '-' + this.lastName
}
}
}
// 简写形式
computed: {
fullName () {
console.log('你正在获取fullName的值')
return this.firstName + '-' + this.lastName
}
}