计算属性、监听器和过滤器
1. 计算属性computed
1-1 计算属性
计算属性 computed
选项在 options
中定义,计算属性会在当其依赖属性的值发生变化时,属性的值会自动更新,与之相关的 DOM
元素也会同步更新
依赖属性的值指的是 data
中定义的数据
计算属性本质是一个方法,只不过在使用计算属性时,把计算属性的名称直接作为属性来使用,并不会把计算属性作为一个方法去调用
{ [key: string]: Function | { get: Function, set: Function } }
**Tips:**计算属性的调用不能使用括号
<div id="app">
<input type="text" v-model="title">
<!-- 计算属性调用时不使用括号() -->
<h3>{{getTitle}}</h3>
</div>
<script src="../vue.js"></script>
<script>
const vm = new Vue({
el: '#app',
data: function() {
return {
// 计算属性响应式 当data数据改变时计算属性则会改变
title: 'Hello Computed'
}
// 计算属性通过computed选项定义
},
computed: {
// 两种形式的值
// 1. {key: function} key为字符串
// 2. {get: function, set: function}
getTitle() {
// 初始化执行
console.log('computed 执行')
return this.title
}
}
})
</script>
1-2 计算属性缓存vs方法
- 计算属性是基于它的响应式依赖进行缓存的。只有在计算属性的相关响应式依赖发生改变时才会重新求值。这就意味着只要
data
里的相应依赖的数据没有发生改变,那个多次访问计算属性里相关的函数会立即返回之前的计算结果 - 定义在
methods
中的方法,每当触发重新渲染时,无论属性是否发生改变,调用方法将总会再次执行函数
计算属性相比较于方法更加优化,并不是所有情况下都使用计算属性,在触发事件时选择对应的方法比较好
计算属性一般在数据量比较大、比较耗时、性能开销比较大的情况下使用,只有在虚拟 DOM
和真实 DOM
不同的情况下才会执行
<div id="app">
<h3>{{getValue}}</h3>
<button type="button" @click="getTitle">修改title</button>
</div>
<script src="../vue.js"></script>
<script>
const vm = new Vue({
el: '#app',
data: function() {
return {
title: 'Hello Computed'
}
},
computed: {
getValue() {
// 初始加载时执行一次
// 当依赖项data中数据发生改变时执行
console.log('执行 getValue')
return this.title;
}
},
methods: {
getTitle() {
// 每次调用方法都会执行
console.log('执行 getTitle')
this.title = 'Hello Vue'
}
}
})
</script>
1-3 计算属性的get与set
每个计算属性都会有 getter
与 setter
用来获取属性值和设置属性值,在 Vue
中计算属性默认只有 getter
,不过在需要时你也可以提供一个 setter
所有 getter
和 setter
的 this
上下文自动地绑定为 Vue
实例
get
—— 初次读取时会执行一次,访问依赖中的数据时会执行,当依赖的数据发生改变时会被再次执行set
—— 用于对get
中使用到的数据进行操作,计算属性值得修改只能通过set
方法- 通过改变依赖项的数据来响应修改计算属性的值
- 通过该方法去设置依赖项
data
中的数据然后计算属性响应更新get
中返回的计算属性值
<div id="app">
<div>first:<input type="text" v-model="first"></div>
<div>last:<input type="text" v-model="last"></div>
<h3>{{getValue}}</h3>
计算属性:<input type="text" v-model="getValue">
</div>
<script src="../vue.js"></script>
<script>
const vm = new Vue({
el: '#app',
data: function() {
return {
first: 'hello',
last: 'computed'
}
},
// 第二种定义计算属性方式
// 通过get与set
computed: {
getValue: {
// 用于返回计算属性的值
get() {
// get与set中this指向Vue实例对象
return this.first + ' ' + this.last
},
// 用于设置计算属性的值
set(value) {
// set可以设置get中使用的数据
// 如果想直接修改计算属性的值必须通过set函数去修改
console.log(value)
// 也可以通过set方法去设置依赖项data中的数据
let str = value.split(' ')
this.first = str[0]
this.last = str[1]
}
}
},
})
</script>
2. 监听器watch
2-1 监听器与实例属性
监听器通过 watch
选项在 options
中定义用来监听某个数据发生的改变,当需要在数据变化时执行异步或开销较大的操作时推荐使用
监听器以 key/value
形式定义,key
为字符串类型表示被监听的属性名(被监听的对象),value
可以是字符串(方法名称)、函数(可以获取到监听对象改变前的值以及更新后的值)、对象(包含回调函数以及其它 options
选项 —— 初始化监听、深度遍历)、数组
{ [key: string]: string | Function | Object | Array }
<div id="app">
<div>string:<input type="text" v-model="str"></div>
<div>function:<input type="text" v-model="func"></div>
<div>object:<input type="text" v-model="obj"></div>
<div>array:<input type="text" v-model="arr"></div>
</div>
<script src="../vue.js"></script>
<script>
const vm = new Vue({
el: '#app',
data: function () {
return {
str: 'Hello Watch',
func: 'Hello Watch',
obj: 'Hello Watch',
arr: 'Hello Watch'
}
},
// 在options选项中定义watch选项
watch: {
// watch用来监听属性的变化
// 属性为字符串类型,值可以是字符串,函数,对象,数组
// 字符串类型 字符串为方法名
str: 'changeEvent',
// function类型 参数1新值 参数2旧值
func: function (newValue, oldValue) {
console.log('新的值 -->' + newValue, '旧的值 -->' + oldValue)
},
// object对象类型 options选项回调函数handler, 立即执行immediate,深度监听deep
obj: {
handler(newValue, oldValue) {
console.log('新的值 -->' + newValue, '旧的值 -->' + oldValue)
},
immediate: true,
deep: true
},
// 数组依次调用
arr: [
// 方法名
'changeEvent',
// 函数
function handler(newValue, oldValue) {
console.log('新的值 -->' + newValue, '旧的值 -->' + oldValue)
},
// 对象
{
handler(newValue, oldValue) {
console.log('新的值 -->' + newValue, '旧的值 -->' + oldValue)
}
}
]
},
methods: {
// 当title属性值改变时触发
changeEvent() {
console.log(this.str)
}
}
})
</script>
2-2 监听器实例
监听器也可以通过实例属性 wm.$watch
来定义,参数属性与 watch
选项中一致
当监听的属性不存在时,不会报错 ,回调函数首次执行返回的值为 undefined
vm.$watch( expOrFn {string | Function}, callback {Function | Object}, [options] {Object} )
expOrFn
—— 参数是一个监听属性,可以是字符串类型或者函数的返回值callback
—— 回调函数,当数据发生变化时执行回调,可以是函数也可以是对象options
—— 配置项对象类型,用来设置选项是否初始化监听,是否深度监听
<div id="app">
<input type="text" v-model="title">
</div>
<script src="../vue.js"></script>
<script>
const vm = new Vue({
el: '#app',
data: function() {
return {
title: '监听器实例'
}
}
})
// 通过vm.$watch实例来监听属性变化
vm.$watch('title', function(newValue, oldValue){
console.log('新的值 -->' + newValue, '旧的值 -->' + oldValue)
})
// 当监听的属性不存在时,不会报错回调函数参数返回undefined
vm.$watch('name', function(newValue, oldValue){
console.log('新的值 -->' + newValue, '旧的值 -->' + oldValue)
}, {immediate: true})
</script>
2-3 深度监听与初始化监听
Vue
中 watch
选项默认不监听对象嵌套深层的属性,只能监听简单数据类型变化以及最外层对象属性
也可以在 watch
中定义属性时将对象深层的属性设置为被监听的属性,watch
中属性值必须为字符串类型
deep
选项 —— 为了发现对象内部值的变化,可以在选项参数中指定deep: true
,深度监听immediate
选项 —— 在选项参数中指定immediate: true
将立即以表达式的当前值触发回调,初始化监听
<div id="app">
<div>
简单类型:<input type="text" v-model="title">
<h3>{{title}}</h3>
</div>
<div>对象嵌套:<input type="text" v-model="obj.lesson.name">
<h3>{{obj.lesson.name}}</h3>
</div>
<div>数组嵌套:<input type="text" v-model="arr[1][0]">
<h3>{{arr[1][0]}}</h3>
</div>
</div>
<script src="../vue.js"></script>
<script>
const vm = new Vue({
el: '#app',
data: function () {
return {
title: 'Hello Watch',
obj: {
lesson: {
name: 'vue'
}
},
arr: ['html', ['css', ['js']]]
}
},
watch: {
title: function (val, old) {
console.log('执行')
},
// 对象中的深层属性不能监听
obj: {
handler(val, old) {
console.log(val, old)
},
// 使用deep 深度监听
deep: true,
// immediate 初始化监听
immediate: true
},
// 注意属性必须为字符串
// 可以通过将对象深层属性作为watch监听属性
'obj.lesson.name': function (val, old) {
console.log(val, old)
},
arr: function (val, old) {
console.log(val, old)
}
}
})
</script>
2-4 监听器vs计算属性
-
对于
computed
:computed
默认第一次加载的时候就开始监听
- 它支持缓存,只有依赖的数据发生了变化,才会重新计算
- 不支持异步,当
computed
中有异步操作时,无法监听数据的变化 computed
的值会默认走缓存,计算属性是基于它们的响应式依赖进行缓存的,也就是基于data
声明过,或者父组件传递过来的props
中的数据进行计算的- 如果一个属性是由其他属性计算而来的,这个属性依赖其他的属性,一般会使用
computed
- 如果
computed
属性的属性值是函数,那么默认使用get
方法,函数的返回值就是属性的属性值;在computed
中,属性有一个get
方法和一个set
方法,当数据发生变化时,会调用set
方法
-
对于
Watch
:- 它不支持缓存,数据变化时,它就会触发相应的操作
- 支持异步监听
- 监听的函数接收两个参数,第一个参数是最新的值,第二个是变化之前的值
- 当一个属性发生变化时,就需要执行相应的操作
- 监听数据必须是
data
中声明的或者父组件传递过来的props
中的数据,当发生变化时,会触发其他操作,函数有两个的参数:immediate
:组件加载立即触发回调函数deep
:深度监听,发现数据内部的变化,在复杂数据类型中使用,例如数组中的对象发生变化。需要注意的是,deep
无法监听到数组和对象内部的变化
-
总结:
computed
计算属性 : 依赖其它属性值,并且computed
的值有缓存,只有它依赖的属性值发生改变,下一次获取computed
的值时才会重新计算computed
的值watch
侦听器 : 更多的是观察的作用,无缓存性,类似于某些数据的监听回调,每当监听的数据变化时都会执行回调进行后续操作
-
使用场景:
- 当需要进行数值计算,并且依赖于其它数据时,应该使用
computed
,因为可以利用computed
的缓存特性,避免每次获取值时都要重新计算 - 当需要在数据变化时执行异步或开销较大的操作时,应该使用
watch
,使用watch
选项允许执行异步操作 ( 访问一个API
),限制执行该操作的频率,并在得到最终结果前,设置中间状态,这些都是计算属性无法做到的
- 当需要进行数值计算,并且依赖于其它数据时,应该使用
<div id="app">
<input type="text" v-model="getTitle">
<h3>{{getTitle}}</h3>
<input type="text" v-model="message">
<h3>{{message}}</h3>
</div>
<script src="../vue.js"></script>
<script>
const vm = new Vue({
el: '#app',
data: function() {
return {
title: 'Hello Vue',
message: 'Hello World'
}
},
// 1. computed 用于属性的计算
// 2. computed 属性值会缓存
// 3. computed 需要return返回值
// 4. computed 初始加载监听
// 5. computed 不支持异步
// 6. computed 产生新属性
// 7. computed 属性值是一函数
// 8. computed 一对多情况下使用
computed: {
getTitle: {
get() {
return this.title;
},
set(value) {
this.title = value;
}
}
},
// 1. watch 用于监听属性值得变化
// 2. watch 不会缓存值变化时回调
// 3. watch 不是必须使用return
// 4. watch 初始加载监听需要使用immediate属性
// 5. watch 支持异步
// 6. watch 不产生新属性
// 7. watch 属性值可以是字符串,对象,数组,函数
// 8. watch 一对多情况下使用
watch: {
message: {
handler(newVal, oldVal) {
this.message = newVal
},
deep: true,
immediate: true
}
}
})
</script>
3. 过滤器filters
3-1 定义过滤器
过滤器可对数据进行筛选、过滤、格式化,与 computed
、watch
、methods
不同,它不会改变原始值
通过 Mustache
语法插值或者 v-bind
表达式进行绑定,过滤器通常添加在表达式管道符号 |
后面 {{message | filter}}
- 可以在组件的选项
options
中定义filter
过滤器,局部定义 - 在创建
Vue
实例之前定义过滤器,全局定义
**Tips:**当全局过滤器和局部过滤器重名时,会采用局部过滤器
<div id="app">
<h3>{{getComputedTime}}</h3>
<h3>{{getMethodsTime()}}</h3>
<!-- 插值语法绑定 -->
<h3>{{time | getTime}}</h3>
<!-- v-bind指令绑定 -->
<h3 v-bind:id="title | myFilter">v-bind指令绑定</h3>
</div>
<script src="../vue.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/dayjs/1.11.2/dayjs.min.js"></script>
<script>
// 全局定义过滤器
Vue.filter('myFilter', function (value) {
return value.slice(4, value.length).toLowerCase()
})
const vm = new Vue({
el: '#app',
data: function () {
return {
time: 1652496467882,
title: 'headTitle'
}
},
// 通过computed实现过滤器
computed: {
getComputedTime() {
return dayjs(this.time).format('YYYY年MM月DD日')
}
},
// 通过methods实现过滤器
methods: {
getMethodsTime() {
return dayjs(this.time).format('YYYY年MM月DD日')
}
},
// 通过过滤器定义
filters: {
getTime(value) {
// 过滤器的值作为过滤器函数的参数
return dayjs(value).format('YYYY年MM月DD日')
}
}
})
</script>
3-2 过滤器使用方法
- 过滤器函数接收表达式的值作为第一个参数。
getTime
过滤器函数将会收到time
的值作为第一个参数 - 过滤器函数可以接受多个参数,第一个参数为表达式的值,后面的参数则是过滤器函数自定义参数。
getTime
被定义为接收二个参数的过滤器函数。其中time
的值作为第一个参数,普通字符串format
作为第二个参数 - 过滤器可以串联使用。
getHead
被定义为接收单个参数的过滤器函数,表达式title
的值将作为参数传入到函数中。然后继续调用同样被定义为接收单个参数的过滤器函数getMain
,将getHead
的结果传递到getMain
中
<div id="app">
<h3>{{time | getTime('YYYY年MM月DD日')}}</h3>
<!-- 过滤器串联使用 -->
<!-- 将getHead氯气函数得到的值作为getMain过滤器函数的参数 -->
<h3>{{title | getHead | getMain}}</h3>
</div>
<script src="../vue.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/dayjs/1.11.2/dayjs.min.js"></script>
<script>
const vm = new Vue({
el: '#app',
data: function() {
return {
time: 1652574881705,
title: '我是过滤器 嘻嘻'
}
},
filters: {
getTime(value, format) {
// 表达式的值为第一个参数
console.log(value)
// 第二个参数为过滤器函的自定义参数
return dayjs(value).format(format)
},
getHead(value) {
return value.slice(0, 5)
},
getMain(value) {
return value.slice(2, value.length)
}
}
})
</script>