# Computed 和 Watch

Computed本质是一个具备缓存的watcher,依赖的属性发生变化就会更新视图。 适用于计算比较消耗性能的计算场景。当表达式过于复杂时,在模板中放入过多逻辑会让模板难以维护,可以将复杂的逻辑放入计算属性中处理。

Watch没有缓存性,更多的是观察的作用,可以监听某些数据执行回调。当我们需要深度监听对象中的属性时,可以打开deep:true选项,这样便会对对象中的每一项进行监听。这样会带来性能问题,优化的话可以使用字符串形式监听,如果没有写到组件中,不要忘记使用unWatch手动注销哦。

  • 功能上:computed是计算属性,也就是依赖其它的属性计算所得出最后的值。watch是去监听一个值的变化,然后执行相对应的函数
  • 使用上:computed中的函数必须要用return返回;watch的回调里面会传入监听属性的新旧值,通过这两个值可以做一些特定的操作,不是必须要用return
  • 性能上:computed中的函数所依赖的属性没有发生变化,那么调用当前的函数的时候会从缓存中读取,而watch在每次监听的值发生变化的时候都会执行回调
  • 场景上
    • computed:当一个属性受多个属性影响的时候,例子:购物车商品结算;
    • watch:当一条数据影响多条数据的时候,例子:搜索框

# 使用场景

computed和watch都起到监听/依赖一个数据,并进行处理的作用

computed: 一个数据受多个数据影响,对同步数据的处理。

watch: 一个数据影响多个数据,观测某个值的变化去完成一段开销较大的复杂业务逻辑。

# computed

很多时候, 在页面调用接口加载数据之前, 可能会出现报错的情况, 但是页面不会有问题, 那么产生这个问题的原因, 是什么呢?

例如下面的代码, 属性值不存在, 所以就报错了

<div>{{list.a.b}}</div>
<script>
var vm = new Vue({
    el: '#demo',
    data: {
        list: {}
    }
})
</script>

为了避免不报错, 这时候, 类似加个判断, 在数据多的情况下, 明显的优势就出来了

<div>{{b}}</div>
<script>
var vm = new Vue({
    el: '#demo',
    data: {
        list: {}
    },
    computed: {
        b(){
            if(!Object.value(this.list).length && this.list.a.b){
                return this.list.a.b
            }else{
                return ''
            }
        }
    }
})
</script>

# watch的高级用法

  1. handler方法和immediate属性
<div id="demo">{{ fullName }}</div>
<script>
var vm = new Vue({
    el: '#demo',
    data: {
        firstName: 'Foo',
        lastName: 'Bar',
        fullName: 'Foo Bar'
    },
    watch: {
        firstName: function (val) {
            console.log('第一次没有执行~')
            this.fullName = val + ' ' + this.lastName
        }
    }
})
</script>

可以看到,初始化的时候watch是不会执行的。看上边的例子,只要当firstName的值改变的时候才会执行监听计算。但如果想在第一次它在被绑定的时候就执行怎么办?

这时候就要修改一下我们的例子:

var vm = new Vue({
    watch: {
        firstName: {
            handler(val) {
                console.log('第一次执行了~')
                this.fullName = val + ' ' + this.lastName
            },
            // 代表在watch里声明了firstName这个方法之后立即先去执行handler方法
            immediate: true
        }
    }
})
  1. deep属性

watch里还有一个deep属性,代表是否开启深度监听,默认为false,下面来看一个例子:

<div id="app">
    <div>obj.a: {{obj.a}}</div>
    <input type="text" v-model="obj.a">
</div>
<script>
var vm = new Vue({
    el: '#app',
    data: {
        obj: {
            a: 1
        }
    },
    watch: {
        obj: {
            handler(val) {
                console.log('obj.a changed')
            },
            immediate: true
        }
    }
})
</script>

当我们在input输入框中输入数据改变obj.a的值时,我们发现在控制台没有打印出'obj.a changed'

受现代 JavaScript 的限制 (以及废弃 Object.observe),Vue 不能检测到对象属性的添加或删除。

由于 Vue 会在初始化实例时对属性执行getter/setter转化过程,所以属性必须在data对象上存在才能让 Vue 转换它,才能让它是响应式的。

所以,想要深度监听属性值的变化,就需要deep:true

watch: {
    obj: {
        handler(val) {
           console.log('obj.a changed')
        },
        immediate: true,
        deep: true
    }
}

deep属性的意思是深度遍历,会在对象一层层往下遍历,在每一层都加上监听器。

但是使用deep属性会给每一层都加上监听器,性能开销可能就会非常大了。这样我们可以用字符串的形式来优化:

watch: {
    'obj.a': {
        handler(val) {
            console.log('obj.a changed')
        },
        immediate: true
        // deep: true
    }
}

直到遇到'obj.a'属性,才会给该属性设置监听函数,提高性能。