VUE响应式原理、watch、计算属性computed
响应式原理
视图更新后做的事
视图内所有插值表达式和指令,都会重新运行和求值!
如果对数组进行修改并不会更新视图,但是数据改变了,
此时通过修改别的可以驱动视图更新的属性,视图更新使得数组的修改可以显示
实现响应式的条件(视图能更新):
- 数据要有劫持
 - 视图上必须出现对应数据
 
(满足以上两个条件的数据 => watcher的依赖)
如何知道一个数据发生了变化?
js对象的属性都有4种描述
- 是否可以修改 writable:true
 - 是否可以删除 configurable:true
 - 是否可以枚举 enumerable:true 即 是否可以for in
 - 值是什么 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);
    }
  }
},