由浅入深,带你用JavaScript实现响应式原理

前言为什么前端框架Vue能够做到响应式?当依赖数据发生变化时 , 会对页面进行自动更新 , 其原理还是在于对响应式数据的获取和设置进行了监听 , 一旦监听到数据发生变化 , 依赖该数据的函数就会重新执行 , 达到更新的效果 。那么我们如果想监听对象中的属性被设置和获取的过程 , 可以怎么做呢?
1.Object.defineProperty

在ES6之前 , 如果想监听对象属性的获取和设置 , 可以借助Object.defineProperty方法的存取属性描述符来实现 , 具体怎么用呢?我们来看一下 。
const obj = {name: 'curry',age: 30}// 1.拿到obj所有的keyconst keys = Object.keys(obj)// 2.遍历obj所有的key , 并设置存取属性描述符keys.forEach(key => {let value = https://www.isolves.com/it/cxkf/yy/js/2022-03-30/obj[key]Object.defineProperty(obj, key, {get: function() {console.log(`obj对象的${key}属性被访问啦!`)return value},set: function(newValue) {console.log(`obj对象的${key}属性被设置啦!`)value = newValue}})})// 设置:obj.name = 'kobe' // obj对象的name属性被设置啦!obj.age = 24 // obj对象的age属性被设置啦!// 访问:console.log(obj.name) // obj对象的name属性被访问啦!console.log(obj.age) // obj对象的age属性被访问啦!在Vue2.x中响应式原理实现的核心就是使用的Object.defineProperty , 而在Vue3.x中响应式原理的核心被换成了Proxy , 为什么要这样做呢?主要是Object.defineProperty用来监听对象属性变化 , 有以下缺点:
  • 首先 , Object.defineProperty设计的初衷就不是为了去监听对象属性的 , 因为它的主要使用功能就是用来定义对象属性的;
  • 其次 , Object.defineProperty在监听对象属性功能上有所缺陷 , 如果想监听对象新增属性、删除属性等等 , 它是无能为力的;
2.Proxy
在ES6中 , 新增了一个Proxy类 , 翻译为代理 , 它可用于帮助我们创建一个代理对象 , 之后我们可以在这个代理对象上进行许多的操作 。
2.1.Proxy的基本使用
如果希望监听一个对象的相关操作 , 当Object.defineProperty不能满足我们的需求时 , 那么可以使用Proxy创建一个代理对象 , 在代理对象上 , 我们可以监听对原对象进行了哪些操作 。下面将上面的例子用Proxy来实现 , 看看效果 。
基本语法:const p = new Proxy(target, handler)
  • target:需要代理的目标对象;
  • handler:定义的各种操作代理对象的行为(也称为捕获器);
const obj = {name: 'curry',age: 30}// 创建obj的代理对象const objProxy = new Proxy(obj, {// 获取对象属性值的捕获器get: function(target, key) {console.log(`obj对象的${key}属性被访问啦!`)return target[key]},// 设置对象属性值的捕获器set: function(target, key, newValue) {console.log(`obj对象的${key}属性被设置啦!`)target[key] = newValue}})// 之后的操作都是拿代理对象objProxy// 设置:objProxy.name = 'kobe' // obj对象的name属性被设置啦!objProxy.age = 24 // obj对象的age属性被设置啦!// 访问:console.log(objProxy.name) // obj对象的name属性被访问啦!console.log(objProxy.age) // obj对象的age属性被访问啦!// 可以发现原对象obj同时发生了改变console.log(obj) // { name: 'kobe', age: 24 }2.2.Proxy的set和get捕获器
在上面的例子中 , 其实已经使用到了set和get捕获器 , 而set和get捕获器是最为常用的捕获器 , 下面具体来看看这两个捕获器吧 。
(1)set捕获器
set函数可接收四个参数:
  • target:目标对象(被代理对象);
  • property:将被设置的属性key;
  • value:设置的新属性值;
  • receiver:调用的代理对象;
(2)get捕获器
get函数可接收三个参数:
  • target:目标对象;
  • property:被获取的属性key;
  • receiver:调用的代理对象;
2.3.Proxy的Apply和construct捕获器
上面所讲的都是对对象属性的操作进行监听 , 其实Proxy提供了更为强大的功能 , 可以帮助我们监听函数的调用方式 。