小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
1. computed
的 getter
和 setter
在大多数情况下,计算属性(computed
)只需要一个 getter
方法即可,所以我们会将计算属性直接写成一个函数(表示的就是计算属性的 getter
方法,本质上就是对象的 get
属性对应的函数):
computed: {
// 计算属性 fullName 的 getter 方法
fullName: function() {
return this.firstName + ' ' + this.lastName;
}
}
上面的写法相当于下面这种写法的语法糖:
computed: {
// 计算属性 fullName 对应一个对象,对象中需要有 getter(get) 方法
fullName: {
get: function() {
return this.firstName + ' ' + this.lastName;
}
}
}
但是,如果我们还想设置计算属性的值呢?这时我们就可以给计算属性设置一个 setter
方法:
computed: {
// 计算属性 fullName 的完整写法
fullName: {
// 计算属性 fullName 的 getter 方法
get: function() {
return this.firstName + ' ' + this.lastName;
},
// 计算属性 fullName 的 setter 方法
set: function(newValue) {
console.log(newValue);
const newArr = newValue.split(' ');
if (newArr.length === 2) {
this.firstName = newArr[0];
this.lastName = newArr[1];
} else {
this.firstName = newValue;
this.lastName = '';
}
}
}
}
这样一来,一旦给计算属性 fullName
赋值,就会执行其中的 setter
(set
) 方法。而在 setter
方法中,因为对 data
选项中的 firstName
和 lastName
做了修改,所以计算属性 fullName
的 getter
方法又会被重新调用,从而返回最新的 fullName
值。
<body>
元素中的代码:
<div id="app"></div>
<template id="my-app">
<button @click="changeFullName">修改 fullName</button>
<h2>{{ fullName }}</h2>
</template>
<script src="./js/vue.js"></script>
<script>
const App = {
data() {
return {
firstName: 'Shane',
lastName: 'Filan',
}
},
computed: {
// 计算属性 fullName 的完整写法
fullName: {
// 计算属性 fullName 的 getter 方法
get: function() {
return this.firstName + ' ' + this.lastName;
},
// 计算属性 fullName 的 setter 方法
set: function(newValue) {
console.log(newValue);
const newArr = newValue.split(' ');
if (newArr.length === 2) {
this.firstName = newArr[0];
this.lastName = newArr[1];
} else {
this.firstName = newValue;
this.lastName = '';
}
}
}
},
methods: {
changeFullName() {
this.fullName = 'Coder Zhj';
}
},
template: '#my-app'
};
Vue.createApp(App).mount('#app');
</script>
页面效果:
2. 源码解析
你可能想知道,Vue
内部是如何对我们传入的是一个 getter
,还是一个包含 getter
和 setter
的对象进行处理的呢?
其实非常简单,Vue
源码内部只是做了一个逻辑判断而已,有关代码如下:
源码解析:
if (computedOptions) { // 如果有 computedOptions 即 computed 选项(属性)
for (const key in computedOptions) { // 遍历 computedOptions(computed)对象中的属性
const opt = (computedOptions as ComputedOptions)[key] // 通过计算属性名取出对应的计算属性值
const get = isFunction(opt) // 该计算属性是函数吗?
? opt.bind(publicThis, publicThis) // 是函数,将该函数中的 this 绑定为 publicThis(即实例中的代理对象),同时把 publicThis 传给该函数的第一个参数,然后把该函数赋值给 get(后面作为该计算属性的 getter 方法)
: isFunction(opt.get) // 不是函数,那应该是个对象,那该对象中的 get 属性值是函数吗?(对象中是否有 getter 方法?)
? opt.get.bind(publicThis, publicThis) // 对象中的 get 属性值是函数(对象中有 getter 方法),将该函数中的 this 绑定为 publicThis,同时把 publicThis 传给该函数的第一个参数,然后把该函数赋值给 get(后面作为该计算属性的 getter 方法)
: NOOP // 对象中的 get 属性值也不是函数(对象中没有 getter 方法),那么把一个空的函数实现赋值给 get
if (__DEV__ && get === NOOP) {
warn(`Computed property "${key}" has no getter.`)
}
const set =
!isFunction(opt) && isFunction(opt.set) // 如果该计算属性不是函数同时其中的 set 属性是函数
? opt.set.bind(publicThis) // 则将该函数中的 this 绑定为 publicThis,然后把该函数赋值给 set
: __DEV__ // 否则如果是开发环境
? () => {
warn(
`Write operation failed: computed property "${key}" is readonly.`
)
} // 则把会给出警告信息的函数赋值给 set
: NOOP // 不是开发环境,就把空函数赋值给 set
// 调用 computed 函数以实现计算属性具体的一些功能(响应式、缓存等),后面讲 Composition API 时,
// 我们也都是自己调用此函数来实现计算属性的,后面讲完响应式原理之后再来理解
const c = computed({
get,
set
})
Object.defineProperty(ctx, key, {
enumerable: true,
configurable: true,
get: () => c.value,
set: v => (c.value = v)
})
if (__DEV__) {
checkDuplicateProperties!(OptionTypes.COMPUTED, key)
}
}
}