目录

一、计算属性(computed)

1、计算属性的特点

2、计算属性的原理——getter 和 setter

3、computed 里返回一个函数——给 computed 属性中的方法传参

4、computed 与 methods 的区别

二、侦听器(watch)

1、侦听器的特点

2、使用 watch的注意事项

3、watch的两个属性

(1)、deep

(2)、immediate

4、深度监听一个对象时如何避免新值与旧值相等呢?

5、watch 与 $watch 的关系

6、watch 的使用案例

三、计算属性 和 侦听器 适用场景

四、推荐几篇关于 computed 和 watch 的原理的深度解析好文


、计算属性(computed)

1、计算属性的特点

  • 支持 数据缓存——页面重新渲染时,只有数据发生了改变,才会执行相应的函数。
  • 减少模板中计算逻辑。
  • 依赖“响应式数据”。

2、计算属性的原理——getter 和 setter

在computed中的,属性都有一个get和一个set方法。当 computed 属性的属性值是函数,那么默认会走get方法,函数的返回值就是属性的值;当数据变化时,调用set方法。

计算属性默认只有 getter,在需要时你也可以提供一个 setter。

computed: {
  fullName: {
    // getter
    get: function () {
      return this.firstName + ' ' + this.lastName
    },
    // setter
    set: function (newValue) {
      var names = newValue.split(' ')
      this.firstName = names[0]
      this.lastName = names[names.length - 1]
    }
  }
}

再运行 vm.fullName = 'John Doe' 时,setter 会被调用,vm.firstName 和 vm.lastName 也会相应地被更新。 

实战应用案例:用computed监听:在子组件中更新父组件中传过来的变量

3、computed 里返回一个函数——给 computed 属性中的方法传参

<template lang="pug">
  .topic-list(v-for='item in topicList')
    a(:href='splitUrl(item.id)' target='_blank') {{item.name}}
</template>
<script>
export default {
  computed: {
    splitUrl () {
      return id => `${window.location.origin}/list/topic/${id}${this.isFromYY ? `?yyId=${this.info.yyId}` : ''}`
    }
  }
}
</script>

使用了闭包。 

4、computed 与 methods 的区别

<template>
    <div>
        <p>reverseMsg1:{{reverseMsg1}}</p>
        <p>reverseMsg2:{{reverseMsg2()}}</p>
        <button @click="()=> $forceUpdate()">强制更新</button>
        <div>
            <input type="text" v-model="msg">
        </div>
    </div>
</template>

<script>
export default {
    data() {
        return {
            msg: "hello vue"
        }
    },
    computed: {
        reverseMsg1() {
            console.log("reverseMsg1执行了");
            return this.msg.split("").reverse().join("");
        }
    },
    methods: {
        reverseMsg2(){
            console.log("reverseMsg2执行了");
            return this.msg.split("").reverse().join("");
        }
    },
}
</script>

由上面这个例子可以看出:

computed 与 methods 的区别:

  • computed里定义的函数,函数名可以作为变量直接使用,默认 会执行该函数体。
  • methods里定义的函数,必须调用 才能执行该函数体。
  • computed里的数据会被缓存,每次更新都会先对比数据是否发生了变化,是才更新,否不执行,大大减少了渲染时间。
  • methods里的函数不会被缓存,只要数据有更新,就会执行对应的函数。
  • 如果你不希望有缓存,请用方法来替代。

二、侦听器(watch)

1、侦听器的特点

  • 支持异步
  • 可以侦听任何逻辑,比如:函数节流,Ajax异步获取的数据,甚至操作DOM(不建议)。
  • 页面重新渲染时,无论值变不变,都会重新执行,因为watch不支持数据缓存。
  • 监听的函数接收两个参数,第一个参数是:新值,第二个参数是:原来的值。

2、使用 watch的注意事项

不要在 watch 中使用箭头函数,因为箭头函数没有 this,它的 this 会继承它的父级函数,但是它的父级函数是 window,导致箭头函数的 this 指向 window,而不是 Vue 实例。

3、watch的两个属性

(1)、deep

默认情况下,侦听器只会监听数据本身的改变,若要进行深度监听,那就需要使用 deep。

deep:其值是 true 或 false。深度监听,用来控制是否要监听对象内布值的变化(在页面初始化化时不会触发,只有当值改变时才会触发)。

不过,deep 无法监听到数组的变动和对象的新增,参考 vue 数组变异,只有以响应式的方式触发才会被监听到——vm.$set

(2)、immediate

默认情况下,侦听器需要 data 后面值改变了才会生效,若需要侦听器一进入页面就生效,那就需要使用 immediate。

immediate:其值是 true 或 false。组件加载时,用来控制是否要立即执行回调函数(在页面初始化后是否立即执行)。

4、深度监听一个对象时如何避免新值与旧值相等呢?

深度监听一个对象时,要监测到对象的具体需要被监听的属性,就能够解决新值与旧值相等的问题。

例如:问题再现

data () {
  return {
    obj: { a: 1 }
  }
},
watch: {
  obj (newVal, oldVal) {
    console.log(newVal, oldVal)
  }
}

上述代码中,当改变 obj 里属性的值时,打印 newVal 和 oldVal 的结果,发现他们是相等的。这是因为:改变一个对象里属性的值时,新值和旧值指向的是同一块内存区,所以无法拿到旧值,也可以理解为是浅拷贝的问题。我们可以通过直接监听该对象里的属性来解决这个问题:

data () {
  return {
    obj: { a: 1 }
  }
},
watch: {
  "obj.a": (newVal, oldVal) => {
    console.log(newVal, oldVal)
  }
}

5、watch 与 $watch 的关系

Vue 实例将会在实例化时调用$watch(),遍历 watch 对象的每一个属性

6、watch 的使用案例

<template>
    <div>
        {{$data}}
        <br/>
        <button @click="() => {a += 1}">a+1</button>
    </div>
</template>

<script>
export default {
    data() {
        return {
            a: 1,
            b: {c: 2, d: 3},
            e: {
                f:{
                    g: 4
                }
            },
            h: []
        }
    },
    watch: {
        a(val, oldVal){
            this.b.c += 1;
            console.log("new: %s, old: %s", val, oldVal);
        },
        "b.c": function(val, oldVal){
            this.b.d += 1;
            console.log("new: %s, old: %s", val, oldVal);
        },
        "b.d": function(val, oldVal){
            this.e.f.g += 1;
            console.log("new: %s, old: %s", val, oldVal);
        },
        e: {
            handler: function(){
                this.h.push("哈")
            },
            deep: true
        },
        h(val, oldVal){
            console.log("new: %s, old: %s", val, oldVal);
        }
    },
}
</script>

三、计算属性 和 侦听器 适用场景

computed 能做的,watch 都能做,反之不行。不过,能用 computed 的尽量用 computed。

如果一个属性是由其他属性计算而来的,这个属性依赖其他属性,是一个 多对一 或者 一对一 ,一般用computed。

比如:

<template>
    <div>
        {{fullName}}
        <div>firstName:<input type="text" v-model="firstName"></div>
        <div>lastName:<input type="text" v-model="lastName"></div>
    </div>
</template>

<script>
export default {
    /**
     * 用计算属性去实现
     */
    data() {
        return {
            firstName: "charles",
            lastName: "jake",
        }
    },
    computed: {
        fullName(){
            return this.firstName + " " + this.lastName;
        }
    },


    /**
     * 用侦听器去实现
     */
    // data() {
    //     return {
    //         fullName: "charles jake",
    //         firstName: "charles",
    //         lastName: "jake",
    //     }
    // },
    // watch: {
    //     firstName(val, oldVal){
    //         if(val != oldVal){
    //             this.fullName = val + " " + this.lastName;
    //         }
    //     },
    //     lastName(val, oldVal){
    //         if(val != oldVal){
    //             this.fullName = this.firstName + " " + val;
    //         }
    //     }
    // },
}
</script>

四、推荐几篇关于 computed 和 watch 的原理的深度解析好文

深入理解 Vue Computed 计算属性:https://segmentfault.com/a/1190000010408657
Vue的数据依赖实现原理简析:https://segmentfault.com/a/1190000010014281#articleHeader2
vue中$watch源码阅读笔记:vue中$watch源码阅读笔记 - Clarence2J - 博客园

Logo

前往低代码交流专区

更多推荐