《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] = newVal
    trigger(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, {
  
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

前端 贾公子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值