如何在 JavaScript 中模拟操作符重载

790 阅读2分钟

起因

JavaScript 本身目前并不支持操作符重载这种功能,很多时候如果需要处理不是数字的话很麻烦。或许你会封装成函数处理,但类似这样的代码却不是很直观。

const object = div(add(a, b), sub(c, add(d, mul(e, f))))

如果支持操作符重载能够像下面这样多好

const object = (a + b) / (c - (d + e * f))

方案

  1. 当然可以通过 Babel 插件在编译时修改源码实现,npm 中也存在一些这样的插件。但这样的代码仅能够在编译后运行,而且这样的代码在 TypeScript 中会直接报错。

  2. 后来我就想到可以通过 带标签的模板字符串 来模拟实现。

原理

const calc = (strings, ...arg) => {
  console.log(strings) // ['', ' + ', '']
  console.log(arg) // [a, b]
}

const object = calc`${a} + ${b}`

带标签的模板字符串其实将模版字符串拆分传递给了一个函数,这时就可以用函数解析模板字符串内容。

第一个参数包含一个字符串值的数组,其余的参数与表达式相关。这样只需遍历 strings 找到对应位置上的操作符、参数,将数据进行对应处理就完成了。

一个简单的字符串例子

当然上面例子仅仅是按照顺序计算的。但按照习惯,操作符或许还应该具有一定的优先级,而且还要实现分组操作符等功能

封装

根据原理和需求,我将核心代码封装开源在 tagged-operator,对实现感兴趣可以看看源码。npm 包 tagged-operator 已经发布,也可以安装尝试一下。

优势

  1. 这种方式相比调用函数看起来会更直观些
  2. 这里操作符其实就是普通字符串,理论上可以用任何符号、文字等作为操作符
  3. 原生支持,而且理论上经过 Babel 编译最低可以运行在 ES2015 以上
  4. 支持在 TypeScript 中使用,能够进行统一的类型限制

劣势

  1. 第一见这种代码可能比较难以理解,如果滥用一些奇怪的符号可能更加让人摸不到头脑
  2. 写法依旧有些笨重
  3. 类型支持不能像封装成函数那样限制每个参数类型,仅能够统一每个参数类型

不知道是否还有其它的方式实现,也可以交流下