关于 computed 和 watch 的差异:
1.computed 是计算一个新的属性 默认初始化会执行一次,并将该属性挂载到 vm(Vue 实例)上,而 watch 是监听已经存在且已挂载到 vm 上的数据 默认需配置才会执行,所以用 watch 同样可以监听 computed 计算属性的变化(其它还有 data、props)
2.computed 本质是一个惰性求值的观察者,具有缓存性,只有当依赖变化后,才会计算新的值,computed 第一次加载就监听,而 watch 则是当数据发生变化便会调用执行函数
3.从使用场景上说,computed 适用一个数据被多个数据影响,而 watch 适用一个数据影响多个数据
一.计算属性(computed)
应用场景:在一个计算属性里可以完成各种复杂的逻辑,包括运算、函数调用等,只要最终返回一个结果就可以。
<template>
<div class="box">
<h1>计算属性</h1>
<hr>
<!-- 直接渲染得到数据 -->
<p><input type="text" v-model="message"></p>
<!-- 对data的数据进行简单的操作
这样有一个不好的地方就是后期对页面数据的维护不是那么方便 -->
<p>{{message.toUpperCase()}}</p>
<!-- 通过计算属性改变mesage进行渲染 -->
<p>{{computedMessage}}</p>
<hr>
<!-- 通过事件来改变属性 -->
<span>{{message}}</span><button @click="changeMessage()">点击改变</button>
<hr>
</div>
</template>
<script>
export default {
data() {
return {
message: 'Hello world!',
passw2: 'sss',
}
},
// computed属性是data返回值,在使用的时候只使用函数名不是调用函数
computed:{//计算属性相当于data里的属性
//什么时候执行:初始化显示/相关的data属性发生变化
computedMessage(){//计算属性中的get方法,方法的返回值就是属性值
return this.message.split('')
},
// computed属性get和set的使用
getAndsetMsg: {
get() {//回调函数 当需要读取当前属性值时执行,根据相关数据依赖计算并返回新的属性值
return this.message
},
set(val) {//计算属性一般是没有set方法, 只读属性.
//监视当前属性值的变化,当属性值发生变化时执行,更新相关的属性数据
//val就是getAndsetMsg的最新属性值
this.message = val+ 'muzidigbig'
}
}
},
methods:{
changeMessage(){
this.message = this.computedMessage+'altman'
}
},
}
</script>
Setter 函数:可以监听当前值的变化,然后赋值给相应的依赖数据,得到新值
Getter 函数:计算属性本质上是一个函数,但在模板中使用时可以像属性一样访问。
这里需要注意的是computed中的函数名不能和所操作data中的数据一样;computed中的函数名相当于是操作data数据后的新数据,在模块中直接使用这个函数名即可实现对data中数据改变的渲染。
注意,不应该使用箭头函数来定义 computed (例如 computedMessage: () => { return XXX;})。
理由:箭头函数绑定了父级作用域的上下文,所以 this 将不会按照期望指向 Vue 实例,严格模式下this 将是 undefined。
优点:计算属性会进行缓存,如果多次使用使用,计算属性只会调用一次。
计算属性的原理:
1.计算属性走缓存的机制如何实现:
-首先,计算属性多次被使用时且依赖的data中的属性没发生修改时,计算属性会走缓存机制。
-用脏数据标记(dirty)实现缓存机制。dirty是watcher的属性:dirty为true->读取计算属性时会重新计算;dirty为false->读取计算属性会走缓存。
2.当页面p的计算属性中的c属性依赖data中的a属性时,计算属性的更新步骤:
-因为c依赖于a,所以a可以收集到c的watcher。
-当a发生修改时,把watcher的dirty设置为true。
-a还会收集页面p的watcher,a会通知p进行更新,因为dirty为true,所以页面p会重新计算c。
-c更新完成后,将dirty设置为false。
二.事件(methods)
应用场景:函数的调用
<template>
<div class="on">
<!-- v-on 就是监听事件,可以用v-on指令监听DOM事件来触发一些javascript代码。
两中事件监听器(无参,有参);事件的行内调用
v-on 还有一种简单的写法,就是用@代替-->
<div @click="count++">点击添加1:{{count}}</div>
<!-- 如果同时传入某个参数,同时需要event时,可以通过$event传入事件 -->
<!--
.stop - 调用 event.stopPropagation()。
.prevent - 调用 event.preventDefault()。
.capture - 添加事件侦听器时使用 capture 模式。
.self - 只当事件是从侦听器绑定的元素本身触发时才触发回调。
.{keyCode | keyAlias} - 只当事件是从特定键触发时才触发回调。
.native - 监听组件根元素的原生事件。
.once - 只触发一次回调。
.left - (2.2.0) 只当点击鼠标左键时触发。
.right - (2.2.0) 只当点击鼠标右键时触发。
.middle - (2.2.0) 只当点击鼠标中键时触发。
.passive - (2.3.0) 以 { passive: true } 模式添加侦听器
-->
<button type="button" @click.prevent="init(10,$event)">初始值</button>
<button type="button" @click="add()">+1</button>
<button type="button" @click="substrack()">-1</button>
<h2>{{countNum}}</h2>
</div>
</template>
<script>
export default {
name: "on",
data() {
return {
count: 0,
countNum: 0
};
},
methods: {
/**
* 注意,不应该使用箭头函数来定义 method 函数 (例如 plus: () => this.a++)。
* 理由是箭头函数绑定了父级作用域的上下文,所以 this 将不会按照期望指向 Vue 实例,this.a 将是 undefined。
*/
init(num, event) {
this.countNum = num;
console.log(event);
},
add() {
return this.countNum++;
},
// substrack: () => {
// return this.countNum--; // undefined
// }
substrack() {
return this.countNum--;
}
}
};
</script>
注意,不应该使用箭头函数来定义 method 函数 (例如 plus: () => this.a++)。
理由:箭头函数绑定了父级作用域的上下文,所以 this 将不会按照期望指向 Vue 实例,严格模式下this 将是 undefined。
三.监听器(watch)
应用场景:数据变化时执行异步或开销较大的操作。
首先确认 watch是一个对象,一定要当成对象来用。
对象就有键,有值。
第一种 键:就是你要监听的那个数据,比如说$route,这个就是要监控路由的变化。或者是data中的某个变量。
值可以是函数:就是当你监控的家伙变化时,需要执行的函数,这个函数有两个形参,第一个是改变后的新值,第二个是改变之前的值。
第二种 值也可以是函数名:不过这个函数名要用单引号来包裹。(不常用)
第三种情况厉害了(监听的那个数据值是一个对象):
值是包括选项的对象:选项包括有三个。
第一个是handler:其值是一个回调函数。即监听到变化时应该执行的函数。
第二个是deep:其值是true或false;确认是否深入监听。(监听 对象/数组里面值的变化)。
第三个是immediate:其值是true或false;确认是否以当前的初始值执行handler的函数。
注意,不应该使用箭头函数来定义 watcher 函数 (例如 searchQuery: newValue => this.updateAutocomplete(newValue)
)。理由是箭头函数绑定了父级作用域的上下文,所以 this
将不会按照期望指向 Vue 实例,
用法一:基础用法
<template>
<div class="box">
<h1>监听器</h1>
<label>二次密码:</label>
<input v-model="watchMsg" />
</div>
</template>
<script>
export default {
data() {
return {
message: 'Hello world!',
watchMsg: '我是监听操作',
}
},
watch:{
// watch第一次绑定值的时候不会执行监听,修改数据才会触发函数
watchMsg(newVal,oldVal){
console.log('newVal:'+newVal,'oldVal:'+oldVal);
},
}
}
</script>
用法二:handler方法和immediate属性
<template>
<div class="box">
<h1>监听器</h1>
<label>二次密码:</label>
<input v-model="watchMsg" />
</div>
</template>
<script>
export default {
data() {
return {
message: 'Hello world!',
watchMsg: '我是监听操作',
}
},
watch:{
// 如果需要第一次就执行监听 则需要设置:immediate: true
watchMsg: {
handler(newVal,oldVal){
console.log('newVal:'+newVal,'oldVal:'+oldVal);
},
immediate:true,
}
}
}
</script>
注意到handler
了吗,我们给 watchMsg绑定了一个handler
方法,之前我们写的 watch 方法其实默认写的就是这个handler
,Vue.js会去处理这个逻辑,最终编译出来其实就是这个handler
。
而immediate:true
代表如果在 wacth 里声明了watchMsg之后,就会立即先去执行里面的handler方法,如果为 false
就跟我们以前的效果一样,不会在绑定的时候就执行。
用法三:
deep属性
watch 里面还有一个属性 deep
,默认值是 false
,代表是否深度监听,比如我们 data 里有一个obj
属性/数组:
<template>
<div class="box">
<h1>监听器</h1>
<label>二次密码:</label>
<input v-model="obj.watchMsg" />
</div>
</template>
<script>
export default {
data() {
return {
message: 'Hello world!',
obj:{
watchMsg: '我是监听操作',
}
}
},
watch:{
// 注意:属性值发生变化后,handler执行后获取的 newVal 值和 oldVal 值是一样的
obj: {
handler(newVal,oldVal){
console.log('newVal:'+newVal,'oldVal:'+oldVal);
},
immediate:true,
deep:true,
},
// 函数执行后,获取的 newVal 值和 oldVal 值不一样
'obj.watchMsg': {
handler(newVal,oldVal){
console.log('newVal:'+newVal,'oldVal:'+oldVal);
},
// 立即处理 进入页面就触发
immediate:true,
}
}
}
</script>
2、数组的watch((一维、多维)数组的变化不需要深度监听)
<template>
<div class="box">
<h1>监听器</h1>
<p>{{array}}</p>
<button @click='addArray()'>点击改变数组</button>
</div>
</template>
<script>
export default {
data() {
return {
message: 'Hello world!',
array:['mu','zi','dig','big']
}
},
watch:{
// 需要注意的是这里所监听的对象应该是数组
array:{
handler(newVal,oldVal){
console.log(newVal+'--'+oldVal)
},
// deep:true,
// immediate:true,
}
},
methods:{
addArray() {
this.array.push('66')
}
}
}
</script>
3、数组对象中对象属性变化监测需要使用deep:true
深度监听,多少层内产生变化都可以监测到。
<template>
<div class="box">
<h1>监听器</h1>
<ul>
<li v-for="(item,index) in arrayObj" :key="item.id">{{index}}--{{item.name}}---<input type="text" v-model="item.age" @keydown="change(index)"></li>
</ul>
<hr>
<ul>
<li v-for="(item,index) in arrayObj" :key="item.id">{{index}}--{{item.name}}---{{item.age}}</li>
</ul>
</div>
</template>
<script>
export default {
data() {
return {
arrayObj:[
{name:'张三',age:'23'},
{name:'李四',age:'22'}
],
}
},
watch:{
// 需要注意的是这里所监听的对象应该是数组
arrayObj:{
handler(newVal,oldVal){
console.log(newVal+'--'+oldVal)
},
deep:true,
immediate:true,
}
},
methods:{
change(i) {
// console.log(this.changeValue)
this.arrayObj[i].age = this.arrayObj[i].age
}
}
}
</script>
4、对象具体属性的watch[活用computed]
<template>
<div class="box">
<h1>监听器</h1>
<p>{{obj.name}}</p>
<input type="text" v-model="obj.name" name="" id="">
<p>{{newName}}</p>
</div>
</template>
<script>
export default {
data() {
return {
obj:{name:'muzi',age:'23'},
}
},
computed:{
newName(){
return this.obj.name.toUpperCase();
}
},
watch:{
newName(newVal,oldVal){
console.log(newVal+'--'+oldVal)
},
// newName:{
// handler(newVal,oldVal){
// console.log(newVal+'--'+oldVal)
// },
// deep:true,
// immediate:true,
// }
},
}
</script>
从效果图上可以看出,计算属性最好使用在更改数据上然后进行渲染;先进行的计算属性再进行的监听。
5.监听路由变化
// 方法一:
watch:{
$router(to,from){
console.log(to.path)
}
}
// 方法二:
watch: {
$route: {
handler: function (val, oldVal){
console.log(val);
},
// 深度观察监听
deep: true
}
},
// 方法三:
watch: {
'$route' : 'getRoutePath'
},
methods: {
getRoutePath(){
console.log( this .$route.path);
}
}
1.1 computed
1.是计算值
2.具有缓存性,页面重新渲染值不会变化,计算属性会立即返回之前的计算结果,而不必再次执行函数(只有当依赖数据发生改变才会重新计算)
3.不支持异步,当computed内有异步操作时无效,无法监听数据的变化
4.不支持箭头函数
5.第一次加载就监听,必须 return;默认初始化会执行一次
6.最典型的栗子:购物车商品结算的时候
理由是箭头函数绑定了父级作用域的上下文,所以 this
将不会按照期望指向 Vue 实例,我们这里需要挂载到vm上。
2.2 watch
1.是观察的动作
2.无缓存性,页面重新渲染时值不变化也会执行(值改变则直接触发)
3.支持异步
4.监听数据必须是 data 中声明过或者父组件传递过来的props中的数据以及挂载到vm上的数据(computed)
5.不支持箭头函数
6.无需return;默认无数据改动不会执行,可配置 {immediate:true}来实现初始化执行一次
7.栗子:搜索数据
理由是箭头函数绑定了父级作用域的上下文,所以 this
将不会按照期望指向 Vue 实例,我们这里需要挂载到vm上。
2.3 methods
1.是函数
2.无缓存性,页面重新渲染会被重新调用
3.支持异步
4.不支持箭头函数
5.每次调用都会重新执行
理由是箭头函数绑定了父级作用域的上下文,所以 this
将不会按照期望指向 Vue 实例,我们这里需要挂载到vm上。
下面还另外总结了几点关于 computed
、watch 和 methods 的差异:
-
computed
是计算一个新的属性,并将该属性挂载到 vm(Vue 实例)上,而watch
是监听已经存在且已挂载到vm
上的数据,所以用watch
同样可以监听computed
计算属性的变化(其它还有data
、props
) -
computed
本质是一个惰性求值的观察者,具有缓存性,只有当依赖变化后,才会计算新的值,computed 第一次加载就监听,其他直接使用这个计算得到的值,而watch
则是当数据发生变化便会调用执行函数 - 计算属性是有缓存的,只要相关依赖没有改变,多次访问计算属性得到的值是之前缓存的计算结果,不会多次执行
- methods则是调用才更新
- 从使用场景上说,
computed
适用一个数据被多个数据影响,而watch
适用一个数据影响多个数据
区别:
1.watch和computed都是以Vue的依赖追踪机制为基础的,它们都试图处理这样一件事情:当某一个数据(称它为依赖数据)发生变化的时候,所有依赖这个数据的“相关”数据“自动”发生变化,也就是自动调用相关的函数去实现数据的变动。
2.对methods:methods里面是用来定义函数的,很显然,它需要手动调用才能执行。而不像watch和computed那样,“自动执行”预先定义的函数
【总结】:methods里面定义的函数,是需要主动调用的,而和watch和computed相关的函数,会自动调用,完成我们希望完成的作用
若有不足请多多指教!希望给您带来帮助!