您的位置:首页 > 新闻 > 会展 > 常德农科院网站_php安防企业网站源码_广告推广方案_广州关键词优化外包

常德农科院网站_php安防企业网站源码_广告推广方案_广州关键词优化外包

2025/3/12 11:22:07 来源:https://blog.csdn.net/m0_74350516/article/details/146187964  浏览:    关键词:常德农科院网站_php安防企业网站源码_广告推广方案_广州关键词优化外包
常德农科院网站_php安防企业网站源码_广告推广方案_广州关键词优化外包

Vue计算&侦听属性:深入解析与实践

2.4 计算属性

2.4.1 插值语法实现反转字符串

在Vue开发中,我们常常会遇到需要对数据进行处理并展示在页面上的需求。以反转字符串为例,假设我们希望用户输入一个字符串,然后将其反转后展示在页面中。使用插值语法来实现这一功能,代码如下:

<body><!-- 需求:用户输入字符串,然后反转展示在页面中 --><div id="app"><h1>{{msg}}</h1>输入的信息:<input type="text" v-model="info" /><hr><!--直接写在插值语法中,有以下三个问题:1. 可读性差。2. 代码没有得到复用。3. 难以维护。-->反转的信息:{{info.split('').reverse().join('')}}<hr>反转的信息:{{info.split('').reverse().join('')}}<hr>反转的信息:{{info.split('').reverse().join('')}}<hr></div><script>const vm = new Vue({el: "#app",data: {msg: "插值语法 - 反转字符串案例",info: ""}});</script>
</body>

在这段代码中,我们直接在插值语法 {{}} 内使用JavaScript表达式 info.split('').reverse().join('') 来实现字符串的反转。然而,这种方式存在明显的弊端。首先,代码的可读性较差,因为插值语法内的表达式逻辑较为复杂,不便于阅读和理解。其次,当需要在多个地方展示反转后的字符串时,相同的表达式需要重复书写,代码没有得到复用。最后,一旦逻辑发生变化,例如反转算法需要调整,就需要在所有使用该表达式的地方进行修改,维护成本较高。正如Vue官网所说:模版内的表达式以及指令语法非常便利,但设计它们的初衷是用于简单运算,在模版中放入太多的逻辑会让模版过重且难以维护。

2.4.2 方法实现反转字符串

为了解决插值语法的问题,我们可以将字符串反转的逻辑封装到一个方法中。代码如下:

<body><div id="app"><h1>{{msg}}</h1>输入的信息:<input type="text" v-model="info" /><br><!-- 在插值语法中可以调用方法,小括号不能省略。这个方法需要是Vue实例所管理的。 -->反转的信息:{{reverseInfo()}}<br><!-- 缺点:效率有问题 会重复调用 -->反转的信息:{{reverseInfo()}}<br>反转的信息:{{reverseInfo()}}<br>反转的信息:{{reverseInfo()}}<br></div><script>const vm = new Vue({el: "#app",data: {msg: "计算属性 - 反转字符串案例",info: ""},methods: {// 反转信息的方法reverseInfo() {return this.info.split("").reverse().join("");}}});</script>
</body>

在上述代码中,我们在Vue实例的 methods 选项中定义了 reverseInfo 方法,该方法实现了字符串反转的功能。在模板中,通过 {{reverseInfo()}} 调用该方法来展示反转后的字符串。这种方式虽然解决了代码复用和一定程度的维护性问题,但在效率上存在不足。当页面中有多个地方调用 reverseInfo() 方法时,每次调用都会重新执行该方法,即使 info 的值并未发生改变。如果 reverseInfo 方法内部的逻辑较为复杂,多次重复执行会消耗较多的性能资源。

2.4.3 计算属性实现反转字符串

1. 什么是计算属性?

计算属性是Vue提供的一种强大特性,它允许我们使用Vue的原有属性(通常是 data 对象当中的属性),经过一系列的运算或计算,最终得到一个全新的属性。这个全新的属性有自己独立的属性名和属性值,并且与 data 中的原始属性相关联,但又具有相对独立的存在意义。例如,我们可以基于 data 中的一个字符串属性,通过特定的运算得到一个反转后的字符串属性,这个反转后的字符串属性就是一个计算属性。

2. 计算属性的作用?
  • 代码复用:将复杂的数据处理逻辑封装在计算属性中,避免在模板中重复书写相同的表达式,提高了代码的复用性。例如在反转字符串的案例中,使用计算属性后,无论在模板的多少个地方需要展示反转后的字符串,都只需引用计算属性即可。
  • 便于维护:当数据处理逻辑发生变化时,只需在计算属性的定义处进行修改,而无需在模板中多处查找和修改相同的表达式,降低了维护成本。例如,如果我们要改变字符串反转的算法,只需要修改计算属性中的逻辑,模板中的引用无需变动。
  • 执行效率高:计算属性具有缓存机制,只有当它所依赖的 data 中的属性值发生变化时,才会重新计算。如果多次访问计算属性且其依赖的属性值未改变,计算属性不会重复计算,而是直接返回之前缓存的结果,从而提高了执行效率。这在复杂计算场景下优势尤为明显。
3. 计算属性怎么用?

计算属性需要使用新的配置项 computed。其语法格式如下:

computed: {// 这是一个计算属性,可以有多个计算属性 计算属性1: {// 当读取计算属性1的值的时候或该计算属性所关联的Vue原有属性的值发生变化时,getter方法被自动调用。get() { },// 当修改计算属性1的值的时候,setter方法被自动调用。set(val) { }},计算属性2: {}
}

需要注意以下几点:

  • 不能在 set 方法里面改原有属性的值:在 set 方法中修改计算属性所依赖的原始属性值,可能会导致无限循环等问题。因为计算属性的值是基于原始属性计算得出的,修改原始属性又会触发计算属性的重新计算,从而陷入死循环。
  • 计算属性通过 vm.$datavm._data 是无法访问的:计算属性是Vue实例基于 data 中的属性计算生成的,它并不直接存储在 vm.$datavm._data 中,而是通过 computed 配置项进行管理和访问。
  • 计算属性的 getter/setter 方法中的 thisvm:在 gettersetter 方法内部,this 指向Vue实例本身,这使得我们可以方便地访问 data 中的属性和其他方法。例如在 getter 方法中可以通过 this.info 访问 data 中的 info 属性来进行计算。
  • 计算属性的 getter 方法的调用时机
    • 第一个时机:初次访问该属性。当模板中首次出现对计算属性的引用时,getter 方法会被调用,计算并返回属性值。
    • 第二个时机:计算属性所依赖的数据发生变化时。例如,某个计算属性依赖于 data 中的 info 属性,当 info 的值发生改变时,该计算属性的 getter 方法会再次被调用,重新计算属性值,以保证数据的一致性。
  • 计算属性的 setter 方法的调用时机:当计算属性被修改时。(在 setter 方法中通常是修改属性,因为只有当属性值变化时,计算属性的值就会联动更新。注意:计算属性的值被修改并不会联动更新属性的值。)例如,在模板中有一个双向绑定到计算属性的输入框,当用户在输入框中修改值时,会触发计算属性的 setter 方法。
  • 计算属性没有真正的值,每一次都是依赖 data 属性计算出来的:计算属性本身并不存储实际的值,它的值是根据其依赖的 data 属性动态计算得出的。这也是为什么当依赖的 data 属性变化时,计算属性会重新计算。
  • 计算属性的 gettersetter 方法不能使用箭头函数,因为箭头函数的 this 不是 vm,而是 window:由于箭头函数没有自己独立的 this,它的 this 是继承自外层作用域,在Vue的计算属性中使用箭头函数会导致 this 指向错误,无法正确访问Vue实例的 datamethods

下面是一个完整的使用计算属性实现字符串反转的示例:

<body><div id="app"><h1>{{msg}}</h1>输入的信息:<input type="text" v-model="info" /><br>反转的信息:{{reversedInfo}}<br>反转的信息: <input type="text" v-model="reversedInfo" /><!-- 多次调用计算属性,只会执行一次,效率高 -->{{fun}}<br><!-- {{fun}}<br>{{fun}}<br>{{fun}}<br>{{fun}}<br> --><!-- 多次调用方法,每次都是执行,效率低 --><!-- {{hello()}}<br>{{hello()}}<br>{{hello()}}<br>{{hello()}}<br>{{hello()}}<br> --></div><script>const vm = new Vue({el: "#app",data: {msg: "计算属性 - 反转字符串案例",info: ""},methods: {hello() {console.log("hello方法执行了");return "hello";}},computed: {// 可以定义多个计算属性fun: {// get方法的调用时机get() {console.log("getter方法调用了");//console.log(this === vm)return "haha" + this.info;},// 不能使用箭头函数,使用箭头函数会导致this的指向是:window// get:()=>{//     console.log('getter方法调用了')//     console.log(this === vm)//     return 'haha'// },set(val) {console.log("setter方法调用了");//console.log(this === vm)}},// 完整写法/* reversedInfo : { get(){return this.info.split('').reverse().join('')},// 当修改计算属性的时候,set方法被自动调用。set(val){//console.log('setter方法被调用了。')// 不能这么更改计算属性的值,这样做就递归了。//this.reversedInfo = val// 怎么修改计算属性呢?原理:计算属性的值变还是不变,取决于计算属性关联的Vue原始属性的值。// 也就是说:reversedInfo变还是不变,取决于info属性的值变不变。// 将值赋给info,通过info来实现反推修改,//例如,需要将翻转值修改为hello,则需要反转给info,通过info再反转回来// 本质上:修改计算属性,实际上就是通过修改Vue的原始属性来实现的。this.info = val.split('').reverse().join('')}} */// 简写形式:set不需要的时候。reversedInfo() {return this.info.split("").reverse().join("");}}});</script>
</body>

在这个示例中,我们定义了 reversedInfo 计算属性。其中,reversedInfogetter 方法实现了字符串反转的逻辑,当 info 的值发生变化时,reversedInfo 会自动重新计算。并且我们还展示了计算属性的缓存特性,多次调用 fun 计算属性时,只要其依赖的 info 未改变,getter 方法不会重复执行。

4、计算属性的简写形式

当我们只需要考虑读取计算属性的值,而不涉及修改操作时,可以启用计算属性的简写形式。在这种简写形式中,计算属性后面跟的是一个方法,该方法返回 getter 被调用后的值。示例代码如下:

computed: {reversedInfo() {console.log('getter被调用了');return this.info.split('').reverse().join('')}
}

在这个例子中,reversedInfo 就是一个使用简写形式的计算属性。它等同于完整写法中只定义了 getter 方法的情况,省略了 setter 方法的定义,因为在该场景下不需要对计算属性进行赋值操作。

2.5 侦听属性

2.5.1 侦听属性作用

侦听属性的核心作用是监视某个属性的变化。当被监视的属性一旦发生改变时,就会执行预先定义好的某段代码。与计算属性相比,侦听属性可以实现异步流程,这是它的一个重要特点。计算属性主要用于根据已有数据进行同步计算并返回结果,而侦听属性更侧重于在属性值变化时触发特定的行为,这些行为可以是同步的,也可以是异步的,并且不需要像计算属性那样依赖 return 语句来返回值。

2.5.2 watch配置项

在Vue中,监视属性变化需要使用 watch 配置项。通过 watch 配置项,我们可以监视多个属性,监视哪个属性,就把该属性的名字拿过来即可。具体有以下几种情况:

  • 可以监视Vue的原有属性:例如,我们可以监视 data 中定义的普通属性,像 number 属性,当 number 的值发生变化时,触发相应的处理逻辑。
  • 如果监视的属性具有多级结构,一定要添加单引号:当监视的属性是对象的嵌套属性,如 a.b,必须使用单引号将属性路径括起来,即 'a.b'。这样Vue才能正确识别要监视的具体属性。
  • 无法直接监视对象深层次属性:如果要监视的对象中某个深层次属性,如 a.b,而 b 属性在对象初始化时压根不存在,那么直接监视 b 是无效的。在这种情况下,需要特殊处理,例如使用深度监视来确保能够捕获到深层次属性的变化。
  • 启用深度监视,默认是不开启深度监视的:深度监视用于监视对象或数组的嵌套属性的变化。默认情况下,Vue的 watch 配置项不会自动监视对象内部深层次属性的变化,只有当对象或数组的引用发生改变时才会触发监视。如果需要监视对象内部深层次属性的变化,就需要手动启用深度监视。
  • 也可以监视计算属性:计算属性的值是基于其他属性计算得出的,我们同样可以通过 watch 配置项来监视计算属性的变化,当计算属性的值发生改变时,执行相应的逻辑。

2.5.3 如何深度监视:

  • 监视多级结构中某个属性的变化:写法是 'a.b.c' : {}。注意这里要使用单引号将属性路径括起来。例如,如果有一个对象 obj,其结构为 obj = {a: {b: {c: 0}}},要监视 c 属性的变化,就可以在 watch 配置项中使用 'a.b.c' : {} 的形式,并在对象中定义相应的处理逻辑。
  • 监视多级结构中所有属性的变化:可以通过添加深度监视来完成,即在 watch 配置项中设置 deep : true。例如,对于对象 obj = {a: {b: 0, c: 0}, d: 0},如果要监视 obj 内部所有属性(包括 a 对象中的 bc 属性)的变化,就可以这样配置:
watch: {obj: {deep: true,handler(newValue, oldValue) {console.log("对象内部属性发生了变化");}}
}

2.5.4 如何后期添加监视:

  • 调用API:可以通过调用 vm.$watch('number1', {}) 来后期添加对某个属性的监视。其中,'number1' 是要监视的属性名,后面的对象可以配置监视的相关选项,如 immediate(是否在初始化时立即调用 handler 方法)、deep(是否启用深度监视)以及 handler(属性变化时执行的处理函数)等。

2.5.5 watch的简写:

  • 简写的前提:当不需要配置 immediatedeep 时,可以使用简写形式。
  • 如何简写:在 watch 配置项中,直接使用属性名作为键,属性值为一个函数,该函数接收两个参数 newVal(新值)和 oldVal(旧值)。例如:
watch: {number1(newVal, oldVal) {console.log(newVal, oldVal);}
}
  • 后期添加的监视如何简写:对于后期添加的监视,可以使用 vm.$watch('number1', function(newVal, oldVal){}) 的形式进行简写。其中,'number1' 是要监视的属性名,后面的函数是属性变化时执行的处理函数,接收 newValoldVal 两个参数。

以下是一个完整展示 watch 配置项使用的示例代码:

<body><div id="app"><h1>{{msg}}</h1>数字:<input type="text" v-model="number"><br>数字:<input type="text" v-model="a.b"><br>数字:<input type="text" v-model="a.c"><br>数字:<input type="text" v-model="a.d.e.f"><br>数字(后期添加监视):<input type="text" v-model="number2"><br></div><script>const vm = new Vue({el: "#app",data: {msg: "侦听属性的变化",// 原有属性number: 0,// 多层次属性a: {b: 0,c: 0,d: {e: {f: 0}}},number2: 0},// 计算属性computed: {hehe() {return "haha" + this.number;}},watch: {//1、可以监视多个属性,监视哪个属性,请把这个属性的名字拿过来即可。//1.1 可以监视Vue的原有属性/* number : {// 初始化的时候,调用一次handler方法。immediate : true,// 这里有一个固定写死的方法,方法名必须叫做:handler// handler方法什么时候被调用呢?当被监视的属性发生变化的时候,handler就会自动调用一次。// handler方法上有两个参数:第一个参数newValue,第二个参数是oldValue// newValue是属性值改变之后的新值。// oldValue是属性值改变之前的旧值。handler(newValue, oldValue){console.log(newValue, oldValue)// this是当前的Vue实例。// 如果该函数是箭头函数,这个this是window对象。不建议使用箭头函数。console.log(this)}}, *///1.2 如果监视的属性具有多级结构,一定要添加单引号:'a.b'/* 'a.b' : {  handler(newValue, oldValue){console.log('@')} },'a.c' : {  handler(newValue, oldValue){console.log('@')} }, */// 无法监视b属性,因为b属性压根不存在。/* b : {  handler(newValue, oldValue){console.log('@')} } *///1.3 启用深度监视,默认是不开启深度监视的。a: {// 什么时候开启深度监视:当你需要监视一个具有多级结构的属性,并且监视所有的属性,需要启用深度监视。deep: true,handler(newValue, oldValue) {console.log("@");}},//1.4 也可以监视计算属性/* hehe : {handler(a, b){console.log(a, b)}} *///2、 监视某个属性的时候,也有简写形式,什么时候启用简写形式?// 当只有handler回调函数的时候,可以使用简写形式。number(newValue, oldValue) {console.log(newValue, oldValue);}}});//3、 如何后期添加监视?调用Vue相关的API即可。{}要么是对象,要么是一个回调。//3.1 语法:vm.$watch('被监视的属性名', {})/* vm.$watch('number2', {immediate : true,deep : true,handler(newValue, oldValue){console.log(newValue, oldValue)}}) *///3.2 这是后期添加监视的简写形式。vm.$watch("number2", function (newValue, oldValue) {console.log(newValue, oldValue);});</script>
</body>

2.5.6 computed和watch如何选择?

在实际开发中,computedwatch 都可以用于响应数据的变化,但它们的使用场景有所不同。以下通过一个比较大小的案例来详细说明如何选择。

2.5.6.1 watch实现
<body><div id="app"><h1>{{msg}}</h1>数值1:<input type="number" v-model="num1"><br>数值2:<input type="number" v-model="num2"><br>比较大小:{{compareResult}}</div><script>const vm = new Vue({el: "#app",data: {msg: "比较大小的案例",num1: 0,num2: 0,compareResult: ""},watch: {// 监视num1num1: {immediate: true,handler(val) {let result = val - this.num2;if (result == 0) {this.compareResult = val + " = " + this.num2;} else if (result > 0) {this.compareResult = val + " > " + this.num2;} else {this.compareResult = val + " < " + this.num2;}}},// 监视num2num2: {immediate: true,handler(val) {let result = this.num1 - val;if (result == 0) {this.compareResult = this.num1 + " = " + val;} else if (result > 0) {this.compareResult = this.num1 + " > " + val;} else {this.compareResult = this.num1 + " < " + val;}}}}});</script>
</body>

在这个示例中,我们使用 watch 分别监视 num1num2 的变化。当 num1num2 的值发生改变时,handler 函数会被调用,通过比较两个数值的大小,更新 compareResult 的值,从而在页面上显示比较结果。

2.5.6.2 computed实现
<body><div id="app"><h1>{{msg}}</h1>数值1:<input type="number" v-model="num1"><br>数值2:<input type="number" v-model="num2"><br>比较大小:{{compareResult}}</div><script>const vm = new Vue({el: "#app",data: {msg: "比较大小的案例",num1: 0,num2: 0},computed: {// 计算属性的简写形式compareResult() {let result = this.num1 - this.num2;if (result == 0) {return this.num1 + " = " + this.num2;} else if (result > 0) {return this.num1 + " > " + this.num2;} else {return this.num1 + " < " + this.num2;}}}});</script>
</body>

这里使用 computed 来实现相同的比较大小功能。compareResult 计算属性根据 num1num2 的值进行比较,并返回相应的比较结果。当 num1num2 的值发生变化时,compareResult 会自动重新计算,从而更新页面上的显示。

2.5.6.3 总结
  • 优先选择computed:以上比较大小的案例可以用 computed 完成,并且比 watch 还要简单。所以在实际开发中,要遵守一个原则:如果 computedwatch 都能够完成的功能,优先选择 computed。这是因为 computed 具有缓存机制,只有当它所依赖的属性值发生变化时才会重新计算,而 watch 则是在属性值变化时就会执行相应的处理函数,可能会导致不必要的性能开销。
  • 选择watch的情况:如果要开启异步任务,只能选择 watch。因为 computed 依靠 return 语句来返回计算结果,它是同步的,无法直接处理异步操作。而 watch 不需要依赖 return,可以在 handler 函数中方便地执行异步任务,如使用 setTimeout 进行延迟操作、发起AJAX请求等。例如,在比较大小的案例中,如果需要延迟3s出现结果,就可以在 watchhandler 函数中使用 setTimeout 来实现:
watch: {// 监视num1num1: {immediate: true,handler(val) {let result = val - this.num2;// 需求2:3s后出现比较结果//  此时使用箭头函数,箭头函数没有this,会向上找到num1,num1是vm的属性,//   如果将此时箭头函数转成普通函数,this就会是windowsetTimeout(() => {if (result == 0) {this.compareResult = val + " = " + this.num2;} else if (result > 0) {this.compareResult = val + " > " + this.num2;} else {this.compareResult = val + " < " + this.num2;}}, 3000);}},// 监视num2num2: {immediate: true,handler(val) {let result = this.num1 - val;setTimeout(() => {if (result == 0) {this.compareResult = this.num1 + " = " + val;} else if (result > 0) {this.compareResult = this.num1 + " > " + val;} else {this.compareResult = this.num1 + " < " + val;}}, 3000);}}
}

而如果在 computed 中使用 setTimeout,由于 setTimeout 是异步的,它的 thiswindow,并且 computed 不能直接返回异步操作的结果,所以无法实现延迟显示比较结果的需求。

2.5.7 关于函数的写法,写普通函数还是箭头函数?

在Vue开发中,关于函数的写法,无论是普通函数还是箭头函数,目标都是为了让 thisvm 相等,以便能够正确访问Vue实例的属性和方法。具体来说:

  • 所有Vue管理的函数,建议写成普通函数:例如在 methods 中定义的方法、computedgettersetter 方法、watchhandler 函数等,这些函数都是由Vue进行管理和调用的,使用普通函数可以确保 this 指向Vue实例 vm
  • 所有不属于Vue管理的函数,例如 setTimeout 的回调函数、Promise 的回调函数、AJAX 的回调函数,建议使用箭头函数:这些函数通常是在JavaScript的原生环境中执行,使用箭头函数可以避免 this 指向的问题。因为箭头函数没有自己独立的 this,它的 this 是继承自外层作用域,在这种情况下,外层作用域通常是Vue实例,所以可以正确访问Vue实例的属性和方法。例如在 watch 中使用 setTimeout 时,使用箭头函数作为 setTimeout 的回调函数,可以确保在回调函数中能够正确访问Vue实例的属性和方法,如 this.compareResult

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com