JavaScript 模拟向量运算符重载

keitaflq 9年前

来自: 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微信公众号

</div>