关于 Vue 的数据响应式

180 阅读2分钟

1、ES6 的 getter 、setter 和 Object.defineProperty

  • getter
  • 使用 getter 后,obj1.姓名 就是一个属性,而不再是方法
  • 示例:
let obj1 = {
    姓:'w',
    名:'bh',
    姓名(){
        return this.姓 + this.名
    }
}
console.log(obj1.姓名())

//使用 getter
let obj2 = {
    姓:'w',
    名:'bh',
    get 姓名(){
        return this.姓 + this.名
    }
}
console.log(obj2.姓名)  //输出:wbh
  • setter
  • 不能使用 obj3.姓名('高媛媛'),因为 obj3.姓名 并不是一个方法函数,而是用 set 模拟的属性
  • 示例:
let obj3 = {
    姓:'w',
    名:'bh',
    get 姓名(){
        return this.姓 + this.名
    }
    set 姓名(xxx){
        this.姓 = xxx[0]
        this.名 = xxx.substring(1)
    }
}

obj3.姓名 = '高原源'  

console.log(`姓:${obj.姓},名:${obj.名}`)  //输出:姓:高,名:原源

2、Object.defineProperty()

  • Object.defineProperty()方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
  • 语法:Object.defineProperty(obj, prop, descriptor)
    • obj:要在其上定义属性的对象。
    • prop:要定义或修改的属性的名称。
    • descriptor:将被定义或修改的属性描述符。
  • 示例:
const object1 = {};

Object.defineProperty(object1, 'property1', {
  value: 42,
});

console.log(object1.property)   //输出:42
  • Object.defineProperty的作用:
    • 可以给对象添加属性 value,可以给对象添加 getter/setter
    • getter / setter 用于对属性的读写进行监控

3、对于代理和监听的理解

  • 代理:让 data3 成为 {n:0} 的代理,通过 data3 访问 n 就会转变为对 obj 的操纵,访问 obj 的n,通过 data3 设定一个 n,就是设定 obj 对象中的 n。
  • 示例:
let data3 = proxy({ data:{n:0} }) 

function proxy({data}){  
// function proxy(options){
// const data = options.data => const {data} = options
 
    const obj = {}
    Object.defineProperty(obj, 'n', {
        get(){
            return data.n
        },
        set(value){
            if(value<0)return
            data.n = value
        }
    })
    return obj // obj 就是代理
}

data3.n = -1
console.log(`${data3.n}`)  //输出:0
  • 监听:就是通过 Object.defineProperty 方法把原来的 mayData5 删除并覆盖,从而确保用户无法直接的修改 mayData5 中的内容
  • 示例:
let myData5 = {n:0}
let data5 = proxy2({ data:myData5 }) /

function proxy2({data}){
    let value = data.n
    Object.defineProperty(data, 'n', {
        get(){
            return value
        },
        set(newValue){
            if(newValue<0)return
                value = newValue
        }
     })
        ...
}

4、关于new Vue()对 data 做了什么?

  • const vm = new Vue({data: myData})
  • vue 会让 vm 成为 myData 的代理。
  • vue 会对 myData 的所有属性进行监控。
  • 可以使用 this 来访问到 vm。 this.n === myData.n。
  • 之所以要监控,就是防止 vue 无法得知 myData 的属性变化。
  • vue 得知属性变化才可以使用 render(data) 来更新 UI 和渲染页面。
  • 只要传入一个 myData,原来的 n 就会被改为 get n() 和 set n(value)

5、数据响应式

  • const vm = new Vue({data:{n: 0}})
  • 当修改 vm.n 或 data.n 时,render(data...) 中的 n 就会做出响应的响应。
  • 这个联动的过程就是 vue 的 数据响应式。
  • vue 目前通过 Object.defineProperty 来实现数据响应式。
  • 但是,如果操作原来不存在 data 中的属性呢?
  • 示例:
new Vue({
    data: {
        obj: {
            a: 0 // obj.a 会被 Vue 监听 & 代理
        }
    },
    template: `
        <div>
            {{obj.b}}
            <button @click="setB">set b</button>
        </div>
    `,
    methods: {
        setB() {
            this.obj.b = 1;   //不会显示出 1
        }
    }
}).$mount("#app");
  • Vue 没法监听一开始就不存在的 obj.b

  • 解决方法:

    1. 提前声明好 obj.b
    data: {
        obj: {
            a: 0, // obj.a 会被 Vue 监听 & 代理
            b:undefined
        }
    },
    

    2.使用 Vue.set 或者 this.$set

    Vue.set(this.obj, "b", 1);
    this.$set(this.obj,'b',1)
    // 二选一
    
  • 关于 vue.set 和 this.$set

  • 作用:

    1. 新增 key
    2. 自动创建代理和监听(如果没有创建过)
    3. 触发 UI 更新(并不会立刻更新)

6、data 中有数组怎么办?

  • 由于数组本身的特性,长度无法预估,因此你没法提前声明所有的 key
  • 数组的长度可以一直增加,下标就是 key
  • 因此无法提前给每一个 key 都设定为 undefined,或是每一个都使用 Vue.set 来设置,但是要注意的是 Vue.set 作用于数组时,并不会自动添加监听和代理
  • 解决方法:
    • 使用 Vue 对数组的变更方法:
      1. push()
      2. pop()
      3. shift()
      4. unshift()
      5. splice()
      6. sort()
      7. reverse()
    • 这些方法 (API) 会自动处理对数组该项的监听和代理,并触发视图更新。
    • 原理就是声明一个新的类来继承数组的原本方法

7、个人对 Vue 的数据响应式的理解

  • 当对 Vue 新增一个 data 数据时,通过 Object.defineProperty 方法,将其中的所有属性转化为 setter 和 getter ,从而对其 data 的改变进行监听
  • 再把 vm 作为 data 的代理,对 data 进行监控,vm 在监听到变化后会调用 render(data)
  • UI = render(data)