JavaScript递归优化

10

有个同事开玩笑地发送了一封包含以下内容的 html 文件,旨在让你的浏览器崩溃

<html>
<script type="text/javascript">
function crash(){
  for(i=0;i<5000000001;i++){
    document.write(i);
  }
}
</script>
<body onload="crash();">
</body>
</html>
无论如何,在Chrome中它的效果并不好,因此有人提出了一个友好的竞赛,看谁能够尽快编写Javascript代码,使页面在不导致浏览器无响应或崩溃的情况下计数到5,000,000,000。我想出了以下一段Javascript代码,旨在在Chrome中使用。
<html>
<script type="text/javascript">
function countToFiveBillion(counter, num){
  if(num < 5000000000)
  {
    num++;
    if(num % 18700 == 0){
      counter.innerHTML = num;
      setTimeout(function() {countToFiveBillion(counter, num)}, 1);
    } else {
      countToFiveBillion(counter, num);
    }
  }
}
function initiateCountDown()
{
   var counter = document.getElementById("counter");
   var num = +counter.innerHTML;
   countToFiveBillion(counter, num);
}
</script>
<body onload="initiateCountDown();">
<div id="counter">0</div>
</body>

</html>

这段代码只能在Chrome浏览器中运行的原因是我使用了setTimeout调用来避免在Chrome中创建一个栈溢出。(Chrome允许递归调用的最大堆栈大小超过其他所有浏览器)。

有没有办法让计数更快?我认为我可以在它引起溢出之前稍微增加计数的数量(不到100)。唯一的限制是必须在计数时显示尽可能多的数字。


改进后的代码:

<html>
<script type="text/javascript">
var counter;
var num = 0;
function countToFiveBillion(){
    if(num < 5000000000)
    {
    num++;
    if(num % 18701 == 0){
        setTimeout("countToFiveBillion()", 1);
            counter.value = num;
        } else {
        countToFiveBillion();
    }
    } else {
        counter.value = "number greater than 5 Billion";
    }
}
function initiateCountDown()
{
   counter = document.getElementById('counter');
   countToFiveBillion();
}
</script>
<body onload="initiateCountDown();">
    <input type="text" id="counter" value="0" />
</body>

</html>
  • 将计数器和元素设为全局变量
  • 使用文本输入代替 div 元素
  • 将更新 UI 放在设置回调之后
2个回答

2
不要使用 .innerHTML = ... 来展示数字。根据这个测试,设置输入元素的 value 属性更加高效。请参考 此测试
<input type="text" id="counter" value="0" />

不要构造新函数,我建议使用全局/局部变量,并将函数引用作为参数传递给setTimeout,或在初始化时使用setInterval

  • setTimeout("countToFiveBillion()",1)替换为setTimeout(countToFiveBillion,0)。 说明:"countToFiveBillion()"效率低下;首先,字符串被转换为函数并调用,然后另一个函数调用跟随。建议的函数只需要调用一个函数,而不创建新的函数。它也可以更快地运行。
  • 解除限制(我能够将18701增加到20000)。将限制提高到这样的圆整数后,我注意到counter值在每个超时之间更新。
  • 修复了实现中的一些错误(在else块中用.value替换了.innerHTML)。

相关代码:

<input type="text" id="counter" />
<script>
var counter, num = 0;
function countToFiveBillion(){
    if(num < 5e9)
    {
        if(++num % 18701 == 0){
            setTimeout(countToFiveBillion, 0);
            counter.value = num;
        } else {
            countToFiveBillion();
        }
    } else {
        counter.value = "number greater than 5 Billion";
    }
}
function initiateCountDown(){
    counter = document.getElementById('counter');
    counter.value = num; //Init, show that the script is 
    countToFiveBillion();
}
window.onload = initiateCountDown;
</script>

Fiddle: http://jsfiddle.net/KTtae/


我不确定如何在JS中将函数作为引用传递,我应该只是使用 this.countToFiveBillion 而不是目前所做的方式吗? - msarchet
全局声明变量,然后将 setTimeout(..) 替换为 setTimeout(countToFiveBillion, 1) - Rob W
这提供了轻微的整体速度改进,但我正在寻找更快的方法来实际进行递归或避免堆栈问题。 - msarchet
我认为这是在线性情况下可以达到的最快速度。 - msarchet

0

Webworker示例,index.html

<!DOCTYPE HTML>
<html>
<head>
    <title>5 billion</title>
</head>
<body>
    <input type="text" id="counter" value="0" />
    <script type="text/javascript" charset="utf-8">
        var 
            iCounter = document.getElementById('counter')
            , counter = new Worker('worker.js');

        iCounter.value = 0;
        counter.addEventListener('message', function (e) {
            iCounter.value = e.data;
        }, false);
    </script>
</body>
</html>

worker.js:

for (var i = 0; i < 5e9; i++) {
    if (i % 18701 === 0) {
        postMessage(i);
    }
}

计数可以根据需要分成多个工作人员。

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