如果我有一个对象:
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)
,则参考中断。当然,这对于包裹内部闭包以防止污染全局范围至关重要。
如果有人找到更快的参考保留方法,请回答这个问题。