Vue-2-响应式原理、watch、计算属性computed


VUE响应式原理、watch、计算属性computed

响应式原理

视图更新后做的事

视图内所有插值表达式和指令,都会重新运行和求值!

如果对数组进行修改并不会更新视图,但是数据改变了,

此时通过修改别的可以驱动视图更新的属性,视图更新使得数组的修改可以显示

实现响应式的条件(视图能更新):

  1. 数据要有劫持
  2. 视图上必须出现对应数据

(满足以上两个条件的数据 => watcher的依赖)

如何知道一个数据发生了变化?

js对象的属性都有4种描述

  1. 是否可以修改 writable:true
  2. 是否可以删除 configurable:true
  3. 是否可以枚举 enumerable:true 即 是否可以for in
  4. 值是什么 value

通过Object.getOwnPropertyDescriptor()可以获得对象额属性的4种描述

let desc = Object.getOwnPropertyDescriptor(obj,'name')

通过Object.defineProperty()来修改对象的属性的描述

Object.defineProperty(obj,'name',{
    value:'asfa',
    enumerable:false,
})

这里Vue源码内通过Object.defineProperty()来进行数据劫持(数据监听)。通过set和get方法

let val = data.msg;
Object.defineProperty(data,'msg',{
    // 当msg被访问时候触发
    get(){
        console.log('msg被访问')
        return val
    },
    // 当msg被修改时触发,可以在这里写dom操作,且
    // 参数就是新修改的值
    set(newVal){
        val = newVal;
        console.log('msg被修改了')
        oDiv.innerText = newVal
    }
})

如何在新增一个属性的同时给它添加数据劫持?

当一个obj没有那么属性时,当它实例化时,就没有办法给name进行数据劫持,在后续的对name修改,视图不会更新

用实例方法添加初始值和数据劫持,$是为了与用户的set(可能存在)进行区分

this.$set(this.obj,'name',1000)

用vue的静态方法添加

Vue.set(this.obj,'name',1000)

const vm = new Vue({
  el: '#app',
  data: {
    obj:{}
  },
  methods: {
    fn() {
      // 新增属性name.(不算修改)
      // this.obj.name = 1000;
      // 用实例方法添加初始值和数据劫持
      // this.$set(this.obj, 'name', 1000);
      // 通过Vue的静态方法添加.
      // Vue.set(this.obj, 'name', 1000);

      // 访问实例的默认属性data.
      // console.log(this.$data);
    }
  }
})

或者直接对已经被劫持的对象添加

this.obj.name = 1000 不行!

this.obj = { name : 1000} 可以!

计算属性和watch

计算属性是watch的一种特例.

计算属性能够实现的功能,watch都能实现.

watch能够实现的功能,*计算属性不一定能够实现*.例如异步操作.

计算属性内部不能包含异步操作.

但是计算机属性有缓冲,尽可能使用前一次的计算结果

这样是为了避免不必要的函数调用

可以手动关闭cache: false,

computed: {
  num3: {
    // 关闭计算属性的缓存功能.
    // 这样做会导致每次视图更新,计算属性的get都会重复调用.返回计算属性的值.
    cache: false,
    get() {
      console.log('num3计算属性函数触发了')
      return this.num1 + this.num2
    }
  }
},

计算属性

计算属性的值,依赖别的数据的值的变化而变化

写法一:写成函数

当函数里面的值变化时都会触发改函数重新计算并返回最终的值

函数名就是我们需要使用的数据名

data: {
  num1: Math.floor(Math.random() * 10) + 1,
  num2: Math.floor(Math.random() * 10) + 1,
},
// 计算属性.可以简写成一个函数.return最终的值.
// 这个计算属性的函数,在num1或者num2变化时,都会自动触发返回新的值.
// 函数名就是我们需要使用的数据名.
computed: {
  num3() {
    console.log('num3函数触发了');
    return this.num1 + this.num2
  }
},

写法二:对象

通过属性的值为get()返回的值

如果要手动修改计算属性需要设置set()

data: {
  num1: Math.floor(Math.random() * 10) + 1,
  num2: Math.floor(Math.random() * 10) + 1,
},
// 计算属性的函数简写,实际上是全写的只有get的形式.
computed: {
  num3: {
    get() {
      return this.num1 + this.num2
    },
    set(newVal) {
      this.num1 = newVal - this.num2
    }
  }
},

watch

类似与监听属性变化,变化时候就触发,需要预先给定属性名,来接收变化

默认是没有立即执行的

data: {
  num1: Math.floor(Math.random() * 10) + 1,
  num2: Math.floor(Math.random() * 10) + 1,
  num3: 0
},
// 侦测属性(监听)
watch: {
  // 当num1变化时,就触发这个函数,
  num1() {
    this.num3 = this.num1 + this.num2;
  },
  // 当num2变化时,就触发这个函数
  num2() {
    this.num3 = this.num1 + this.num2;
  }
},

如要立即执行

watch: { 
  num1: {
    // 立即,默认触发一次handler.
    immediate: true,
    // handler在每次num1变化时都会触发.
    handler() {
      this.num3 = this.num1 + this.num2;
    }
  },
  // 当num2变化时,就触发这个函数
  num2: {
    immediate: true,
    // handler在每次num2变化时都会触发.
    handler() {
      this.num3 = this.num1 + this.num2;
    }
  }
},

异步:

watch: {
  num1: {
    immediate: true,
    handler() {
      setTimeout(() => {
        this.num3 = this.num1 + this.num2
      }, 3000);
    }
  }
},