关于javascript:vuejs中的普通方法计算属性computed与监听属性watch四者的比较

41次阅读

共计 12044 个字符,预计需要花费 31 分钟才能阅读完成。

背景

vue 中, 实现同一个性能需要, 能够应用 一般办法 , 也能够应用computed 属性以及 watch 属性, 对于它们的应用, 刚开始时, 存在着一些困惑

至于什么时候应用办法, 什么时候应用计算 computed 属性, 以及 watch 属性, 往往令人很头疼

不同的形式适宜对应的场景, 以下是本篇的内容

需要场景

输出 A,B 两数并求和, 当和后果满足指定的条件时, 指定该后果属于哪个年龄阶段

result <= 6 => 是个儿童
result > 6 && result <= 17  => "岁是个少年"
result > 17 && result  <=40 => "岁是个青年"
result > 40 && result <=65 => "岁是个中年人"
result > 65 && result <=100 =>"岁是个老年人"
isNaN(result))=> "你输出的信息有误"
 result > 100 "岁, 曾经超过了百岁, 还是地球人么"

具体成果演示

<img src=”https://static01.imgkr.com/temp/b07a314d7f9242feb3fbd1e61f0726ed.gif” class=”medium-zoom lazy”>

需要剖析

  1. 初始化值 A,B 两个数
  2. 计算拿到两数之和的后果, 并且做绝对应的逻辑判断

办法 1 - 应用模板形式实现

vue 模板中, 插值表达式中能够做简略的逻辑判断

具体代码如下所示

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>01- 一般办法实现 </title>
    <style>
        .box {
           margin-left: 35%;
           margin-top: 100px;
        }
    </style>
</head>
<body>
    
    <div id="root">
        <div class="box">
            A: <input type="number"  v-model:value="A" />
            <span>+</span>
            B: <input  type="number" v-model:value="B" />
            <button>=</button>
            <span>{{parseInt(A)+parseInt(B) }}</span><br /><br />

            <div>
                后果: {{A}} +{{B}} = {{parseInt(A)+parseInt(B)}} 
                <span v-if="(parseInt(A)+parseInt(B))<=6"> 岁是个儿童 </span>
                <span v-else-if="(parseInt(A)+parseInt(B)) > 6 && (parseInt(A)+parseInt(B)) <= 17"> 岁是个少年 </span>
                <span v-else-if="(parseInt(A)+parseInt(B)) > 17 && (parseInt(A)+parseInt(B)) <= 40"> 岁是个青年 </span>
                <span v-else-if="(parseInt(A)+parseInt(B)) > 40 && (parseInt(A)+parseInt(B)) <= 65"> 岁是个中年人 </span>
                <span v-else-if="(parseInt(A)+parseInt(B)) > 65 && (parseInt(A)+parseInt(B)) <= 100"> 岁是个老年人 </span>
                <span v-else-if="(parseInt(A)+parseInt(B)) > 100"> 岁, 曾经超过了百岁, 还是地球人么 </span>
                <span v-else="isNaN(parseInt(A)+parseInt(B))"> 你输出的信息有误 </span>
           </div>                                                
        </div>
    </div>
    <script src="./js/vue.js"></script>
    <script>
        const vm = new Vue({
            el: "#root",
            data() {
                return {
                    A: "4",
                    B: '5',
                }
            }
        })
    </script>
</body>
</html>

vue 模板 内应用表达式解决简略的逻辑, 十分便当, 然而它只适宜于简略的运算, 如果逻辑很简单, 那么保护模板就会变得很麻烦, 不直观

倡议

对于简单的逻辑, 都应该应用办法或者计算 computed 属性

额定拓展

为什么 data 的值写成一个函数, 而非一个对象?

简要

当一个组件被定义,data必须申明为返回一个初始数据对象的函数, 因为组件可能被用来创立多个实例

也就是说, 在很多页面中, 定义的组件能够复用在多个页面

如果 data 是一个纯碎的对象, 则所有的实例将共享援用同一份 data 数据对象, 无论在哪个组件实例中批改data, 都会影响到所有的组件实例

如果 data 是函数, 每次创立一个新实例后, 调用 data 函数, 从而返回初始数据的一个全新正本数据对象

这样每复用一次组件, 会返回一份新的 data 数据, 相似于给每个组件实例创立一个公有的数据空间, 让各个组件的实例各自独立, 互不影响

办法 2 - 应用一般办法实现

示例代码如下所示, 在 methods 中定义方法 (性能), 在vue 模板 中间接办法的调用(函数名())

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>01- 应用一般办法实现 </title>
    <style>
        .box {
           margin-left: 35%;
           margin-top: 100px;
        }
    </style>
</head>
<body>
    
    <div id="root">
        <div class="box">
            A: <input type="number"  v-model:value="A" />
            <span>+</span>
            B: <input  type="number" v-model:value="B" />
            <button>=</button>
            <span>{{addResult() }}</span><br /><br />
            <div>
                后果: {{A}} +{{B}} = {{addResult()}}  {{outPut()}}
            </div>  
        </div>
    </div>
    <script src="./js/vue.js"></script>
    <script>
        const vm = new Vue({
            el: "#root",
            data() {
                return {
                    A: "4",
                    B: '5',
                }
            },

            methods: {addResult() {return parseInt(this.A)+parseInt(this.B)
                },

                outPut() {const result = parseInt(this.A)+parseInt(this.B);
                    if(result <= 6) {return "岁是个儿童"}else if(result > 6 && result <= 17) {return "岁是个少年"} else if(result > 17 && result  <=40) {return "岁是个青年"}else if(result > 40 && result <=65) {return "岁是个中年人"}else if(result > 65 && result <=100) {return "岁是个老年人"}else if(isNaN(result)) {return "你输出的信息有误"}else {return "岁, 曾经超过了百岁, 还是地球人么"}
                }
            },
        })
    </script>
</body>
</html>

以上就是在 methods 中定义方法, 去实现的

注意事项

应用一般办法, 实现时, 每当触发办法, 都会引起页面从新渲染,执行办法函数, 它是没有缓存的

如果有一个性能开销比拟大的计算属性, 它须要遍历一个很大的数组, 并做大量的计算, 而这个计算属性又有其余依赖, 如果没有缓存, 不必计算属性, 那么就会一直的执行收集属性的getter, 如果不心愿有缓存, 就用办法来代替

办法 3 - 应用计算属性 computed 实现

vue 实例配置选项中, 增加 computed 属性, 值是一个对象, 并且增加与之绝对应的计算属性

计算属性失去的值是之前缓存的计算结果,不会屡次执行

实例代码如下所示

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>02- 应用计算属性 computed 实现 </title>
    <style>
        .box {
           margin-left: 35%;
           margin-top: 100px;
        }
    </style>
</head>
<body>
    
    <div id="root">
        <div class="box">
            A: <input type="number"  v-model:value="A" />
            <span>+</span>
            B: <input  type="number" v-model:value="B" />
            <button>=</button>
            <span>{{addResult}}</span><br /><br />
            <div>
                后果: {{A}} +{{B}} = {{addResult}}  {{outPut}}
            </div>  
        </div>
    </div>
    <script src="./js/vue.js"></script>
    <script>
        const vm = new Vue({
            el: "#root",
            data() {
                return {
                    A: "4",
                    B: '5',
                }
            },

            computed: {
                addResult: {  // 原始书写的形式
                    get() {return parseInt(this.A)+parseInt(this.B)
                    }
                },

                outPut: {get() {const result = parseInt(this.A)+parseInt(this.B);
                        if(result <= 6) {return "岁是个儿童"}else if(result > 6 && result <= 17) {return "岁是个少年"} else if(result > 17 && result  <=40) {return "岁是个青年"}else if(result > 40 && result <=65) {return "岁是个中年人"}else if(result > 65 && result <=100) {return "岁是个老年人"}else if(isNaN(result)) {return "你输出的信息有误"}else {return "岁, 曾经超过了百岁, 还是地球人么"}
                    }
                }
            }
        })
    </script>
</body>
</html>

舒适提醒

当一旦确定计算属性只读取(get), 而不去批改set, 确定了只读不改, 能够应用简写模式, 如下所示等价

// 其余省略, 如上所示   
computed: {// 一旦确定计算属性只读取(get), 而不去批改 set, 能够应用简写模式
    // 确定了只读不改, 那么就能够应用简写模式   
    addResult() {return parseInt(this.A)+parseInt(this.B)
    },

    outPut() {const result = parseInt(this.A)+parseInt(this.B);
        if(result <= 6) {return "岁是个儿童"}else if(result > 6 && result <= 17) {return "岁是个少年"} else if(result > 17 && result  <=40) {return "岁是个青年"}else if(result > 40 && result <=65) {return "岁是个中年人"}else if(result > 65 && result <=100) {return "岁是个老年人"}else if(isNaN(result)) {return "你输出的信息有误"}else {return "岁, 曾经超过了百岁, 还是地球人么"}
    }
}
})

注意事项

  1. 计算属性的后果, 不必挂载在 data 上面进行数据的初始化, 在 vue 模板中能够间接应用, 不必加圆括号 计算属性名(), 这点有别于一般办法的调用
  2. 在模板中放入太多的逻辑会让模板过重且难以保护, 也不直观(简略的逻辑能够放在模板中解决)
  3. 对于简单逻辑,能够应用计算属性(计算属性的 getter 函数是没有副作用, 但也能够应用办法, 然而计算属性在计算数量量比拟大, 具备缓存计算结果的作用, 性能更高, 频繁调用办法, 解析模板, 渲染页面, 是比拟耗费性能的)
  4. 计算属性是基于它们的响应式依赖进行缓存的, 只在相干响应式依赖产生扭转时它们才会从新求值, 相比于一般办法的调用,每当触发从新渲染时,调用办法执行函数, 会解析vue 模板

办法 4 - 应用 watch 监听属性来实现

  1. 通过 vm 对象的 $watch()watch配置来监督指定的属性
  2. 当属性变动时, 回调函数主动调用, 在函数外部进行计算

具体实例代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>03- 监听属性 watch 办法实现 </title>
    <style>
        .box {
           margin-left: 35%;
           margin-top: 100px;
        }
    </style>
</head>
<body>
    
    <div id="root">
        <div class="box">
            A: <input type="number"  v-model:value="A" />
            <span>+</span>
            B: <input  type="number" v-model:value="B" />
            <button>=</button>
            <span>{{addResult}}</span><br /><br />

            <div>
                后果: {{A}} +{{B}} = {{addResult}}  {{outPut}}
            </div>  
        </div>
    </div>
    <script src="./js/vue.js"></script>
    <script>
        const vm = new Vue({
            el: "#root",
            data() {
                return {
                    A: "4",
                    B: '5',
                    addResult: '',
                    outPut: ''
                }
            },

            watch: {
                A: {
                    immediate: true,   // 初始化时让 handler 调用一下, 默认是 false
                    handler(newVal,oldVal) {console.log("A 数据扭转了","最新的:",newVal,"旧的:",oldVal);
                        this.addResult = parseInt(newVal)+parseInt(this.B)
                        const result = parseInt(this.addResult);
                        if(result <= 6) {this.outPut = "岁是个儿童"}else if(result > 6 && result <= 17) {this.outPut = "岁是个少年"} else if(result > 17 && result  <=40) {this.outPut = "岁是个青年"}else if(result > 40 && result <=65) {this.outPut = "岁是个中年人"}else if(result > 65 && result <=100) {this.outPut = "岁是个老年人"}else if(isNaN(result)) {this.outPut= "你输出的信息有误"}else {this.outPut = "岁, 曾经超过了百岁, 还是地球人么"} 
                    }
                },

                B: {
                    immediate: true,
                    handler(newVal, oldVal) {console.log("B 数据变了","最新的",newVal,"旧的",oldVal);
                        this.addResult = parseInt(this.A)+parseInt(newVal);
                        const result = parseInt(this.addResult);
                        if(result <= 6) {this.outPut = "岁是个儿童"}else if(result > 6 && result <= 17) {this.outPut = "岁是个少年"} else if(result > 17 && result  <=40) {this.outPut = "岁是个青年"}else if(result > 40 && result <=65) {this.outPut = "岁是个中年人"}else if(result > 65 && result <=100) {this.outPut = "岁是个老年人"}else if(isNaN(result)) {this.outPut= "你输出的信息有误"}else {this.outPut = "岁, 曾经超过了百岁, 还是地球人么"} 
                    }
                }
            },
        })

    </script>
</body>
</html>

当响应的数据不须要immediate:true,deep: true, 就能够简写, 下面的watch,如下是等价的

留神

如果写成简写的形式, 那么就无奈写配置选项

// 其余局部省略, 如上所示
    watch: {
        // 等价于如下
        A(newVal) {// 这里的 newVal 参数, 指的是以后监督属性, 最新的值, 能够写一个, 也能够写两个(newVal,oldVal)
            this.addResult = parseInt(newVal)+parseInt(this.B)
            const result = parseInt(this.addResult);
            if(result <= 6) {this.outPut = "岁是个儿童"}else if(result > 6 && result <= 17) {this.outPut = "岁是个少年"} else if(result > 17 && result  <=40) {this.outPut = "岁是个青年"}else if(result > 40 && result <=65) {this.outPut = "岁是个中年人"}else if(result > 65 && result <=100) {this.outPut = "岁是个老年人"}else if(isNaN(result)) {this.outPut= "你输出的信息有误"}else {this.outPut = "岁, 曾经超过了百岁, 还是地球人么"} 
        },

        B(newVal) {console.log("B 数据变了","最新的",newVal,"旧的");
            this.addResult = parseInt(this.A)+parseInt(newVal);
            const result = parseInt(this.addResult);
            if(result <= 6) {this.outPut = "岁是个儿童"}else if(result > 6 && result <= 17) {this.outPut = "岁是个少年"} else if(result > 17 && result  <=40) {this.outPut = "岁是个青年"}else if(result > 40 && result <=65) {this.outPut = "岁是个中年人"}else if(result > 65 && result <=100) {this.outPut = "岁是个老年人"}else if(isNaN(result)) {this.outPut= "你输出的信息有误"}else {this.outPut = "岁, 曾经超过了百岁, 还是地球人么"} 
        }
    },
})

当然,Vue提供了 $watch 实例办法, 也能够这么写

<script>
const vm = new Vue({
    el: "#root",
    data() {
        return {
            A: "4",
            B: '5',
            addResult: '',
            outPut: ''
        }
    },
        // 等价于如下形式
vm.$watch('A',{
    immediate: true,   // 初始化时让 handler 调用一下, 默认是 false, 不写这个的话, 第一次不会调用 handler 函数,会达不到本人的预期
    handler(newVal,oldVal) {console.log("A 数据扭转了","最新的:",newVal,"旧的:",oldVal);
        this.addResult = parseInt(newVal)+parseInt(this.B)
        const result = parseInt(this.addResult);
        if(result <= 6) {this.outPut = "岁是个儿童"}else if(result > 6 && result <= 17) {this.outPut = "岁是个少年"} else if(result > 17 && result  <=40) {this.outPut = "岁是个青年"}else if(result > 40 && result <=65) {this.outPut = "岁是个中年人"}else if(result > 65 && result <=100) {this.outPut = "岁是个老年人"}else if(isNaN(result)) {this.outPut= "你输出的信息有误"}else {this.outPut = "岁, 曾经超过了百岁, 还是地球人么"} 
    }
})

// 等价于如下所示, 监督 B 属性
vm.$watch('B', {
    immediate: true,  // 初始化时, 调用一些 handler 函数, 不写这个的话, 第一次不会调用 handler 函数
    handler(newVal, oldVal) {console.log("B 数据变了","最新的",newVal,"旧的",oldVal);
        this.addResult = parseInt(this.A)+parseInt(newVal);
        const result = parseInt(this.addResult);
        if(result <= 6) {this.outPut = "岁是个儿童"}else if(result > 6 && result <= 17) {this.outPut = "岁是个少年"} else if(result > 17 && result  <=40) {this.outPut = "岁是个青年"}else if(result > 40 && result <=65) {this.outPut = "岁是个中年人"}else if(result > 65 && result <=100) {this.outPut = "岁是个老年人"}else if(isNaN(result)) {this.outPut= "你输出的信息有误"}else {this.outPut = "岁, 曾经超过了百岁, 还是地球人么"} 
    }
})

</script>

对于 watch 属性, 是一个十分有用的属性, 如果须要对一些数据做一些监测, 新旧数据的比照, 变换, 达到某些条件时, 做一些逻辑操作, 那么 watch 能够监听 data 上面的属性, 还能够监听计算结果属性

对于 watch 与 $watch 书写的机会

  1. 如果很明确你要监督哪个数据, 在传教概念实例的时候, 就写watch
  2. 如果在创立实例的时候, 你不晓得要监督哪个数据, 后续会依据用户的一些行为, 监测哪些数据, 那么就能够应用 $watch 这个 API
  3. 当被监督的属性变动时, 回调函数主动调用, 进行相干操作
  4. 监督的属性必须存在, 能力进行监督
  5. 监督数据有两种形式一种实例化 Vue 对象时, 传入 watch 配置选项, 另一种是vm.$watch

watch 中的深度监督

下面都是间接的监听 data 上面间接挂载的属性, 当咱们想要监听某个对象下的单个属性时, 那怎么办? 如下所示

const vm = new Vue({
    el: '#root',
    data() {
        return {
            info: {
              name: 'itclanCoder',  // 想要监听 info 对象下某单个属性
              age: 4
            }
        }
    },

    // 监听多级构造中某个属性的变动
    watch: {
        'info.name': {console.log("info 上面的 name 属性扭转了");
         }
    }
    
})

Vue 中, 默认不监测对象外部值的扭转, 如果想要监测对象下的每个属性的变动 (也就是监测多层级构造), 能够设置开启deep: true 配置, 如下所示

const vm = new Vue({
    el: '#root',
    data() {
        return {
            info: {
              name: 'itclanCoder', 
              age: 4
            }
        }
    },

    // 监听多级构造中某个属性的变动
    watch: {
         info: {
             immediate: true, // 初始化时, 立刻调用 handler 函数
             deep: true,  // 开启深度监测
             handler() {console.log("name 和 age 都扭转触发了");
             }
         }
    }
    
})

// 等价于上面这种写法
vm.$watch('info',{
    immediate: true, // 初始化时, 立刻调用 handler 函数
    deep: true,  // 开启深度监测
    handler() {console.log("name 和 age 都扭转触发了");
    }
})

::: tip 留神

 vm.$watch('info',function(newVal,oldVal) {   // 此处不能写箭头函数, 要写一般函数, 否则 this 的绑定就会出问题
            console.log("新值",newVal,"旧值",oldVal);
        },{
            immediate:true,
            deep: true
        })

在变更 (不是替换) 对象或数组时,旧值将与新值雷同,因为它们的援用指向同一个对象 / 数组。Vue 不会保留变更之前值的正本

正告

  1. 但凡 vue 治理的函数不要写箭头函数
  2. 计算属性外面的 get,set 不能写成箭头函数

watch 反对异步工作维持数据

重点内容:

当须要在数据变动时执行异步或开销较大的操作时,应用 watch 这个形式是最有用的, 而 computed 是没有方法做到的(靠的是返回值)

 watch: {
        // 等价于如下
        A(newVal) {// 这里的 newVal 参数, 指的是以后监督属性, 最新的值, 能够写一个, 也能够写两个(newVal,oldVal)
           setTimeout(() => {  // 这里的回调函数不能写成一般函数, 否则 this 就会指向 window,会出问题
                this.addResult = parseInt(newVal)+parseInt(this.B)
                const result = parseInt(this.addResult);
                if(result <= 6) {this.outPut = "岁是个儿童"}else if(result > 6 && result <= 17) {this.outPut = "岁是个少年"} else if(result > 17 && result  <=40) {this.outPut = "岁是个青年"}else if(result > 40 && result <=65) {this.outPut = "岁是个中年人"}else if(result > 65 && result <=100) {this.outPut = "岁是个老年人"}else if(isNaN(result)) {this.outPut= "你输出的信息有误"}else {this.outPut = "岁, 曾经超过了百岁, 还是地球人么"} 
            },2000)
        },
    },
})

有时候, 咱们想要提早多长时间在实现绝对应的逻辑, 那么 watch 就能够无效的去开启一个异步工作

<img src=”https://static01.imgkr.com/temp/f48c1fac362c4028a05fe6cb9b9a204d.png” class=”medium-zoom lazy”>

从下面的图中总结出

  1. computed: 监测的是依赖值, 当依赖值不变的状况下, 会间接读取缓存进行复用, 当依赖值有变动时, 会从新计算
  2. watch: 监测的是属性值, 只有属性发生变化, 都会触发执行回调函数来执行一系列的操作

然而 computed 不行,computed靠的是返回值,watch是靠你本人亲手写代码去批改

计算属性外面是没有方法去开启异步工作, 它必须同步执行, 去保护数据的,然而 watch 却能够

当应用 watchcomputed都能够实现时, 那么举荐应用computed,然而当要解决实现一些异步工作时, 那么就须要应用watch

computedwatch 之间的区别

computed能实现的性能,watch都能够实现

watch能实现的性能,computed不肯定能实现, 例如:watch能够进行一部操作

两个重要的小区别

  1. 所被 vue 治理的函数, 最好写成一般函数, 这样 this 的指向才是 vm 或组件实例对象
  2. 所有不被 vue 所治理的函数 (定时器的回调函数,ajax 的回调函数等 Promise 的回调函数)最好写成箭头函数, 这样 this 的指向才是 vm 或组件实例对象

总结

vue 中实现同一个性能, 对于简略的逻辑性能, 能够应用模板, 其次是办法(但不具备数据缓存的能力), 若逻辑很简单, 须要缓存数据, 则应用计算属性, 而 watch 属性, 同样也能实现

在平时的开发中, 优先应用计算属性, 能够看出它更简略, 不便, 然而想要执行异步工作, 那么就得应用 watch,computed 能做的,watch 也能做, 但反过来, 却不行

原文出处 -vuejs 中的一般办法 / 计算属性 computed 与监听属性 watch 四者的比拟

正文完
 0