setTimeout需要的时间比应该的长

时间:2014-03-29 23:56:55

标签: javascript firefox greasemonkey settimeout setinterval

有人可以解释为什么下面的setTimeout命令在Greasemonkey中执行(400-500 ms)的脚本要比在Firefox控制台中执行的时间长得多100毫秒?

var start = new Date ().getTime ();
console.log (
    new Date ().getHours ()+" : " + new Date ().getMinutes () 
    + " : " + new Date ().getSeconds () + " : " 
    + new Date ().getMilliseconds () 
); 

setTimeout (foo,100);
//foo ();

function foo () {
    var time = new Date () - start;
    console.log ("Execution time: " + time + "ms");
}

很奇怪,因为如果我为纯setTimeout(foo,100)切换foo(),Greasemonkey和Firefox控制台都会快速执行它(~10 ms)。

1 个答案:

答案 0 :(得分:5)

实际上,在我的临时系统(Win XP,FF 28.0,GM 1.15)上,这与Greasemonkey几乎没什么关系,与(A)控制台和(B)Firefox和/或你的机器有什么关系正在做。

JavaScript timers are notoriously bad。 (另见"Accuracy of JavaScript Time" by John Resig。)

您的搜索结果基于非常小的样本量,并且数据不足以开始查看准确的图片。此外,浏览器和Greasemonkey一直在针对此问题进行更改,因此您的版本可能会有所不同。

如果我们使用间隔,EG:

setInterval (foo, 100);

function foo () {
    var time = new Date () - start;
    console.log ("Execution time: " + time + "ms");
}

然后我们可以开始收集一些统计数据,看看可能会发生什么。

优化代码并添加统计信息会给出如下用户说明:

// ==UserScript==
// @name        _Greasemonkey timing test
// @include     https://stackoverflow.com/questions/22738493/*
// @grant       none
// ==/UserScript==
// @grant       GM_addStyle

/*--- Test:
        Both grant modes
        FF console
        Firebug console
        Embedded in page.
*/
var numMeasurements = 100;
var measurementList = [];
var startDate       = new Date ();
var startTime       = startDate.getTime ();

console.log (
    startDate.getHours ()+ " : " + startDate.getMinutes () + " : "
    + startDate.getSeconds () + " : " + startDate.getMilliseconds ()
);

var startDate   = new Date ();  //-- Record time just before interval start.
//setTimeout (timelog, 100);
/*--- WARNING: for delays less than about 50, system "granularity" and
    overhead effects will distort the results even more.
*/
var logTimer    = setInterval (timelog, 100);

function timelog () {
    timelog.numloops    = timelog.numloops  ||  0;

    if (timelog.numloops >= numMeasurements) {
        console.log ('===> Reached ' + timelog.numloops + ' loops.');
        clearInterval (logTimer);

        //--- Calculate stats:
        var stats           = {};
        stats.min           = Number.MAX_VALUE; //-- Always start at opposite
        stats.max           = Number.MIN_VALUE;
        stats.sum           = 0;
        stats.mean          = 0;
        stats.sumSqrs       = 0;
        stats.stdDev        = 0;
        stats.N             = measurementList.length;

        for (var J = 0;  J < stats.N;  ++J) {
            var measVal     = measurementList[J];
            stats.sum      += measVal;
            stats.sumSqrs  += measVal * measVal;

            if (measVal > stats.max)    stats.max = measVal;
            if (measVal < stats.min)    stats.min = measVal;
        }

        stats.mean          = stats.sum / stats.N;
        stats.stdDev        = Math.sqrt (
            (stats.sumSqrs / stats.N) - (stats.mean * stats.mean)
        );


        //--- Display stats:
        var decsToDisplay   = 1;
        console.log (' Measurements: ' + stats.N);
        console.log ('      Average: ' + stats.mean.toFixed (decsToDisplay) );
        console.log ('   Min to Max: ' + stats.min + ' to ' + stats.max);
        console.log ('Std Deviation: ' + stats.stdDev.toFixed (decsToDisplay) );
    }
    else {
        timelog.numloops++;
        var timeNow = new Date ();
        var timeDif = timeNow - startDate;
        measurementList.push (timeDif);
        console.log (
            '==> Execution time ('
              //-- Left-pad value for more legible column, 3 chars wide.
            + ("  " + timelog.numloops).slice (-3) + '): '
              //-- Left-pad value for more legible column, 4 chars wide.
            + ("   " + timeDif).slice (-4) + ' ms   '
            , timeNow.getTime ()
        );
        startDate   = timeNow;
    }
}


安装脚本和/或您可以see this code in action at jsFiddle

要查看Greasemonkey是否是一个因素,我们应该测试至少这些场景:

  1. Firefox控制台中的代码。
  2. 网页中的代码,包括控制台和控制台已关闭。
  3. Greasemonkey脚本中的代码,其中沙箱处于活动状态(@grant GM_addStyle设置)。
  4. Greasemonkey脚本中的代码,@grant none处于活动状态。
  5. Firebug控制台中的代码。
  6. 理想情况下,网页和系统环境应尽可能保持不变。

    使用100 ms延迟和100个样本(可能是有意义数据的最小值)进行测试,得到(所有值都以毫秒为单位):

    //-- These first were run against stackoverflow.com/q/22738493
    
                                                      Std
    Condition                      Min  Max   Avg   Deviation
    --------------------------     ---  ---  -----  ---------
    Firefox console, run 1:          0  518  138.9    133.2
    Firefox console, run 2:          1  466  215.4    209.6
    
    Firebug console, run 1:          1  144  100.5     21.8
    Firebug console, run 2:          3  209  100.9     25.2
    
    GM to FF cons, in sandbox:       0  398  135.4    112.9
    GM to FF cons, @grant none 1:    0  387  125.3     97.4
    GM to FF cons, @grant none 2:    0  563  145.2    122.0
    GM to Firebug console:          38  401  109.4     49.1
    
    //-- These were run against jsfiddle.net/caL94/2
    
    jsFiddle to FF console 1:        2  375  113.3     82.5
    jsFiddle to FF console 2:        1  575  169.7    171.1
    jsFiddle to Firebug console:    27  219  103.5     24.9
    
    jsFiddle, consoles closed 1:     0  530  105.3     57.2
    jsFiddle, consoles closed 2:     5  195  100.0     21.9
    


    从这些数字来看,应该很清楚:

    1. JavaScript定时器不适合精确定时。(但它们可能适用于大多数实际的网页用途。)
    2. Greasemonkey中的计时代码与Firebug控制台中运行的代码一样,或者不记录到任何控制台。
    3. 真正的大热门是登录Firefox的控制台( Ctrl Shift K ) - 从根本上降低了计时器的准确性。
    4. 计算机和浏览器(甚至网页)在很大程度上说明了所有影响的准确性和可重复性。
    5. 另外,请记住browsers throttle javascript timers if you switch away from the tab(失去焦点)。