在JavaScript中对动态类型运算符进行双重调度

时间:2013-11-28 13:14:19

标签: javascript performance oop inheritance double-dispatch

我想以这样的方式制定代数表达式,以便可以交换基础数字类型。如果你愿意,可以考虑复数,大整数,矩阵等。出于这个原因,我写了add(a, b)a.add(b)而不是a + b。在静态类型语言中,我只是使用函数add的基于类型的重载来实现各种替代方法。但是对于JavaScript来说这不起作用,所以我正在寻找替代方案。执行的方法取决于两个操作数的类型。

我提出的一种方法是以下double dispatch机制:

  1. 将表达式写为a.add(b)

  2. 按以下方式为给定类型(例如我自己的Complex类型或内置Number类型)实施该方法:

    add: function(that) { that.addComplex(this); }
    

    因此第二个调用的方法名称编码其中一个操作数的类型。

  3. 实施专门的方法来处理所有组合。例如,设置

    Number.prototype.addComplex = function(that)
      { return newComplex(that.real + this, that.imaginary); }
    
  4. 假设我知道所有类型,所以我可以确保处理所有组合。我现在困扰的是这些对象的创建

    上述方法在很大程度上依赖于虚方法调度,所以我认为它需要某种继承。经典构造函数没有问题,但根据我刚才所做的this jsperf,使用构造函数创建对象往往比对象文字慢。有时速度相当慢,就像本例中的Firefox一样。所以我不愿意为每个引发这种开销,例如复数值数值中间只是为了使我的算子重载工作。

    我在这个jsperf中尝试的另一种方法是不使用原型,而是将虚方法存储为每个单个对象实例的属性。在几乎所有经过测试的浏览器上运行得非常快,但在这里我担心对象的大小。我担心有两个实际浮点值的对象,但可能只有50个不同的成员函数来处理所有的运算符重载对。

    第三种方法是使用单个add函数,以某种方式检查其参数的类型,然后根据它做出决定。可能在某些列表中查找实际实现,这些列表由一些数字类型标识符的组合索引。我还没有把它写出来进行测试,但是这种类型检查感觉很慢,而且我也怀疑JIT编译器能够优化这种奇特的函数调度。

    是否有某种方法可以将当前的JavaScript实现欺骗为使用对象创建并且不占用过多内存的对象进行适当的优化双重调度?

1 个答案:

答案 0 :(得分:1)

第三种方法看起来非常可行:

function Complex(re, im) {
    return {type:'c', re:re, im:im }
}
function Real(n) {
    return {type:'r', n:n }
}

funcs = {
    add_c_r: function(a, b) {
        console.log('add compl to real')
    },
    add_r_c: function(a, b) {
        console.log('add real to compl')
    }
}

function add(a, b) {
    return funcs["add_" + a.type + "_" + b.type](a, b);
}

add(Complex(1, 2), Real(5))
add(Real(5), Complex(1, 2))

一个额外的字段+一个间接是合理的成本。