在微调性能时,多次调用JavaScript方法的最佳方法是什么?

Kil*_*and 6 javascript methods performance object call

我一直在研究JavaScript性能.我已经了解到,在不止一次访问时,通常最好将闭包变量和类成员复制到本地范围以加快速度.例如:

var i = 100;
var doSomething = function () {
    var localI = i;
    // do something with localI a bunch of times

    var obj = {
        a: 100
    };
    var objA = obj.a;
    // do something with objA a bunch of times
};
Run Code Online (Sandbox Code Playgroud)

我理解这一点; 它为解释器添加了一个快捷方式,用于按名称查找值.在处理方法时,这个概念变得非常不清楚.起初,我认为它会以同样的方式工作.例如:

var obj = {
    fn: function () {
        // Do something
        return this.value;
    },
    value: 100
};
var objFn = obj.fn
objFn();
// call objFn a bunch of times
Run Code Online (Sandbox Code Playgroud)

事实上,这根本不起作用.访问这样的方法会将其从其范围中删除.当它到达this.value行时,这指的是window对象,this.value可能是未定义的.我可以使用objFn.call(obj)将其范围传回到它中,而不是直接调用objFn和丢失范围,但这是否比原始的obj.fn()更好或更差?

我决定写一个脚本来测试这个,我得到了非常令人困惑的结果.该脚本对多个测试进行迭代,这些测试循环执行上述函数调用多次.每次测试所花费的平均时间输出到身体.

使用许多简单方法创建对象.额外的方法用于确定解释器是否必须更加努力地找到特定方法.

测试1简单地调用this.a();
测试2创建一个局部变量a = this.a然后调用a.call(this);
测试3使用YUI的绑定函数创建局部变量以保留范围.我评论了这一点.由YUI创建的额外函数调用使这种方式变慢.

除了使用z而不是a之外,测试4,5和6是1,2,3的副本.

YUI的后续功能用于防止失控的脚本错误.时间在实际测试方法中完成,因此setTimeouts不应影响结果.每个函数总共调用10000000次.(如果要运行测试,可以轻松配置.)

这是我用来测试的整个XHTML文档.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xml:lang="en" dir="ltr">
    <head>
        <script type="text/javascript" src="http://yui.yahooapis.com/combo?3.1.2/build/yui/yui-min.js"></script>
        <script>
            YUI().use('node', function (Y) {
                var o = {
                    value: '',
                    a: function () {
                        this.value += 'a';
                    },
                    b: function () {
                        this.value += 'b';
                    },
                    c: function () {
                        this.value += 'c';
                    },
                    d: function () {
                        this.value += 'd';
                    },
                    e: function () {
                        this.value += 'e';
                    },
                    f: function () {
                        this.value += 'f';
                    },
                    g: function () {
                        this.value += 'g';
                    },
                    h: function () {
                        this.value += 'h';
                    },
                    i: function () {
                        this.value += 'i';
                    },
                    j: function () {
                        this.value += 'j';
                    },
                    k: function () {
                        this.value += 'k';
                    },
                    l: function () {
                        this.value += 'l';
                    },
                    m: function () {
                        this.value += 'm';
                    },
                    n: function () {
                        this.value += 'n';
                    },
                    o: function () {
                        this.value += 'o';
                    },
                    p: function () {
                        this.value += 'p';
                    },
                    q: function () {
                        this.value += 'q';
                    },
                    r: function () {
                        this.value += 'r';
                    },
                    s: function () {
                        this.value += 's';
                    },
                    t: function () {
                        this.value += 't';
                    },
                    u: function () {
                        this.value += 'u';
                    },
                    v: function () {
                        this.value += 'v';
                    },
                    w: function () {
                        this.value += 'w';
                    },
                    x: function () {
                        this.value += 'x';
                    },
                    y: function () {
                        this.value += 'y';
                    },
                    z: function () {
                        this.value += 'z';
                    },
                    reset: function () {
                        this.value = '';
                    },
                    test1: function (length) {
                        var time = new Date().getTime();

                        while ((length -= 1)) {
                            this.a();
                        }
                        return new Date().getTime() - time;
                    },
                    test2: function (length) {
                        var a = this.a,
                        time = new Date().getTime();

                        while ((length -= 1)) {
                            a.call(this);
                        }
                        return new Date().getTime() - time;
                    },
                    test3: function (length) {
                        var a = Y.bind(this.a, this),
                        time = new Date().getTime();

                        while ((length -= 1)) {
                            a();
                        }
                        return new Date().getTime() - time;
                    },
                    test4: function (length) {
                        var time = new Date().getTime();

                        while ((length -= 1)) {
                            this.z();
                        }
                        return new Date().getTime() - time;
                    },
                    test5: function (length) {
                        var z = this.z,
                        time = new Date().getTime();

                        while ((length -= 1)) {
                            z.call(this);
                        }
                        return new Date().getTime() - time;
                    },
                    test6: function (length) {
                        var z = Y.bind(this.z, this),
                        time = new Date().getTime();

                        while ((length -= 1)) {
                            z();
                        }
                        return new Date().getTime() - time;
                    }
                },
                iterations = 100, iteration = iterations, length = 100000,
                t1 = 0, t2 = 0, t3 = 0, t4 = 0, t5 = 0, t6 = 0, body = Y.one('body');

                body.set('innerHTML', '<span>Running ' + iterations + ' Iterations&hellip;</span>');
                while ((iteration -= 1)) {
                    Y.later(1, null, function (iteration) {
                        Y.later(1, null, function () {
                            o.reset();
                            t1 += o.test1(length);
                        });
                        Y.later(1, null, function () {
                            o.reset();
                            t2 += o.test2(length);
                        });
                        /*Y.later(1, null, function () {
                            o.reset();
                            t3 += o.test3(length);
                        });*/
                        Y.later(1, null, function () {
                            o.reset();
                            t4 += o.test4(length);
                        });
                        Y.later(1, null, function () {
                            o.reset();
                            t5 += o.test5(length);
                        });
                        /*Y.later(1, null, function () {
                            o.reset();
                            t6 += o.test6(length);
                        });*/
                        if (iteration === 1) {
                            Y.later(10, null, function () {
                                t1 /= iterations;
                                t2 /= iterations;
                                //t3 /= iterations;
                                t4 /= iterations;
                                t5 /= iterations;
                                //t6 /= iterations;

                                //body.set('innerHTML', '<dl><dt>Test 1: this.a();</dt><dd>' + t1 + '</dd><dt>Test 2: a.call(this);</dt><dd>' + t2 + '</dd><dt>Test 3: a();</dt><dd>' + t3 + '</dd><dt>Test 4: this.z();</dt><dd>' + t4 + '</dd><dt>Test 5: z.call(this);</dt><dd>' + t5 + '</dd><dt>Test 6: z();</dt><dd>' + t6 + '</dd></dl>');
                                body.set('innerHTML', '<dl><dt>Test 1: this.a();</dt><dd>' + t1 + '</dd><dt>Test 2: a.call(this);</dt><dd>' + t2 + '</dd><dt>Test 4: this.z();</dt><dd>' + t4 + '</dd><dt>Test 5: z.call(this);</dt><dd>' + t5 + '</dd></dl>');
                            });
                        }
                    }, iteration);
                }
            });
        </script>
    </head>
    <body>
    </body>
</html>
Run Code Online (Sandbox Code Playgroud)

我在三种不同的浏览器中使用Windows 7运行它.这些结果以毫秒为单位.

Firefox 3.6.8

Test 1: this.a();
    9.23
Test 2: a.call(this);
    9.67
Test 4: this.z();
    9.2
Test 5: z.call(this);
    9.61
Run Code Online (Sandbox Code Playgroud)

Chrome 7.0.503.0

Test 1: this.a();
    5.25
Test 2: a.call(this);
    4.66
Test 4: this.z();
    3.71
Test 5: z.call(this);
    4.15
Run Code Online (Sandbox Code Playgroud)

Internet Explorer 8

Test 1: this.a();
    168.2
Test 2: a.call(this);
    197.94
Test 4: this.z();
    169.6
Test 5: z.call(this);
    199.02
Run Code Online (Sandbox Code Playgroud)

Firefox和Internet Explorer产生了我的预期结果.测试1和测试4相对接近,测试2和测试5相对接近,测试2和测试5比测试1和测试4花费更长时间,因为有一个额外的函数调用过程.

Chrome我完全不懂,但它的速度要快得多,也许不需要调整亚毫秒级的性能.

有没有人对结果有很好的解释?多次调用JavaScript方法的最佳方法是什么?

小智 1

好吧,只要你的网站有 IE8 用户作为访问者,这就无关紧要了。使用 1 或 3(用户不会看到差异)。

对于“为什么”这个问题,可能没有一个好的答案。当谈到优化时,这些脚本引擎可能会专注于优化他们在现实生活中经常看到的场景,在这些场景中,优化可以被证明是正确的,在哪些地方它会产生影响,并且在某种程度上它会失效最少的测试量。