当前位置: 首页 > news >正文

《Vuejs设计与实现》第 5 章(非原始值响应式方案) 上

目录

5.1 理解 Proxy 和 Reflect

5.2 JavaScript 对象和 Proxy 的工作原理

5.3 如何代理 Object


5.1 理解 Proxy 和 Reflect

Proxy 可以创建一个代理对象,实现对其他对象的代理,拦截并重新定义对对象的基本操作。
注意,Proxy 只能代理对象,不能代理非对象值(如字符串、布尔值等)。
基本操作包括读取属性值、设置属性值等。例如:

obj.foo // 读取属性 foo 的值
obj.foo++ // 读取并设置属性 foo 的值

可以使用 Proxy 拦截基本操作:

const p = new Proxy(obj, {// 拦截读取属性操作get() { /*...*/ },// 拦截设置属性操作set() { /*...*/ }
})

Proxy 构造函数接收两个参数:被代理对象和一个包含一组拦截函数的对象(trap夹子)。get 函数用于拦截读取操作,set 函数用于拦截设置操作。
在 JS 中,函数也是对象,所以调用函数也是对一个对象的基本操作:

const fn = (name) => {console.log('我是:', name)
}// 调用函数
fn()

我们可以用 Proxy 里的 apply 函数进行拦截:

const p2 = new Proxy(fn, {// 使用 apply 拦截函数调用apply(target, thisArg, argArray) {target.call(thisArg, ...argArray)}
})p2('hcy') // 输出:'我是:hcy'
Proxy 只能拦截对象的基本操作。
非基本操作,如调用对象下的方法(称为复合操作):
obj.fn()

复合操作实际上由两个基本操作组成的:首先是 get 操作得到 obj.fn 属性,其次是函数调用。即获得 obj.fn 值后再调用它,这就是我们刚才提到的 apply。
理解 Proxy 只能代理对象的基本操作对于后续实现数组或 Map、Set 等数据类型的代理至关重要。

我们来看 Reflect。Reflect 是一个全局对象,提供了一些方法,例如:

  1. Reflect.get()
  2. Reflect.set()
  3. Reflect.apply()

Reflect 中的方法与 Proxy 的拦截器方法同名。它们提供了对象操作的默认行为。例如,以下两个操作是等价的:

const obj = { foo: 1 }// 直接读取
console.log(obj.foo) // 1// 使用 Reflect.get 读取
console.log(Reflect.get(obj, 'foo')) // 1

如果两种操作等价,Reflect 存在的意义是什么呢?
Reflect.get() 还接受第三个参数,也就是 receiver,你可以将它看作函数调用中的 this,例如:

const obj = { foo: 1 }console.log(Reflect.get(obj, 'foo', { foo: 2 }))  // 输出的是 2 而不是 1

在这段代码中,我们指定第三个参数 receiver 为一个对象 { foo: 2 },这时读取到的值是 receiver 对象的 foo 属性值。
事实上,Reflect 的各个方法都有很多其他用途,但在此我们只关注与响应式数据实现相关的部分,我们回顾一下上一节的响应式代码:

const obj = { foo: 1 }const p = new Proxy(obj, {get(target, key) {track(target, key)// 注意,这里我们没有使用 Reflect.get 完成读取return target[key]},set(target, key, newVal) {// 这里同样没有使用 Reflect.set 完成设置target[key] = newValtrigger(target, key)}
})

 在 get 和 set 拦截函数中,我们都是直接使用原始对象 target 来完成对属性的读取和设置操作的,其中原始对象 target 就是上述代码中的 obj 对象。
然而,这段代码存在一些问题。通过 effect 可以看出。首先,我们修改一下 obj 对象,为其添加一个 bar 属性:

const obj = {foo: 1,get bar() {return this.foo}
}

上述代码 bar 是一个访问器属性,它返回了 this.foo 的值。接下来,我们在 effect 的副作用函数中通过代理对象 p 访问 bar 属性:

effect(() => {console.log(p.bar) // 1
})

这个过程中发生了什么?当执行 effect 注册的副作用函数时,会读取 p.bar 属性。
因为 p.bar 是一个访问器属性,所以会执行 getter 函数。
getter 函数通过 this.foo 读取了 foo 属性值,所以我们认为副作用函数和 foo 属性之间会建立联系。当我们尝试改变 p.foo 的值时:

p.foo++

副作用函数并没有重新执行。问题在哪里呢?
实际上,问题出在 bar 属性的 getter 函数里:

const obj = {foo: 1,get bar() {// 这里的 this 指向的是谁?return this.foo}
}

当我们使用 this.foo 读取 foo 属性值时,这里的 this 指向的是谁呢?
我们回顾一下整个流程。首先,我们通过代理对象 p 访问 p.bar,这会触发代理对象的 get 拦截函数:

const p = new Proxy(obj, {

相关文章:

  • 发那科机器人5(异常事件和程序备份加载+ROBOGUIDE离线仿真)
  • 二叉树的深度
  • Conda激活环境无效
  • 对称加密以及非对称加密
  • transformer 笔记 tokenizer moe
  • [Windows] 希捷(Seagate)硬盘官方检测工具 - SeaTools(1.4.0.7)
  • 【身份证识别表格】批量识别身份证扫描件或照片保存为Excel表格,怎么大批量将身份证图片转为excel表格?基于WPF和腾讯OCR的识别方案
  • Path to Integer_ABC402分析与解答
  • SCDN是什么?
  • 上班摸鱼远程打游戏,哪款远控软件好用点?
  • 【Bootstrap V4系列】学习入门教程之 组件-表单(Forms)
  • MySQL如何优雅的执行DDL
  • 图解gpt之神经概率语言模型与循环神经网络
  • 【应急响应】- 日志流量如何分析?
  • SecureCRT网络穿透/代理
  • 网络研讨会开发注册中, 5月15日特励达力科,“了解以太网”
  • 深入理解C/C++内存管理:从基础到高级优化实践
  • kafka 面试总结
  • 微服务中 本地启动 springboot 无法找到nacos配置 启动报错
  • Mac QT水平布局和垂直布局
  • 匈牙利外长称匈方已驱逐两名乌克兰外交官
  • 保利42.41亿元竞得上海杨浦东外滩一地块,成交楼面单价超8万元
  • 我驻苏丹使馆建议在苏中国公民尽快撤离
  • 老铺黄金拟配售募资近27亿港元,用于门店拓展扩建及补充流动资金等
  • 司法部:加快研究制定行政执法监督条例,建立完善涉企行政执法监督长效机制
  • 扶桑谈|素称清廉的石破茂被曝受贿,日本政坛或掀起倒阁浪潮