简述 Vue响应式数据的原理

2023-11-30  本文已影响0人  简单tao的简单

vue2

vue2 利用 es5 的 Object.defineProperty 实现响应式

下面详解一下 Object.defineProperty

下面我们通过使用 Object.defineProperty 创建一个对象来熟悉它的用法

正常创建对象这样就可以了

let vm = {
   name: '简书'
}

使用 Object.defineProperty 创建对象

let vm = Object.defineProperty({},"name",{
  get() {
    console.log("执行get");
    return name || "简书"
  },
  set(newVal) {
    console.log("执行set");
    console.log("新值:" + newVal);
    if (newVal !== name) {
      name = newVal;
    }
  }
})

其实这两种都创建一个对象的方式,通过Object.defineProperty创建的对象,我们可以看到,多了上面说的两个存取描述符键值方法get 和set 这样的对象,就变得可控被观察,也就是我们说的被劫持,当我们改变或者获取这对象的属性的时候,我们就可以控制到它

下面我们改变vm.name = "1",我们通过控制台可以看到

实现观察者机制,响应式对象

let vm = {
  id:"jianshu",
  name:"简书"
};
let keys = Object.keys(vm); //Object.keys() 方法会返回一个数组,数组元素就是vm对象的属性名 ['id', 'name']
keys.forEach(key=>{ //keys.forEach 没有返回结果,返回值为undefined,本质上等同于 for 循环,会改变原数组
  let value = vm[key];
  Object.defineProperty(vm, key,{
    get() {
      console.log("执行get");
      return value
   },
   set(newVal){
      console.log("执行set");
      if(newVal !== value){
         value =  newVal;
       }
     }
  })
})
vm.id = "test";
console.log(vm)

控制台输出


这里主要是遍历对象中的每一个属性,每个属性都是赋予get和set,让对象中的每一个属性的改变都会被监测到,检测到改变后,vue会触发rerender函数,重新渲染页面,也就是实现了响应式

Object.defineProperty 的缺点
  1. 无法监听对象非已有的属性的添加和已有属性的删除
    只会对对象原有的全部属性进行做数据劫持,也就是说Vue 不允许动态添加或者删除对象已有属性,它是不做数据劫持的,也就不能实现响应式
    举例
<template>
<div>

<h1>{{ vm }}</h1>
<button @click="addAttribute">新增属性</button>
<button @click="delAttribute">删除属性</button>
</div>
</template>

<script>
export default {
data() {

return { 
  vm:{
    id:"juejin",
    name:"掘金"
  }
}
},
methods: {

addAttribute() {
  this.vm.use = "codercao"
  console.log(this.vm)
},
delAttribute() {
  for(let k in this.vm) {
   if(k=='id'){
     delete this.vm[k]
   }
  }
  console.log(this.vm)
}
},
}
</script>

新增,你会发现控制台打印的vm已经新增了use属性,而页面并没有响应式改变
删除,你会发现控制台打印的vm已经删除了id属性,而页面并没有响应式改变

  1. 数组对象也不能通过属性或者索引(length,index)实现响应式
    vue源码只是对数组的 'push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'进行了重写,但是索引控制数组是没有办法实现响应式的

解决问题

Object.defineProperty 的缺点1可以使用Vue.set(object, propertyName, value) 方法解决,你会发现页面就实现了响应式

addAttribute() {
 //this.vm.use = "codercao"
 this.$set(this.vm,'use','codercao')
 console.log(this.vm)
},

vue3

vue3 利用 ES6 的 proxy 实现响应式

下面详解一下 Object.defineProperty

<script>
  let obj = {
    name: '小猪课堂',
    age: 23
  }
  let p = new Proxy(obj, {});
  console.log(obj);
  console.log(p);
</script>

上面只是简单的初始化了一个代理对象,而我们需要重点掌握的是 Proxy 对象中的 handler 参数。因为我们所有的拦截操作都是通过这个对象里面的函数而完成的。
就好比律师全权代理了我们,那他拦截之后能做什么呢?或者说律师拦截之后他有哪些能力呢?这就是我们 handler 参数对象的作用了,接下来我们就一一来讲解下。

handler 它是一个对象,该对象的属性通常都是一些函数,handler 对象中的这些函数也就是我们的处理器函数,主要定义我们在代理对象后的拦截或者自定义的行为。

//handler 对象的的属性大概有下面这些
handler.apply()
handler.construct()
handler.defineProperty()
handler.deleteProperty()
handler.get()
handler.getOwnPropertyDescriptor()
handler.getPrototypeOf()
handler.has()
handler.isExtensible()
handler.ownKeys()
handler.preventExtensions()
handler.set()
handler.setPrototypeOf()

这里主要讲解一下 handler.get() 和 handler.set(),其他的 handler属性不在此过多阐述,因为我们是为了讲解Vue响应式数据的原理

handler.get

该方法用于拦截对象的读取属性操作,比如我们要读取某个对象的属性,就可以使用该方法进行拦截。

// 拦截读取属性操作
let p5 = new Proxy(target, {
  get: function (target, property, receiver) {
        //target:被代理的目标对象
        //property:想要获取的属性名
        //receiver:Proxy 或者继承 Proxy 的对象
  }
});
// 拦截读取属性操作
let p5 = new Proxy({}, {
    get: function (target, property, receiver) {
        console.log("属性名:", property); // 属性名:name
        console.log(receiver); // Proxy {}
        return target[property] || '小猪课堂'
    }
});
console.log(p5.name); // 小猪课堂

可以看到我们代理的对象其实是一个空对象,但是我们获取 name 属性是是返回了值的,其实是在 handler 对象中的 get 函数返回的。

handler.set

当我们给对象设置属性值时,将会触发该拦截

let p12 = new Proxy(target, {
  set: function (target, property, value, receiver) {
        //target:被代理的目标对象
        //property:将要被设置的属性名
        //value:新的属性值
        //receiver:最初被调用的对象,通常就是 proxy 对象本身
  }
});
let p12 = new Proxy({}, {
  set: function (target, property, value, receiver) {
    target[property] = value;
    console.log('property set: ' + property + ' = ' + value); // property set: a = 10
    return true;
  }
});
p12.a = 10; 

好了,学习了proxy的原理,我们再看下如下代码的控制台输出

let vm = {
  id:"jianshu",
  name:"简书"
}
let newVm = new Proxy(vm,{
  get(target,key){
    console.log("执行get");
    return target[key];
   },
  set(target,key,newVal){
    console.log("执行set");
    if(target[key]!==newVal)
    target[key] = newVal;
  }
})
newVm.use = "codercao" //会触发set拦截
console.log(newVm)

你会发现新增的属性也是响应式的,用Proxy 也一样实现了一个简易的观察者机制,当然深入研究的话,你还可以实现双向绑定。

总结

上一篇下一篇

猜你喜欢

热点阅读