【问题标题】:Strange JavaScript behavior in CodePen with humongous arraysCodePen 中带有巨大数组的奇怪 JavaScript 行为
【发布时间】:2016-04-05 11:53:44
【问题描述】:

以下代码执行无提示的逻辑错误:

const arr = [];
class Point{
  constructor(){
    this.x = Math.random() * 1000000;
    this.y = Math.random() * 1000000;
  }
}
console.time('foo');
let avg = 0;

for(let i = 0; i < 114000000; i++ ){
  arr.push(new Point());
  avg += arr[i].x / 1000;
}
console.log(avg, arr.length);

// shouldn't this double the avg ?
for(let i = 0; i < 114000000; i++ ){
  avg += arr[i].x / 1000;
}

console.log(avg, arr.length);
console.timeEnd('foo');

CodePen - http://codepen.io/darkyen/pen/yOPMZg?editors=0010

可能的行为:

  • 第二个for循环后变量avg要翻倍,数组长度应该是1.14亿。

  • 我应该得到一个内存错误。

作为脚本运行时的输出:

  • avg 在第二个 for 循环之后不会改变。
  • 数组长度不是 114 Mil,(Chrome 2-3M,Firefox Dev 5 Mil,MS Edge 788k)。

【问题讨论】:

  • 一个小提示:avg 不变是由于浮点数的精度。在某些时候,avg 的值变得如此之大,以至于由于尾数的位大小,向其添加一个小数没有任何效果。
  • i&lt;114000000 可能有问题? codepen without any arrays
  • @AndersTornblad Math.random() * 1000000 / 1000 在一半的情况下应该超过 500,对于像 1261167461.290721 这样小的数字(对于我目前的运行),这绝对不足以被舍入错误/精度所吞噬
  • 啊,那是真的...如果数组停止增长.push()失败),将没有arr[i]可以从中获取.x属性,这更有可能是avg 没有增长的原因。如果对.push() 的所有调用均成功,则avg 仍将因浮动错误而关闭。但是我还是不知道为什么avg在第二次循环之后没有变化。

标签: javascript


【解决方案1】:

当您在 Codepen 中编写代码时 - 他们实际上并没有按原样执行它,而是首先对其应用一些转换。

他们将其解析为abstract syntax tree、查找循环和insert instructions explicitly,以在时间过长时停止执行循环。

当你这样做时:

for(let i = 0; i < 114000000; i++ ){
  arr.push(new Point());
  avg += arr[i].x / 1000;
}

您的代码运行方式为:

for (var i = 0; i < 114000000; i++) {
    if (window.CP.shouldStopExecution(1)) { // <- injected by Codepen!!!
        break;
    }
    arr.push(new Point());
    avg += arr[i].x / 1000;
    iter++;
}

您可以通过检查 CodePen 本身的框架代码来看到这一点。

他们在您的代码中注入shouldStopLoop 调用。 他们有一个名为 stopExecutionOnTimeout 的脚本,它执行类似这样的操作(来自 Codepen):

 var PenTimer {
   programNoLongerBeingMonitored:false,
   timeOfFirstCallToShouldStopLoop:0, // measure time
   _loopExits:{}, // keep track of leaving loops
   _loopTimers:{}, // time loops
   START_MONITORING_AFTER:2e3, // give the script some time to bootstrap
   STOP_ALL_MONITORING_TIMEOUT:5e3, // don't monitor after some time
   MAX_TIME_IN_LOOP_WO_EXIT:2200, // kill loops over 2200 ms
   exitedLoop:function(o) { // we exited a loop 
     this._loopExits[o] = false; // mark
   },
   shouldStopLoop:function(o) { // the important one, called in loops
      if(this.programKilledSoStopMonitoring)  return false; // already done
      if(this.programNoLongerBeingMonitored)return true;
      if(this._loopExits[o])  return false; 
      var t=this._getTime(); // get current time
      if(this.timeOfFirstCallToShouldStopLoop === false) 
        this.timeOfFirstCallToShouldStopLoop = t;
        return false;
      }
      var i= t - this.timeOfFirstCallToShouldStopLoop; // check time passed
      if(i<this.START_MONITORING_AFTER) return false; // still good   
      if(i>this.STOP_ALL_MONITORING_TIMEOUT){
        this.programNoLongerBeingMonitored = true;
        return false;
      }
      try{
        this._checkOnInfiniteLoop(o,t);
      } catch(n) {
        this._sendErrorMessageToEditor(); // send error about loop
        this.programKilledSoStopMonitoring=false;
        return true; // killed
      }
      return false; // no need
   },
   _sendErrorMessageToEditor:function(){/*... */
      throw "We found an infinite loop in your Pen. We've stopped the Pen from running. Please correct it or contact support@codepen.io.";
};

如果您想自己运行它 - JSBin 具有类似的功能,并且它们具有 open sourced it 作为循环保护库 - 低于 500 LoC。

【讨论】:

    【解决方案2】:

    这只是codepen 脚本运行器限制。

    我在 Chrome Developer ToolsNode.JS REPL 中运行脚本 - 一切似乎都很好。


    Codepen docs

    【讨论】:

    • 完美!这完全出乎意料......我删除了这个问题,因为它完全具有误导性。
    • 删除问题是不好的选择。它可能对一些未来的读者有所帮助。
    • 其实在标题上加codepen就好了。
    • 我开始编辑此答案,但结果过于广泛 - 所以我对此表示赞同,并发布了我自己的答案,详细说明了这一点。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-09-25
    • 2015-03-18
    • 1970-01-01
    • 1970-01-01
    • 2020-12-15
    • 1970-01-01
    相关资源
    最近更新 更多