JavaScript 教程:深入理解 Proxy 和 Reflect

JavaScript 教程:深入理解 Proxy 和 Reflect

en.javascript.info Modern JavaScript Tutorial en.javascript.info 项目地址: https://gitcode.com/gh_mirrors/en/en.javascript.info

什么是 Proxy?

Proxy(代理)是 JavaScript 中一个强大的元编程特性,它允许你创建一个对象的代理(中间层),可以拦截和自定义对象的基本操作。Proxy 不会直接操作目标对象,而是通过这个代理层来控制对目标对象的访问。

基本语法

创建一个 Proxy 的基本语法如下:

let proxy = new Proxy(target, handler)
  • target:要包装的目标对象,可以是任何类型的对象,包括函数
  • handler:代理配置对象,包含各种"陷阱"(trap)方法,用于拦截操作

无陷阱的简单代理

让我们从一个最简单的例子开始,创建一个没有任何陷阱的代理:

let target = {};
let proxy = new Proxy(target, {}); // 空handler

proxy.test = 5; // 写入代理
console.log(target.test); // 5, 属性出现在目标对象上

console.log(proxy.test); // 5, 可以从代理读取

for(let key in proxy) console.log(key); // test, 迭代也正常工作

在这个例子中,由于没有设置任何陷阱,所有对代理的操作都会透明地转发到目标对象。

代理的内部工作机制

JavaScript 规范中为大多数对象操作定义了"内部方法"。例如:

  • [[Get]]:读取属性的内部方法
  • [[Set]]:写入属性的内部方法
  • [[HasProperty]]in 操作符的内部方法

Proxy 的陷阱就是用来拦截这些内部方法的调用的。下面是一些常见的陷阱方法:

| 内部方法 | 陷阱方法 | 触发时机 | |---------|---------|---------| | [[Get]] | get | 读取属性时 | | [[Set]] | set | 写入属性时 | | [[HasProperty]] | has | 使用 in 操作符时 | | [[Delete]] | deleteProperty | 使用 delete 操作符时 | | [[Call]] | apply | 函数调用时 | | [[Construct]] | construct | 使用 new 操作符时 |

使用 get 陷阱实现默认值

让我们看一个实用的例子:使用 get 陷阱为数组提供默认值。

let numbers = [0, 1, 2];

numbers = new Proxy(numbers, {
  get(target, prop) {
    if (prop in target) {
      return target[prop];
    } else {
      return 0; // 默认值
    }
  }
});

console.log(numbers[1]); // 1
console.log(numbers[123]); // 0 (不存在的项返回默认值)

这个例子展示了如何通过代理为不存在的数组项返回默认值 0,而不是 undefined。

使用 set 陷阱进行验证

另一个常见用例是使用 set 陷阱进行属性写入验证:

let numbers = [];

numbers = new Proxy(numbers, {
  set(target, prop, val) {
    if (typeof val === 'number') {
      target[prop] = val;
      return true; // 必须返回true表示成功
    } else {
      return false; // 返回false会触发TypeError
    }
  }
});

numbers.push(1); // 成功
numbers.push(2); // 成功
console.log("Length is: " + numbers.length); // 2

try {
  numbers.push("test"); // TypeError
} catch(e) {
  console.log(e.message); 
}

注意 set 陷阱必须返回 true 表示操作成功,否则会抛出 TypeError。

保护私有属性

我们可以使用代理来保护以下划线 _ 开头的"私有"属性:

let user = {
  name: "John",
  _password: "secret"
};

user = new Proxy(user, {
  get(target, prop) {
    if (prop.startsWith('_')) {
      throw new Error("Access denied");
    }
    let value = target[prop];
    return (typeof value === 'function') ? value.bind(target) : value;
  },
  set(target, prop, val) {
    if (prop.startsWith('_')) {
      throw new Error("Access denied");
    } else {
      target[prop] = val;
      return true;
    }
  },
  // 其他陷阱...
});

try {
  console.log(user._password); // Error: Access denied
} catch(e) { console.log(e.message); }

使用 has 陷阱实现范围检查

我们可以使用 has 陷阱来定制 in 操作符的行为:

let range = {
  start: 1,
  end: 10
};

range = new Proxy(range, {
  has(target, prop) {
    return prop >= target.start && prop <= target.end;
  }
});

console.log(5 in range); // true
console.log(50 in range); // false

函数代理:apply 陷阱

Proxy 也可以包装函数,使用 apply 陷阱:

function delay(f, ms) {
  return new Proxy(f, {
    apply(target, thisArg, args) {
      setTimeout(() => target.apply(thisArg, args), ms);
    }
  });
}

function sayHi(user) {
  console.log(`Hello, ${user}!`);
}

sayHi = delay(sayHi, 3000);

console.log(sayHi.length); // 1 (*) 代理转发length属性

sayHi("John"); // 3秒后显示"Hello, John!"

Reflect:Proxy 的搭档

Reflect 是一个内置对象,它提供了拦截 JavaScript 操作的方法。这些方法与 Proxy 陷阱一一对应。通常我们会结合使用 Proxy 和 Reflect:

let user = {
  name: "John",
};

user = new Proxy(user, {
  get(target, prop, receiver) {
    console.log(`GET ${prop}`);
    return Reflect.get(target, prop, receiver);
  },
  set(target, prop, val, receiver) {
    console.log(`SET ${prop}=${val}`);
    return Reflect.set(target, prop, val, receiver);
  }
});

let name = user.name; // 显示 "GET name"
user.name = "Pete"; // 显示 "SET name=Pete"

代理的局限性

虽然 Proxy 很强大,但也有一些限制:

  1. 代理无法拦截严格相等检查 ===
  2. 内置对象如 Map、Set 等有内部槽位,代理可能无法完全透明地包装它们
  3. 代理的性能会比直接操作对象稍慢

实际应用场景

Proxy 在实际开发中有许多应用:

  1. 数据验证和格式化
  2. 自动填充默认值
  3. 实现观察者模式
  4. 创建不可变数据结构
  5. 实现缓存层
  6. 日志和性能监控

总结

Proxy 是 JavaScript 中一个强大的元编程工具,它允许你:

  • 拦截和自定义对象的基本操作
  • 实现数据验证、默认值、访问控制等功能
  • 创建更灵活、更安全的抽象

Reflect 则提供了与 Proxy 陷阱对应的方法,通常与 Proxy 配合使用。

虽然 Proxy 很强大,但也应该谨慎使用,因为它会改变 JavaScript 的默认行为,可能会使代码更难理解。在合适的场景下使用 Proxy 可以大大简化代码并增加灵活性。

记住,Proxy 不是所有问题的解决方案,但对于某些特定问题,它提供了优雅的解决方式。

en.javascript.info Modern JavaScript Tutorial en.javascript.info 项目地址: https://gitcode.com/gh_mirrors/en/en.javascript.info

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

柯兰妃Jimmy

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

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

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

打赏作者

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

抵扣说明:

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

余额充值