计算属性(computed)、事件(methods)、监听器(watch)

127 阅读6分钟

computed 和 watch 的差异:

  • computed 是计算一个新的属性默认初始化会执行一次,并将该属性挂载到 vm(Vue 实例)上,而 watch 是监听已经存在且已挂载到 vm 上的数据,默认需配置才会执行,所以用 watch 同样可以监听 computed 计算属性的变化(其它还有 data、props);
  • computed 本质是一个惰性求值的观察者,具有缓存性,只有当依赖变化后,才会计算新的值,computed 第一次加载就监听,而 watch 则是当数据发生变化便会调用执行函数;
  •  从使用场景上说,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>
  • set可以监听当前值的变化,然后赋值给相应的依赖数据,得到新值
  • 这里需要注意的是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);
   }
}

  • computed 
  1.  是计算值 
  2. 具有缓存性,页面重新渲染值不会变化,计算属性会立即返回之前的计算结果,而不必再次执行函数(只有当依赖数据发生改变才会重新计算) 
  3. 不支持异步,当computed内有异步操作时无效,无法监听数据的变化 
  4. 不支持箭头函数 
  5. 第一次加载就监听,必须 return;默认初始化会执行一次 
  6. 最典型的栗子:购物车商品结算的时候 。

理由是箭头函数绑定了父级作用域的上下文,所以 this 将不会按照期望指向 Vue 实例,我们这里需要挂载到vm上。 

  •  watch 
  1. 是观察的动作 
  2.  无缓存性,页面重新渲染时值不变化也会执行(值改变则直接触发) 
  3.  支持异步 
  4.  监听数据必须是 data 中声明过或者父组件传递过来的props中的数据以及挂载到vm上的数据(computed) 
  5.  不支持箭头函数 
  6.  无需return;默认无数据改动不会执行,可配置 {immediate:true}来实现初始化执行一次
  7. 栗子:搜索数据 理由是箭头函数绑定了父级作用域的上下文,所以 this 将不会按照期望指向 Vue 实例,我们这里需要挂载到vm上。 

  •   methods 
  1.  是函数 
  2.  无缓存性,页面重新渲染会被重新调用
  3. 支持异步 
  4.  不支持箭头函数 理由是箭头函数绑定了父级作用域的上下文,所以 this 将不会按照期望指向 Vue 实例,我们这里需要挂载到vm上。 

 下面还另外总结了几点关于 computed 、watch 和 methods 的差异: 

  •  computed 是计算一个新的属性,并将该属性挂载到 vm(Vue 实例)上,而 watch 是监听已经存在且已挂载到 vm 上的数据,所以用 watch 同样可以监听 computed 计算属性的变化(其它还有 data、props) 
  • computed 本质是一个惰性求值的观察者,具有缓存性,只有当依赖变化后,才会计算新的值,computed 第一次加载就监听,其他直接使用这个计算得到的值,而 watch 则是当数据发生变化便会调用执行函数 
  • 计算属性是有缓存的,只要相关依赖没有改变,多次访问计算属性得到的值是之前缓存的计算结果,不会多次执行 
  • methods则是调用才更新 
  • 从使用场景上说,computed 适用一个数据被多个数据影响,而 watch 适用一个数据影响多个数据 

 区别: 

  • watch和computed都是以Vue的依赖追踪机制为基础的,它们都试图处理这样一件事情:当某一个数据(称它为依赖数据)发生变化的时候,所有依赖这个数据的“相关”数据“自动”发生变化,也就是自动调用相关的函数去实现数据的变动。 
  • 对methods:methods里面是用来定义函数的,很显然,它需要手动调用才能执行。而不像watch和computed那样,“自动执行”预先定义的函数 

【总结】:

methods里面定义的函数,是需要主动调用的,而和watch和computed相关的函数,会自动调用,完成我们希望完成的作用