# 一、计算属性
计算属性是指通过一系列运算之后,最终得到的一个特殊的属性值。
# 1、代码体验
<div id="app">
<input type="text" v-model="msg" />
<p>{{ reverseMsg }}</p>
</div>
<script>
var vm = new Vue({
el: '#app',
data: {
msg: ''
},
computed: {
// 计算属性的 getter
reverseMsg: function () {
// `this` 指向 vm 实例
return this.msg.split('').reverse().join('')
}
}
})
</script>
# 2、与方法的区别 ✨
与使用方法的区别
调用方法的话,无论什么情况都会执行函数。
<script>
var vm = new Vue({
methods: {
reverseMsg: function () {
console.log('执行了一次')
return this.msg.split('').reverse().join('')
}
}
})
</script>
而计算属性是可以基于它们的响应式依赖进行缓存的,即如果 msg
没有发生改变,使用 reverseMsg
时返回的是之前的计算结果,而没有再次执行函数。
<div id="app">
<button @click="updateTime">更新</button>
<p>{{ now }}</p>
</div>
<script>
var vm = new Vue({
el: '#app',
data: {
now: Date.now()
},
// Date.now() 不是响应式依赖,计算属性将不再更新
methods: {
// 这时得使用方法
updateTime() {
this.now = Date.now()
}
}
})
</script>
# 3、与监听器的区别 ✨
有时候,我们使用计算属性要比使用监听器简洁的多:
原因
通过下面的案例,我们发现:watch 监听的是已经定义的数据,而 computed 本身就可以作为数据被调用。
<div id="app">
<p>{{ fullName }}</p>
</div>
<script>
var vm = new Vue({
el: '#app',
data: {
firstName: 'Foo',
lastName: 'Bar'
// fullName: 'Foo Bar'
},
computed: {
fullName: function () {
return this.firstName + ' ' + this.lastName
}
}
// watch: {
// firstName: function (val) {
// this.fullName = val + ' ' + this.lastName
// },
// lastName: function (val) {
// this.fullName = this.firstName + ' ' + val
// }
// }
})
</script>
RGB 颜色监听案例
<div id="app">
<div>
<span>R:</span>
<input type="text" v-model.number="r" />
</div>
<div>
<span>G:</span>
<input type="text" v-model.number="g" />
</div>
<div>
<span>B:</span>
<input type="text" v-model.number="b" />
</div>
<hr />
<!-- 使用计算属性 -->
<div class="box" :style="{backgroundColor: rgb}"></div>
</div>
<script>
new Vue({
el: '#app',
data: {
r: 0,
g: 0,
b: 0
},
computed: {
rgb() {
return `rgb(${this.r}, ${this.g}, ${this.b})`
}
}
})
</script>
# 4、getter / setter
注意
原理:底层借助了 Object.defineproperty 方法提供的 getter 和 setter
data 和 计算属性 ✍
通过在控制台打印,我们可以观察它们的位置:
vm
vm.firstName
vm.firstName
vm.fullName # Foo Bar
vm._data.firstName
vm._data.firstName
vm._data.fullName # undefined
书写方式也很类似:
<script>
var vm = new Vue({
data: {
msg: ''
}
computed: {
// 简写(只有get)
reverseMsg: function() {}
// reverseMsg() {}
// 完整写法
reverseMsg: {
get() {},
set(newValue) {}
}
}
})
</script>
前面我们使用计算属性的写法是简写形式(只读不改),下面我们加入 getter、setter 观察一些细节:
<div id="app">
<!-- 调用get -->
<input type="text" v-model="msg" />
<p>{{ reverseMsg }}</p>
<p>{{ reverseMsg }}</p>
<p>{{ reverseMsg }}</p>
<!-- 调用set -->
<button @click="reverseMsg = 'neR'">修改计算属性值</button>
</div>
<script>
var vm = new Vue({
el: '#app',
data: {
msg: 'lencamo'
},
computed: {
reverseMsg: {
// 1、get调用时机
// 初次读取reverseMsg时
// 响应式依赖的数据msg变化时
get() {
// 细节:get()方法只调用了👀一次
console.log('get函数被调用')
return this.msg.split('').reverse().join('')
},
// 2、set调用时机
// reverseMsg被修改时
set(newValue) {
console.log('计算属性被修改')
// 作用:将修改更新到data中响应式依赖
this.msg = newValue.split('').reverse().join('')
}
}
}
})
</script>
# 二、监听器
提示
我们用 computed 能实现的功能,watch 都能实现 🤔。
监听器监听的是数据的变化,这就意味着 watch 不仅可以监听 data 数据,还可以监听计算属性。
# 1、代码体验
<div id="app">
<input type="text" v-model="username" />
</div>
<script src="./lib/vue-2.6.14.js"></script>
<script>
const vm = new Vue({
el: '#app',
data: {
username: 'lencamo'
},
watch: {
// 简写(注意参数的顺序🚩)
username(newVal, oldVal) {
console.log('值' + oldVal + '发生了改变!!\n' + '新值为:' + newVal)
}
// username: function (newVal, oldVal) {
// console.log('值' + oldVal + '发生了改变!!\n' + '新值为:' + newVal)
// },
// 完整的写法(和前面的computed类似)
// username: {
// handler(newVal, oldVal) {
// console.log('值' + oldVal + '发生了改变!!\n' + '新值为:' + newVal)
// }
// }
}
})
</script>
进一步的,我们还可以这样写:
vm.$watch('username', {
handler(newVal, oldVal) {
console.log('值' + oldVal + '发生了改变!!\n' + '新值为:' + newVal)
}
})
思考?
监听是的变化频率:防抖 与 节流
# 2、应用场景 ✨
当需要在数据变化时执行异步或开销较大的操作时,采用 watch 是最有用的。
- 定时器
const vm = new Vue({
watch: {
firstName(newVal) {
setTimeout(() => {
console.log(this) // vm
this.fullName = val + ' ' + this.lastName
}, 1000)
}
}
})
this 指向
为了让 this 指向 vm 或者组件的实例对象,有如下建议:
- 被 vue 管理的函数,写成普通函数
- 不被 vue 管理的函数,写成箭头函数
- 用户名占用
<script>
const vm = new Vue({
el: '#app',
data: {
username: 'admin'
},
watch: {
async username(newVal, oldVal) {
if (newVal === '') return
// 使用axios发起ajax请求,判断username是否被占用
const { data: res } = await axios({
method: 'GET',
url: 'https://www.escook.cn/api/finduser/' + newVal
})
console.log(res)
}
}
})
</script>
# 3、immediate 选项
默认情况下,组件在初次加载完毕时不会调用 watch 监听器(即上面的案例中 首次是不会监听用户名是否被占用的)。
而在使用计算属性则不同,计算属性本身就会在:初次读取 computed 计算属性时就被调用。
immediate 选项的存在,间接证明了使用计算属性的优越性。
<div id="app">
<input type="text" v-model="keyWord" /><br />
<ul>
<li v-for="item in listData" :key="item.id">{{item.music}}</li>
</ul>
</div>
<script>
new Vue({
el: '#app',
data: {
keyWord: '',
data: [
{ id: 1, music: '十年--陈奕迅' },
{ id: 2, music: '分手快乐--梁静茹' },
{ id: 3, music: '十一年--邱永传' },
{ id: 4, music: '阴天快乐--陈奕迅' }
]
// listData: [] // 专门定义供watch监听使用
},
// 计算属性方式
computed: {
listData() {
return this.data.filter((item) => {
return item.music.includes(this.keyWord)
})
}
}
// 监听器方式
// watch: {
// keyWord: {
// handler(newVal) {
// this.listData = this.data.filter((item) => {
// return item.music.includes(newVal)
// })
// },
// immediate: true // // 初次加载时就调用watch监听器
// }
// }
})
</script>
# 4、deep 选项
当 watch 监听的是一个对象时,当对象的属性值发生了变化,是无法被监听到的。
// 监听对象属性值变化
const vm = new Vue({
el: '#app',
data: {
brand: {
id: 1,
brandName: 'lisi',
status: true,
companyName: '京东'
}
},
watch: {
brand: {
handler: async function (newVal) {
// ……
console.log(newVal.brandName)
console.log(newVal.status)
},
deep: true
}
}
})
当然如果不使用 deep 选项,是完全可以直接监听单个对象属性值变化的。
// 监听单个对象属性值变化
watch: {
'brand.brandName': {
handler: async function (newVal) {
……
console.log(newVal)
}
}
}
# 5、$watch
const app = Vue.createApp({
created() {
// ajax/fetch/axios
console.log('created')
this.$watch(
'message',
(newValue, oldValue) => {
//
},
{ deep: true, imediate: true }
)
}
})
# 三、filters 过滤器 *
上面进行假删除时使用了 filter,但要注意的是 vue3.0 已经弃用了 filters。Vue.js 允许你自定义过滤器,它常用的场景(对文本格式化):
- 双花括号插值中
- v-bind 表达式中
<div id="app1">
<!-- 在双花括号中 -->
{{ message | capitalize }}
<!-- 在 `v-bind` 中 -->
<div v-bind:id="idtext | maxLength(12)">div盒子id值长度测试</div>
</div>
<div id="app2">
<!-- 在 `v-bind` 中 -->
<div v-bind:id="idtext | maxLength(12)">div盒子id值长度测试</div>
</div>
<script src="./lib/vue-2.6.14.js"></script>
<script>
// 1、全局过滤器🤔
// 注意观察参数
Vue.filter('maxLength', function (value, len) {
if (value.length <= len) {
return value
}
return value.substr(0, len)
})
const vm1 = new Vue({
// DOM Listeners处理
el: '#app1',
// Data Bindings处理
data: {
message: 'zhangsan',
idtext: 'head-for-table-first-cell'
},
// 2、本地过滤器
filters: {
capitalize: function (value) {
if (!value) return ''
value = value.toString()
return value.charAt(0).toUpperCase() + value.slice(1)
}
}
})
const vm2 = new Vue({
// DOM Listeners处理
el: '#app2',
// Data Bindings处理
data: {
idtext: 'head-for-table-first-cell'
}
})
</script>