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如何实现上面的逻辑...
先有问题再有答案
这个问题可以被拆分为哪些子任务?
链式调用如何实现?
累加后的结果如何保存
对象和数字类型做加法运算js是如何处理的
这个问题 add[1][2][3] + 4 按照 +运算符可以被分为三部分。
第一个:add[1][2][3] 链式调用并结果累加
第二个:+ 在对象和数字的转化
第三个:最原始的加法运算得出结果.
对象和数字如何计算
因为add返回的一定是一个对象 否则链式调用无法实现
所以这里涉及到了js引用类型与基础类型的转换 具体可以参考之前的文章: js基石之数据类型三:类型转换
这里列下关键步骤:
对象在转换到数字或字符串时 逻辑是相似的 只是顺序会有不同。
- 判断对象是否实现了
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();
}
}
- 如果是转换为number则调用ToPrimitive(input, 'Number')
- 如果是转换为String 则调用ToPrimitive(input, 'String')
- 不能转换为原始值走报错逻辑
- 可以转换为原始值 则返回当前的原始值 然后在继续运算 后续可能还会将这个原始值在进行类型转换。
所以在我们这个场景下 可以实现如下代码:
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相关知识点。