如果我有一个对象:
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
现在应该是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;
}
但通常我们需要使用函数(并为解释器提供自动内联的机会,如果有的话)。下一个最快的是使用&#34; global&#34;参考。速度是最快的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
的事实:函数可以以不破坏引用的方式访问它的父作用域变量(对象),因为没有本地变量已创建并且该函数处理一个范围的变量。必要的注意事项是变量没有传入&#34;并且没有这样的本地任务会破坏参考。
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
}
}
还有一点需要注意:交换功能的Javascript版本与C#略有不同,其中ref
关键字。在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)
,则引用会中断。当然,这对于封闭内部封闭以防止全球范围的污染至关重要。
如果有人发现更快的参考保留方式,请回答这个问题。