Skip to content

深入响应式原理

数据响应式举例个来说就是,一个物体对于外界的刺激能够做出相应的反应。使用 Vue进行开发时,一旦页面上的数据在 JS内进行了跟新,不需要开发者修改渲染的代码,Vue会自动进行跟新。

这是因为Vue在实例化声明数据时进行了监听和代理,若数据发生了改变,会监听到变化对此进行重新渲染。

Vue2如何实现数据响应式

当你把一个普通的 JavaScript 对象传入 Vue 实例作为 data 选项,Vue 将遍历此对象所有的 property,并使用 Object.defineProperty 把这些 property 全部转为 getter/setter。
这个getter/setter对于用户是不可见的,但在内部它们让Vue能够追踪依赖,在 property 被访问和修改时通知变更。

javascript
function observe(data){
	if(!data || typeof data !== 'object') return
  for(let key in data){
  	let val = data[key]
    Object.defineProperty(data,key,{
    	enumerable:true,
      cofigurable:true,
      get(){
      	track(data,key)
      }
      set(newVal){
    		trigger(data,key,newVal)
    	}
    })
    if(typeof val === 'object'){
      observe(val)
    }
	}
}
function track(data,key){
  console.log('get data',key)
}
function trigger(data,key,value){
  console.log('set data',key,":",value)
}
var data = {
  name:'baizhe',
  arr:[1,2,3]
}
observe(data)
console.log(data.name)
data.name = 'valley'
data.arr[0] = 4
data.arr[3] = 5 // ⾮响应式
data.age = 6 //⾮响应式

举例代码如上,通过Object.defineProperty()将a的数据转为 getter/setter。但是观测后,之后新增的属性不会有响应式。若是要对data内声明的数据进行更改。其实实例vm就是data的代理,通过vm可以更改data。

javascript
vm.obj.a = 2 // 可以更改
this.obj.a = 2 // 可以更改
this.data.obj.a = 2 // 不可以更改

注意事项

由于JS的限制,Vue对于数组和对象的变化的检查有些问题。

关于对象

  • 示例一
javascript
new Vue({
  data: {},
  template: `
    <div>{{n}}</div>
  `
}).$mount("#app");

n不存在,Vue会给一个警告

  • 示例二
javascript
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; 
    }
  }
}).$mount("#app");

页面中点击按钮不会出现1,这是因为Vue只会检查第一次属性。

解决方法:

1.在obj内声明b

2.使用Vue.set()/this.$set()

javascript
methods:{
      setB(){
	Vue.set(obj, 'b', 2)
	// this.$set(this.obj,'b',2)
  }
}

关于数组

Vue不能检测以下数组变动

1.利用索引直接设置一个数组项

2.修改数组长度

javascript
var vm = new Vue({
  data: {
    items: ['a', 'b', 'c']
  }
})
vm.items[1] = 'x' // 不响应
vm.items.length = 2 // 不响应

解决方法:

javascript
// Vue.set
Vue.set(vm.items, indexOfItem, newValue)
// Array.prototype.splice
vm.items.splice(indexOfItem, 1, newValue)

当数据中含有数组时,若要给数组添加元素,但新增元素的index在声明实例的时候还未定义,为了解决这个问题,Vue的作者将数组的七个方法进行了变更。对Array()函数通过继承,增加了一条原型链的环节,并在环节中添加了七种方法,方法的内部进行了Vue.set()的操作,使的数组的新元素能被实例化。

变更方法

  • pop()
  • shift()
  • unshift()
  • splice()
  • sort()
  • reverse()
  • push()

参考来自Vue文档——深入响应式原理

Vue3如何实现数据响应式

Vue3则是通过创建一个对象的代理来实现的,通过Proxy⽤于创建⼀个对象的代理,从⽽实现基本操作的拦截和⾃定义(如属性查找、赋值、枚举、函数调⽤等)。

Reflect是一个内置对象,提供拦截JS操作的方法,这些方法和Proxy handlers相同。

Reflect.set(target, propertyKey, value[, receiver]) 将值分配给属性的函数。返回⼀个Boolean,如果更 新成功,则返回true。

Reflect.get(target, propertyKey[, receiver]) 获取对象身上某个属性的值,类似于target[name]。

javascript
function reactive(obj){
  const handler = {
		get(target,prop,receiver){
      track(target,prop)
      const value = Reflect.get(...arguments)
      if(typeof value === 'object'){
        return reactive(value)
      }else{
        return value
      }
    },
    set(target,key,value,receiver){
      trigger(target,key,value)
      return Reflect.set(...arguments)
    }
  }
  return new Proxy(obj,handler)
}

 function track(data, key) {
 console.log('get data ', key)
 }
 function trigger(data, key, value) {
 console.log('set data', key, ":", value)
 }

 const people={
 name: 'baizhe'
 }
 const proxy = reactive(people)
 proxy.name = 'Tom'
 proxy.list = []
 proxy.list.push(1) //响应式

Vue3和Vue2响应式原理的区别

前者使用Proxy,后者使用Object.defineProperty。Proxy能劫持整个对象,而Object.defineProperty只能劫持对象的属性;前者递归返回属性对应的值的代理即可实现响应式,后者需要深度遍历每个属性,且对数组操作很不友好。