js元编程:妙用proxy实现add[1][2][3] + 4

js元编程相关文章

js元编程: Symbol&Reflect&Proxy
js基石之Symbol值
js元编程:妙用proxy实现add[1][2][3] + 4

什么是元编程:

元编程是指编写可以操作或改变其他程序的程序。

元编程可以改变 JavaScript 的一些基本操作的行为。

就算不理解元编程的概念也不重要,在js里面元编程主要与这三个对象有关。

Symbol :通过覆写内置的Symbol值等方法 更改js语言内部的基本操作

Reflect :可以获取语言内部的基本操作

Proxy :通过钩子函数 拦截&改变 js语言的基本操作

因此也可以理解为这个系列文章主要是讲解这三个api的。

题目如下

scss 复制代码
const res = add[1][2][3] + 4
console.log(res) // 10

使用js如何实现上面的逻辑...

先有问题再有答案

  1. 这个问题可以被拆分为哪些子任务?
  2. 链式调用如何实现?
  3. 累加后的结果如何保存
  4. 对象和数字类型做加法运算js是如何处理的

这个问题 add[1][2][3] + 4 按照 +运算符可以被分为三部分。

第一个:add[1][2][3] 链式调用并结果累加

第二个:+ 在对象和数字的转化

第三个:最原始的加法运算得出结果.

对象和数字如何计算

因为add返回的一定是一个对象 否则链式调用无法实现

所以这里涉及到了js引用类型与基础类型的转换 具体可以参考之前的文章: js基石之数据类型三:类型转换

这里列下关键步骤:

对象在转换到数字或字符串时 逻辑是相似的 只是顺序会有不同。

  1. 判断对象是否实现了Symbol.toPrimitive方法。
    如果实现了则按照这个方法返回,如果对象没有实现 则走js内部的Symbol.toPrimitive方法。
    内部Symbol.toPrimitive大概逻辑如下:
javascript 复制代码
function ToPrimitive(input, preferredType) {
  switch (preferredType) {
    case Number:
      return toNumber(input);
      break;
    case String:
      return toString(input);
      break;
    default:
      return toNumber(input);
  }

  function isPrimitive(value) {
    return value !== Object(value);
  }

  function toString() {
    if (isPrimitive(input.toString())) return input.toString();
    if (isPrimitive(input.valueOf())) return input.valueOf();
    throw new TypeError();
  }

  function toNumber() {
    if (isPrimitive(input.valueOf())) return input.valueOf();
    if (isPrimitive(input.toString())) return input.toString();
    throw new TypeError();
  }
}
  1. 如果是转换为number则调用ToPrimitive(input, 'Number')
  2. 如果是转换为String 则调用ToPrimitive(input, 'String')
  3. 不能转换为原始值走报错逻辑
  4. 可以转换为原始值 则返回当前的原始值 然后在继续运算 后续可能还会将这个原始值在进行类型转换。

所以在我们这个场景下 可以实现如下代码:

javascript 复制代码
const obj = {
    value: 1,
    [Symbol.toPrimitive](){
        return this.value;
    }
};
console.log('add', obj + 10) // add 11

链式求和

add[1]这种使用方式很像数组取值,也就是从一个对象上动态获取某个索引属性...只是这个case下 我们不是直接返回一个数组的内容而是返回原对象,以方便后续的链式调用和取值...

可以利用JavaScript的代理(Proxy)实现动态拦截属性的能力,然后返回代理本身,并计算当前的索引值与存储的结果求和 得出最新值 存储最新值,以此类推。

代码如下:

javascript 复制代码
const add = new Proxy(
  {
    value: 0,
  },
  {
    get(target, property) {
      const newValue = target.value + Number(property);
      return new Proxy({ value: newValue }, this);
    },
  }
);
console.log("test add", add[1][2]["3"][4]); 

可以看到 链式调用实现了 并且累加的结果也已经存储在value中。

完整实现:

将上述两个步骤结合在一起 略作改动。

代码如下:

javascript 复制代码
const add = new Proxy(
  {
    value: 0,
  },
  {
    get(target, property) {
      if (property === Symbol.toPrimitive) {
        return () => {
          return target.value;
        };
      }
      if (Number.isNaN(Number(property))) {
        throw new Error("key应该是一个数字");
      }
      const newValue = target.value + Number(property);
      return new Proxy({ value: newValue }, this);
    },
  }
);
console.log("test add", add[1][2]["3"][4] + 4);

当通过add[1]这样的操作访问add时,实际上是在访问代理对象,并试图获取索引"1"。 通过代理的handler的get函数,我们拦截了这个访问,然后将 "1" 转换成数值1并与之前累积的和相加(初始化时为0)。然后,我们基于新的累加和。创建一个新的代理对象,以便能够继续拦截后续的访问。

当执行到add[1][2]["3"][4]时,依次操作实现了1 + 2 + 3 + 4的累加。

最后,当执行加法 + 4 时,JavaScript引擎调用对象的[Symbol.toPrimitive]方法来尝试获取可以进行数学运算的值。在我们的handler中,我们覆盖了对[Symbol.toPrimitive]的访问,使其返回当前累积的和(即10),然后这个值与4相加得到最终结果14。

并且我们也对非索引key做了拦截

sql 复制代码
console.log("test add", add["a"] + 1);

总结

这个场景主要是利用了proxy对get属性的拦截。实际上,JavaScript 的 Proxy 对象为您提供了一种拦截和自定义基本操作(如属性读取、赋值、枚举、函数调用等)的方法。这是十分强大的能力。具有很多创造性的使用方式,可以用来构建高级的抽象、响应性框架、甚至代理和远程对象的实现。所以建议大家熟练掌握proxy相关知识点。

相关推荐
C澒1 分钟前
多场景多角色前端架构方案:基于页面协议化与模块标准化的通用能力沉淀
前端·架构·系统架构·前端框架
崔庆才丨静觅3 分钟前
稳定好用的 ADSL 拨号代理,就这家了!
前端
江湖有缘4 分钟前
Docker部署music-tag-web音乐标签编辑器
前端·docker·编辑器
hzb666666 分钟前
unictf2026
开发语言·javascript·安全·web安全·php
恋猫de小郭1 小时前
Flutter Zero 是什么?它的出现有什么意义?为什么你需要了解下?
android·前端·flutter
崔庆才丨静觅8 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60618 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了9 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅9 小时前
实用免费的 Short URL 短链接 API 对接说明
前端