浅析Vue数据响应式

692 阅读1分钟

什么是Vue数据响应式

一个物体能对外界的刺激做出反应,它就是响应式的

  • 我打你一拳,你会喊疼,那么你就是响应式的
  • 当我修改Vue实例中的数据时,视图就会重新渲染,出现最新的内容。这就是Vue的数据响应式 例:
const vm = new Vue({
  components: { Demo },
  data: {
    n: 0
  },
  template: `
   <div>
    {{n}}
    <button @click="add">+1</button>
   </div>
  `,
  methods: {
    add() {
      this.n += 1
    }
  }
}
).$mount('#run')

数字n在页面上显示的是0,当我按下+1按钮时,数字+1,这就是数据响应式

响应式原理

Vue通过Object.defineProperty函数让我们能够精确地添加或修改对象地属性,getter和setter来读和写内容

Object.defineProperty的用法:

let data1 = {}

Object.defineProperty(data1, 'n', {
  value: 0
})
//用 Object.defineProperty 定义 n

getter和setter的用法:

let obj = {
    a: 1,
    get b(){
        return this.a+1;
    },
    set c(value){
        this.a += value
    }
}
console.log(obj.a)      // 1,当前obj 中 a 的值
console.log(obj.b)      // 2,不需要加括号直接调用 b 方法
obj.c = 10
console.log(obj.a)      // 11,通过 c 方法设置 a 的值
console.log(obj.b)      // 12

当Object.defineProperty和getter、setter组合起来使用,我们就可以实现一个对象控制另一个对象的读写,也就是代理

let data1 = proxy({ data:{n:0} }) // 括号里是匿名对象,无法访问

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

从上面的代码,对比Vue,看出什么相似之处了吗?

let data1 = proxy({ data:{n:0} }) ≈ vm=new Vue({data:mydata})

现在我们再来看Vue到底做了什么,是不是就有了一点头绪呢?

vm=new Vue({data:mydata})

  1. Vue会让vm成为mydata的代理
  2. 会对mydata所有属性进行监控
  3. 当数据改变时触发UI更新

Vue的data的bug

我们使用Object.defineProperty(obj,'n',{...})的时候,必须要有一个'n',才能对其进行监听和代理,而且Vue为了节约计算能力,只会检查第一层属性,如果n在第二层,是没有警告的。如果忘记设这个'n'怎么办?

比如这样:

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; //这是不会有任何警告或提示,但是obj.b = 1并不会生效
    }
  }
}).$mount("#app");

解决方法:

使用Vue.set或this.$set(这俩是一模一样的,随便用哪个都行)

还是刚才的代码,这次我们使用Vue.set

new Vue({
  data: {
    obj: {
      a: 0 // obj.a 会被 Vue 监听 & 代理
    }
  },
  template: `
    <div>
      {{obj.b}}
      <button @click="setB">set b</button>
    </div>
  `,
  methods: {
    setB() {
      Vue.set(this.obj,'b',1)//这次就跑通了
    }
  }
}).$mount("#app");

Vue.set做了什么?

  1. 新增key
  2. 自动创建代理和监听(如果没有创建过的话)
  3. 触发UI更新

数组的变异方法

  • 数组没有办法提前声明所有的key,因为数组的key是下标,而数组的长度是可以一直增加的,比如这样,点击按钮后并不会添加d到数组,因为数组内并没有下标为3的key
new Vue({
  data: {
    array: ["a", "b", "c"]
  },
  template: `
    <div>
      {{array}}
      <button @click="setD">set d</button>
    </div>
  `,
  methods: {
    setD() {
      this.array[3] = "d"; //点击按钮后,页面中并不会添加d
      
    }
  }
}).$mount("#app");

解决方法:

尤雨溪给我们提供了便利的做法,直接使用push

new Vue({
  data: {
    array: ["a", "b", "c"]
  },
  template: `
    <div>
      {{array}}
      <button @click="setD">set d</button>
    </div>
  `,
  methods: {
    setD() {
      this.array.push('d')//这样就将'd'push到了数组array中
      
    }
  }
}).$mount("#app");

尤雨溪提供的这个push方法是被修改过的,它包含了额外的一层原型,这层原型共有七个API:push(新增)、pop(弹出最后一个)、shift(弹出第一个)、unshift(在第一个新增)、splice(从中间删除某一项)、sort(正序)、reverse(倒序),而这层原型之后才指向真正的push