5

如果我有一个对象:

var Edge = function() {
  this.a = 0;
  this.b = 0;
  this.prevEdge = null;
  this.nextEdge = null;
  // +20 other members
}

和一个功能:

function ProcessEdge(e)
{
  // some processing and then
  e = e.nextEdge;
  // some processing and then return something
}

以这种方式从其他函数调用:

var e = new Edge();
// some processing and then
var res = ProcessEdge(e);
// e should be now the same object as e.nextEdge
// but it is not, e is still e

e应该是 now ,e.nextEdge但它仍然是e,因为e = e.nextEdge;ProcessEdge()函数中破坏了引用。

第一个想法是通过ProcessEdge()使用类似的东西在函数中一个一个地分配每个成员来实现成员平等e.a = e.nextEdge.a; e.b = e.nextEdge.b; e.prevEdge = e.nextEdge.prevEdge; e.nextEdge = e.nextEdge.nextEdge;,但引用仍然被破坏。当我这样做时,我还必须逐个成员比较,意大利面条代码和它的缓慢性就显现出来了。

在C#和类似语言中,引用保留可以简单地通过ref在变量名之前使用关键字来实现,并且引用被保留,并且速度和简单性和速度符合预期。

好的,现在有效的方法(这是我迄今为止使用的方法):

function ProcessEdge(e)
{
  // some processing and then
  e.Value = e.Value.nextEdge; // instead of e = e.nextEdge;
  // some processing and then return something
}

// And the calling part:
var e = new Edge();
// some processing and then
var res = (function ()
{
  e = { Value: e };
  var $res = ProcessEdge(e);
  e = e.Value;
  return $res;
})
.call(this);

此方法工作正常,但问题是内联函数,每次var res = (function() {}...执行行时都会创建(AFAIK)。这var res...是内部循环,如果有很多数据,它会被调用很多次,在某些情况下每秒 100,000 - 500,000 次。

那么,有没有更快的方法来实现参考保留呢?


编辑:

根据下面的评论,我做了一个速度测试: http : //jsperf.com/closure-vs-compund http://jsperf.com/closure-vs-compund/2

结果是上面提到的关闭方法很慢,正如我所料。我发现最快的方法是完全删除该函数ProcessEdge(),而是将其内联。闭合方法的速度是最快方法的26.63%。所以内联方法比闭包方法快 3.7 倍。内联方法如下:

function main10(a)
{
  var res=0, res2, e, b;
  for (var i=0;i<10000;i++)
  {
    e = new Edge();
    b = new Edge();
    b.a = i*a;

    e.nextEdge = b;

    // ProcessEdge() as inline version:
    e = e.nextEdge;
    res += e.a-10;

    if (e!=b) res+=1000;
  }
  return res;
}

但是通常我们需要使用函数(如果有的话,给解释器一个自动内联的机会)。下一个最快的是使用“全局”引用。速度是最快的98.45%。

是返回值的global_val持有者(使用它是因为 Javascript 中的函数不能返回多个值)。这意味着我们在任何循环之外,并且在范围层次结构中只有一个参考对象global_val,它是在创建所有其他函数的同时创建的。该ProcessEdge()函数会覆盖 的值global_val,并且因为e.nextEdge是对象,这意味着仅轻而快地覆盖引用(没有新副本)。e = e.nextEdge导致参考损坏,但这无关紧要,因为参考现在在global_val并且我们是安全的。

main11()函数中,我们以这种方式调用 ProcessEdge():res += ProcessEdge11(e)并以这种方式将 global_val 的引用存储到 e e = global_val:。

var global_val = {};
function ProcessEdge11(e)
{
  e = e.nextEdge;
  global_val = e;
  return e.a-10;
}

function main11(a)
{
  var res=0, res2, e, b;
  for (var i=0;i<10000;i++)
  {
    e = new Edge();
    b = new Edge();
    b.a = i*a;
    e.nextEdge = b;

    // some processing and then
    res += ProcessEdge11(e);
    e = global_val;
    if (e!=b) res+=1000;  
  }  
  return res;
}

然后还有一种方法值得一提:使用function inside function. 它比上述两种方法慢(最快的 84.42 %),但相当简单。它基本上依赖于与使用相同的事实global_val:函数可以以一种不会破坏引用的方式访问它的父范围变量(对象),因为没有创建本地变量并且函数处理一个范围向上的变量。重要的注意事项是变量没有“传入”,并且没有这样的局部赋值会破坏引用。

function main8(a)
{
  var pseudo_global_ret = 0;

  var res=0, res2, e, b;
  for (var i=0;i<10000;i++)
  {
    e = new Edge();
    b = new Edge();
    b.a = i*a;

     e.nextEdge = b;

    _ProcessEdge();
    res += pseudo_global_ret;
    if (e!=b) res+=1000;
  }
  return res;

  function _ProcessEdge()
  {
    e = e.nextEdge;
    pseudo_global_ret = e.a-10; // instead of return e.a-10
  }
}

ref还有一点需要注意: Javascript 版本的交换函数与 C# 中的关键字稍有不同。在 C# 中我们可以调用swap(ref a, ref b),但在 Javascript 中这是不可能的。要走的路是:

function swap()
{
  var tmp=a;
  a=b;
  b=tmp;
}
var a={x:0}
var b={x:1}
swap();

如果尝试某种swap(a,b),则参考中断。当然,这对于包裹内部闭包以防止污染全局范围至关重要。

如果有人找到更快的参考保留方法,请回答这个问题。

4

0 回答 0