JavaScript 模拟向量运算符重载
来自: http://www.w3ctech.com/topic/1695
写在前面, 本文中实现的运算符重载有很多局限, 完全不能实用, 仅供烧脑和娱乐.
很久以前做过比较完善的 函数重载实现 , 然后自从知道 valueOf 这个东西, 就一直有想搞运算符重载的念头. 搜了下居然搜到自己 11 年发的 帖子 , 对 valueOf 进行了简单的利用.
前些时候在公司做分享, 主题是 WAT JavaScript Explained, 最后是以 JavaScript 尝试重载向量的 +-*/ 运算符结束的, 下文则是在分享中设计的效果与方法.
效果
第一种实现
Vector.createVector('a', 1, 2); Vector.createVector('b', 4, 6); Vector.createVector('c'); c = a + b; c = a - b; c = a * 2; c = b / 4;
第二种实现
Vector.createVector('a', 1, 2); Vector.createVector('b', 4, 6); Vector.createVector('c', 2, 1); Vector.createVector('d'); d = a + b - c; d = -a - b + c;
文中的技巧还可以实现类似的效果:
let a = new Vector(1, 2); let b = new Vector(4, 6); let c = new Vector(4, 6); let d = calc(a + b - c);
方法
细心的同学可能已经发现, 上面提到的例子中, 要么使用了全局变量, 要么需要包裹一个函数 calc . 通过重写 valueOf 方法, 我们可以知道哪些向量参与了运算. 但仅仅如此, 还无法知道参与运算的运算符, 也无法知道第一种实现中类似于 a * 2 和 b / 4 中的 2 和 4 . 但如果我们可以获得整个表达式的值, 就可以从某种程度窥探发生了怎样的运算.
两个向量的 + 和 -
前面我们提到了重写 valueOf 方法获取参与了运算的向量, 但为了知道发生了怎样的运算, 我们还需要为 valueOf 构造特别的返回值. 比如第一个参与运算的向量 a 对应的值为 1 , 第二个向量 b 对应的值为 2 , 那么 a + b 的值则为 3 , a - b 则为 -1 . 这样一来, 通过 setter 或者 calc 函数我们就可以根据表达式的值得知发生了什么运算.
虽然只提到了 + 和 - , 同样的方法我们还可以实现向量的叉乘.
一个向量一个标量的 * 和 /
对于四则运算中的原始值, JavaScript 并不会调用对应的 valueOf . 这样一来, 我们则需要争取通过获得的表达式的值获取运算符和标量. 或者, 或许我们并不需要知道到底是哪一个运算符, 只需要知道这个向量需要缩放的比例. 上面我们提到了第一个参与运算的向量 valueOf 的值是 1 , 这自然而然地带来了一个好处: 表达式 a * n ( a 为向量, n 为标量) 的值是 n , b / m ( b 为向量, m 为标量) 的值则是 1 / m . 向量需要缩放的比例正好是表达式最后的值! 现在知道了参与运算的向量和它需要被缩放的比例, 也就自然可以计算出结果.
多个向量的 + 和 -
上面提到的方式中, 构造 valueOf 的值相对简单, 只要不为零都是可以的. 然而很显然, 如果向量数量大于 2, 这种构造的方式得出的表达式的值很难再推出发生了哪些运算. 不过仅仅是针对 + 和 - 两种运算, 我们还是有办法区分一定数量的向量参的运算.
思考表达式 a + b - c + d - e , 怎样可以使它的值包含所有的运算符信息? 可能有的同学也会立即把它和 flags 或者 enums 之类的联系到一起:
let flagA = 0b0001; let flagB = 0b0010; let flagAB = flagA | flagB; // 0b0011
如果我们可以把类似的性质应用到加减上, 就可以保留一定数量的运算符信息了. 考虑三进制数的加减: 1111 + 1 - 10 + 100 - 1000 = 0202. 和表达式 a + b - c + d - e 联系起来, 0202 中的四个数字分别对应了 - ( e ), + ( d ), - ( c ) 和 + ( b ).
这样一来, 我们为第一个参与运算的向量构建足够大的每一位都为 1 的三进制数作为 valueOf 的返回值, 为随后参与运算的向量分别构建 1, 10, 100 这样的三进制数, 就可以通过表达式的值获知几十个参与运算的向量对应的符号了. 对于首个元素前面有负号的情况, 则可以通过整个表达式的正负来做区分.
实现
上面提到的两种效果的实现都可以在这个仓库找到: vilic/js-operator-overloading .
扫码关注w3ctech微信公众号