2025-01-27 日报 Day79

2025-01-27 日报 Day79

Yuyang 前端小白🥬

今日的鸡汤

没有谁的生活是一帆风顺,许多人都是在一路的披荆斩棘中,摸索属于自己的那一条路。

今日学习内容

1、JS 红皮书 P273-275 第九章:代理与反射

今日笔记

1、代理可以拦截反射 API 的操作,而这意味着完全可以创建一个代理,通过它去代理另一个代理。这样就可以在一个目标对象之上构建多层拦截网:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const target = {
foo: "bar",
};
const firstProxy = new Proxy(target, {
get() {
console.log("first proxy");
return Reflect.get(...arguments);
},
});
const secondProxy = new Proxy(firstProxy, {
get() {
console.log("second proxy");
return Reflect.get(...arguments);
},
});
console.log(secondProxy.foo);
// second proxy
// first proxy
// bar

2、代理问题与不足:

  • 代理中的 this: 代理潜在的一个问题来源是 this 值。我们知道,方法中的 this 通常指向调用这个方法的对象:
1
2
3
4
5
6
7
8
const target = {
thisValEqualsProxy() {
return this === proxy;
},
};
const proxy = new Proxy(target, {});
console.log(target.thisValEqualsProxy()); // false
console.log(proxy.thisValEqualsProxy()); // true
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const wm = new WeakMap();
class User {
constructor(userId) {
wm.set(this, userId);
}
set id(userId) {
wm.set(this, userId);
}
get id() {
return wm.get(this);
}
}
// 由于这个实现依赖 User 实例的对象标识,在这个实例被代理的情况下就会出问题:
const user = new User(123);
console.log(user.id); // 123
const userInstanceProxy = new Proxy(user, {});
console.log(userInstanceProxy.id); // undefined
// 这是因为 User 实例一开始使用目标对象作为 WeakMap 的键,代理对象却尝试从自身取得这个实例。要解决这个问题,就需要重新配置代理,把代理 User 实例改为代理 User 类本身。之后再创建代理的实例就会以代理实例作为 WeakMap 的键了:
const UserClassProxy = new Proxy(User, {});
const proxyUser = new UserClassProxy(456);
console.log(proxyUser.id);
  • 代理与内部槽位: 代理与内置引用类型(比如 Array)的实例通常可以很好地协同,但有些 ECMAScript 内置类型可能会依赖代理无法控制的机制,结果导致在代理上调用某些方法会出错。
    一个典型的例子就是 Date 类型。根据 ECMAScript 规范,Date 类型方法的执行依赖 this 值上的内部槽位[[NumberDate]]。代理对象上不存在这个内部槽位,而且这个内部槽位的值也不能通过普通的 get()和 set()操作访问到,于是代理拦截后本应转发给目标对象的方法会抛出 TypeError:
1
2
3
4
const target = new Date();
const proxy = new Proxy(target, {});
console.log(proxy instanceof Date); // true
proxy.getDate(); // TypeError: 'this' is not a Date object

3、代理捕获器与反射方法: 代理可以捕获 13 种不同的基本操作。这些操作有各自不同的反射 API 方法、参数、关联 ECMAScript 操作和不变式。

  • get(): get()捕获器会在获取属性值的操作中被调用。对应的反射 API 方法为 Reflect.get()。
1
2
3
4
5
6
7
8
9
const myTarget = {};
const proxy = new Proxy(myTarget, {
get(target, property, receiver) {
console.log("get()");
return Reflect.get(...arguments);
},
});
proxy.foo;
// get()
  1. 返回值
    返回值无限制。
  2. 拦截的操作
     proxy.property
     proxy[property]
     Object.create(proxy)[property]
     Reflect.get(proxy, property, receiver)
  3. 捕获器处理程序参数
     target:目标对象。
     property:引用的目标对象上的字符串键属性。①
     receiver:代理对象或继承代理对象的对象。
  4. 捕获器不变式
    如果 target.property 不可写且不可配置,则处理程序返回的值必须与 target.property 匹配。
    如果 target.property 不可配置且[[Get]]特性为 undefined,处理程序的返回值也必须是 undefined。
  • set(): set()捕获器会在设置属性值的操作中被调用。对应的反射 API 方法为 Reflect.set()。
1
2
3
4
5
6
7
8
9
const myTarget = {}; 
const proxy = new Proxy(myTarget, {
set(target, property, value, receiver) {
console.log('set()');
return Reflect.set(...arguments)
}
});
proxy.foo = 'bar';
// set()
  1. 返回值
    返回 true 表示成功;返回 false 表示失败,严格模式下会抛出 TypeError。
  2. 拦截的操作
     proxy.property = value
     proxy[property] = value
     Object.create(proxy)[property] = value
     Reflect.set(proxy, property, value, receiver)
  3. 捕获器处理程序参数
     target:目标对象。
     property:引用的目标对象上的字符串键属性。
     value:要赋给属性的值。
     receiver:接收最初赋值的对象。
  4. 捕获器不变式
    如果 target.property 不可写且不可配置,则不能修改目标属性的值。
    如果 target.property 不可配置且[[Set]]特性为 undefined,则不能修改目标属性的值。
    在严格模式下,处理程序中返回 false 会抛出 TypeError。
此页目录
2025-01-27 日报 Day79