JavaScript V8 优化和“泄漏参数”

4

我在多个地方读到,谨慎使用arguments对象是明智的,这是可以接受的...

    var i = arguments.length, args = new Array(i);
    while (i--) args[i] = arguments[i];

但是,这样做是否可行呢?...

function makeArray (l) {
    var i = l.length, array = new Array(i);
    while (i--) array[i] = l[i];
    return array;
};
//...
//EDIT: put the function on an object to better represent the actual case
var o = {};
o.f = function (callback) {
    var args = makeArray (arguments);
    callback.apply(args[0] = this, args);
};

什么是“泄漏参数”,它如何影响优化?

我专注于V8,但我认为它也适用于其他编译器?

为了证明它的有效性...

function makeArray (l) {
    var i = l.length, array = new Array(i);
    while (i--) array[i] = l[i];
    return array;
};
//...
//EDIT: put the function on an object to better represent the actual case
var o = {};
o.f = function (callback) {
    var args = makeArray (arguments);
    callback.apply(args[0] = this, args);
};
o.m = "Hello, ";
function test(f, n) {
    alert(this.m + " " + n)
}
o.f(test, "it works...")


你应该使用var o={f:function(){ [code] }};代替var o={}; o.f=function(){ [code] }; - Ismael Miguel
实际上它们是等价的 ;) - Cool Blue
是的,它们是相同的。但不同之处在于,您使用我提供的代码创建了一个带有方法的对象。而使用您的代码,您创建了一个空对象,然后修改它以插入新的方法。 - Ismael Miguel
好的,最终结果是一样的。我这么做是为了更贴近我正在分析的实际代码。我知道有几种方法可以做到这一点,但你为什么建议按照你提出的方式来做呢?有什么特别的原因吗? - Cool Blue
我想到了两个好处:1- 最基本的好处是语法本身。您可以在那里声明每种方法和属性,无需进行任何更改。您不需要瞎弄来查找方法/属性的定义位置。2-由于您一次完成所有操作,因此编译器可以在创建对象和访问任何方法/属性时优化代码。 - Ismael Miguel
2个回答

9
arguments存在的问题与本地的evalwith一样:它们会导致别名。 别名会破坏各种优化,因此即使您启用了这些类型函数的优化,您最终可能只会浪费时间。对于JIT而言也是个问题,因为在编译器中花费的时间就不是在运行代码了(尽管优化管道中的某些步骤可以并行运行)。

arguments泄露导致别名问题:

function bar(array) {    
    array[0] = 2;
}

function foo(a) {
    a = 1;
    bar(arguments);
    // logs 2 even though a is local variable assigned to 1
    console.log(a);
}
foo(1);

请注意,严格模式可以消除这个问题:
function bar(array) {    
    array[0] = 2;
}

function foo(a) {
    "use strict";
    a = 1;
    bar(arguments);
    // logs 1 as it should
    console.log(a);
}
foo(1);

然而,严格模式也没有进行优化,我不知道任何合理的解释,除了基准测试不使用严格模式,严格模式很少被使用。这可能会改变,因为许多es6功能需要严格模式,但另一方面,在es6中不需要arguments所以…


好的,但是优化的唯一方法是将复制“arguments”的while循环放在原始函数内部,不能外包...对吗? - Cool Blue

1
这是一个双重问题:
  1. 您将失去所有可能的优化和分支预测
    arguments对象是不可预测的。
  2. 您会泄漏内存。非常糟糕!
考虑以下代码(自行运行风险自负!):

function a(){return arguments;}

x=a(document.getElementsByTagName('*'));

window._interval=setInterval(function(){

    for(var i=0;i<1e6;i++)
    {
        x=a(x);
    }
},5000);
*{font-family:sans-serif;}
<p>Warning! This may overheat your cpu and crash your browser really badly!</p>

<p>I'm not responsible for any software and hardware damages!</p>

<p><b>Run at your own risk!!!</b></p>

<p>If you ran this code, press F5 to stop or close the browser.</p>

<p>If you want, you can try to <button onclick="window.clearInterval(window._interval);window.x='';">stop it</button>. (the RAM may remain unchanged)</p>

现在运行时,观察你的RAM会增加,每5秒钟大约增加300-500MB(RAM使用量)。
一个简单的实现只是简单地传递参数可能会导致这些问题。

更不用说你的代码通常会稍微慢一些。


请注意这个:

注意:

function a(){return arguments;}
function b(arg){return arg;}

x=a(document.getElementsByTagName('*'));

window._interval=setInterval(function(){

    for(var i=0;i<1e6;i++)
    {
        x=b(x);
    }
},5000);
*{font-family:sans-serif;}
<p>This may be safe, but be careful!</p>

<p>I'm not responsible for any software and hardware damages!</p>

<p><b>Run at your own risk!!!</b></p>

<p>If you ran this code, press F5 to stop or close the browser.</p>

<p>If you want, you can try to <button onclick="window.clearInterval(window._interval);window.x='';">stop it</button>.</p>

这段代码与之前的代码不会产生相同的效果。
这是因为b()返回的是同一个变量,而不是一个新的引用指向新的arguments对象。
这是一个非常重要的区别。


“function a(arg){return arg;}” 不会产生相同的结果吗? - Cool Blue
@CoolBlue 不是的,在那种情况下你返回的是同一个变量。而在我的情况下,你会一遍又一遍地返回一个新的arguments对象的引用 - Ismael Miguel
@CoolBlue 我已经添加了一段代码来展示我的观点。你可以尝试一下。但是要非常小心。 - Ismael Miguel
@CoolBlue 我没有看到任何闭包。而且你的代码仍然有(在某种程度上)与第一个示例相同的功能。你的代码将arguments对象传递给另一个函数,然后用于调用另一个函数,该函数包含在arguments对象中,在其中将window对象分配给它。 - Ismael Miguel
是的,你说得对...我过于简化了。我已经编辑过了,让它更像实际的代码。(顺便说一下,这不是我的代码)。 - Cool Blue
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接