【Vue】Vue.js中watch、computed以及methods的联系和区别

computed计算属性对比于methods

  1. computed响应式,而methods非响应;computed在调用时只有当其引用的响应式属性改变时才会调用函数重新计算值(存在缓存),而methods是一个函数,每次调用就是执行其函数式
  2. computed中的成员可以只定义一个函数作为只读属性,也可以自定义get/set作为读写属性
  3. computed以vue对象的属性形式存在

在实际开发中,使用
computed
mothods往往都能起到作用——返回或处理一个我们要的值,但是适用场景不同;比如:当我们要去时刻监控一个视图层对应的数据层的值的变化时,使用
computed就比较合理了,因为
computed可缓存的,只要数据层所依赖的值不改变,
computed就不会改变,而只要变了 ,
computed的值就会实时更新到视图层上,即
computed是响应式的。

而在这个例子中,如果使用watch也可以实现,但是那就是对视图层对应的数据层的值的依赖数据进行监听,发生变化时再调用相应的函数更改该值,那么watchcomputed又有什么区别呢? 异同如下:

computed计算属性对比于watch侦听器:

  1. 相同点:都是vue对监听器的实现,都起到监听/依赖一个数据并进行处理的作用
  2. 在应用中,computed主要应用于同步数据,而watch是用来观测一个值的变化去实现一段开销较大的复杂业务逻辑或执行异步操作
  3. 能用computed优先使用computed,避免当我们需要得到的值依赖于多个数据时多次调用watch的尴尬
  4. watch监听的值接收两个参数——新值、旧值 ,可以设置在初始化时调用

    例:

    watch:监听一个属性值的变化并执行相应的函数

    var vm = new Vue({
        el: '#demo',
      data: {
          firstName: 'Foo',
        lastName: 'Bar',
        fullName:"Foo Bar"
      },
      watch:{
        firstName:function(val){
          this.fullName = val+this.lastName
        },
        lastName:function(val){
          this.fullName = this.firstName+val
        }
      }
    })

    computed:依赖其他属性所计算出来的值

    var vm  = new Vue({
      el:'#demo',
      data:{
            firstName: 'Foo',
        lastName:'Bar'
      },
      computed:{
        fullName(){
          return this.firstName+this.lastName;
        }
      }
    })

高级用法:

计算属性的setter

var vm = new Vue({
  el:'#demo',
  data:{
    firstName:'Foo',
    lastName:'Bar'
  },
  computed:{
    fullName:{
      get(){
          return this.firstName + '·' + this.lastName        
      },
      set(newVal){
        var name = NewVal.split('·')
        this.firstName = name[0];
        this.lastName = name[name.length-1]
      }
    }
  }
})
//运行vm.fullName = 'Kobe Bryant'时,set方法会被调用,vm.firstName和vm.lastName的值会被更新

侦听器的handler方法和immediate属性

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
    }
  }
})

如果想firstName在第一次被绑定的时候就执行:

watch: {
  firstName: {
    handler(val){
      console.log('第一次执行了')
      this.fullName = val + '·' + this.lastName
    },
    immediate:true//在watch中声明后立即执行handler
  }
}

侦听器的deep属性

var vm = new Vue({
  el:'#demo',
  data:{
    item:{
      a:'',
      b:''
    }
  },
  watch:{
    item:{
      handler(val){
        console.log('item.a changed')
      },
      immediate: true
    }
  }
})
//运行vm.item.a = '123',发现控制台没有打印“item.a changed”

改变item.a的值发现控制台没有打印字符串,这是因为vue无法检测到对象属性的添加或者删除。由于vue会在初始化实例时给实例的属性执行getter/setter转化过程,所以属性必须在data对象上存在才能让vue转换它,才能是响应式的

默认情况下watch只监听对对象的引用,如当this.item = {a: '123',b:'123'}执行时handler就会执行,

深度遍历:
watch: {
  obj: {
    handler(val) {
      console.log('item.a changed')
    },
      immediate: true,
      deep: true
  }
}

deep的作用是:在对象一层层往下遍历,每一层都加上侦听器

优化

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

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

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

源码实现:
/* @flow */

import { _Set as Set, isObject } from '../util/index'
import type { SimpleSet } from '../util/index'
import VNode from '../vdom/vnode'

const seenObjects = new Set()

/**
 * Recursively traverse an object to evoke all converted
 * getters, so that every nested property inside the object
 * is collected as a "deep" dependency.
 */
export function traverse (val: any) {
  _traverse(val, seenObjects)
  seenObjects.clear()
}

function _traverse (val: any, seen: SimpleSet) {
  let i, keys
  const isA = Array.isArray(val)
  if ((!isA && !isObject(val)) || Object.isFrozen(val) || val instanceof VNode) {
    return
  }
  if (val.__ob__) {
    const depId = val.__ob__.dep.id
    if (seen.has(depId)) {
      return
    }
    seen.add(depId)
  }
  if (isA) {
    i = val.length
    while (i--) _traverse(val[i], seen)
  } else {
    keys = Object.keys(val)
    i = keys.length
    while (i--) _traverse(val[keys[i]], seen)
  }
}

如果设置this.deep == true,则触发每个深层对象的依赖,追踪其变化。traverse方法递归每一个对象或者数组,触发它们的getter,使得对象或数组的每一个成员都被依赖收集,形成一个“深(deep)”依赖关系。这个函数实现还有一个小的优化,遍历过程中会把子响应式对象通过它们的 dep.id 记录到 seenObjects,避免以后重复访问。

参考学习:

https://cn.vuejs.org/v2/guide…

https://juejin.im/post/5b87f1…

    原文作者:bbbingo
    原文地址: https://segmentfault.com/a/1190000019658505
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞