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相关知识点。

相关推荐
熊的猫43 分钟前
JS 中的类型 & 类型判断 & 类型转换
前端·javascript·vue.js·chrome·react.js·前端框架·node.js
瑶琴AI前端1 小时前
uniapp组件实现省市区三级联动选择
java·前端·uni-app
会发光的猪。1 小时前
如何在vscode中安装git详细新手教程
前端·ide·git·vscode
别拿曾经看以后~2 小时前
【el-form】记一例好用的el-input输入框回车调接口和el-button按钮防重点击
javascript·vue.js·elementui
我要洋人死2 小时前
导航栏及下拉菜单的实现
前端·css·css3
川石课堂软件测试2 小时前
性能测试|docker容器下搭建JMeter+Grafana+Influxdb监控可视化平台
运维·javascript·深度学习·jmeter·docker·容器·grafana
科技探秘人3 小时前
Chrome与火狐哪个浏览器的隐私追踪功能更好
前端·chrome
科技探秘人3 小时前
Chrome与傲游浏览器性能与功能的深度对比
前端·chrome
JerryXZR3 小时前
前端开发中ES6的技术细节二
前端·javascript·es6
七星静香3 小时前
laravel chunkById 分块查询 使用时的问题
java·前端·laravel