以下JS函数为什么会导致浏览器进程崩溃?

3
var wait = function (milliseconds) {
    var returnCondition = false;
    window.setTimeout(function () { returnCondition = true; }, milliseconds);
    while (!returnCondition) {};
};

我知道已经有很多帖子讲解为什么不要尝试在Javascript中实现wait()sleep()函数。因此,本文并不是为了使其可用于实际应用目的,而是为了证明概念的可行性。

尝试中...

console.log("Starting...");wait(3000);console.log("...Done!");

我的浏览器会卡住。为什么wait()似乎永远不会结束?

编辑:感谢目前为止的回答,我不知道while循环永远不允许任何其他代码执行。

那么这个是否可行呢?

var wait = function (milliseconds) {
    var returnCondition = false;
    var setMyTimeOut = true;
    while (!returnCondition) {
        if (setMyTimeOut) {
            window.setTimeout(function() { returnCondition = true; }, milliseconds);
            setMyTimeOut = false;
        }
    };
    return;
};

1
JavaScript 是单线程的,而且你的 while 循环会阻塞任何其他代码的执行...这就是主要原因。 - devnull69
当线程被锁定时,超时不起作用,应该像这样 - adeneo
我不清楚你需要一个 wait() 函数的原因。你几乎肯定是在错误地使用它™。 - Madara's Ghost
1
回应编辑:不,这样行不通。再次强调,您在当前执行路径中从未将returnCondition设置为true,因此最终会导致无限循环。无论您是在循环内部还是外部安排超时,它都不会执行,直到脚本的最后一行完成。 - Amadan
那么 while 循环内部到底发生了什么? - connexo
显示剩余3条评论
2个回答

5

JavaScript在单线程中执行。只有在一个执行路径退出后,另一个执行路径才能开始。因此,当您启动wait(3000)时,会发生以下情况:

  • returnCondition被设置为false
  • 计划了一个超时
  • 启动了一个无限循环。

每个<script>标签、每个事件处理以及每个超时(在浏览器的情况下还包括UI刷新)都会启动一个独立的执行路径。因此,3000ms的超时不能保证在3000ms内运行,而是在引擎处于“空闲”状态的任何时间之后。

wait函数永远不会退出,因此您的脚本执行路径永远不会结束,计划的超时轮也永远不会到来。

编辑:

这意味着,一旦开始<script>标签或Node.js开始执行JavaScript文件,执行必须达到底部,然后才能发生其他事情。如果一个函数作为事件或超时的结果启动,那么该函数需要退出,然后才能发生其他事情。

<script>
  console.log("script top");
  function theTimeout() {
    console.log("timeout top");
    // something long
    console.log("timeout bottom");
  }
  setTimeout(theTimeout, 0);
  setTimeout(theTimeout, 0);
  console.log("script bottom");
</script>

这里有三个执行路径。首先是<script>标签的执行路径:它从打印“script top”开始,安排了两个“现在就执行”的超时操作,然后打印“script bottom”,最后到达<script>的结尾并且解释器处于空闲状态。这意味着它有时间执行另一个执行路径,并且有两个正在等待的超时操作,所以它选择其中一个并开始执行。当它执行时,再也没有其他东西可以执行(甚至是UI更新);即使第二个超时操作也是在“立即”调度的,它也会被留下等待直到第一个超时操作的执行路径结束。当第一个执行完毕后,第二个超时操作的机会到来,它将被执行。

我理解,至少部分地理解。如果我在 while 循环内设置超时时间会怎样? - connexo
1
如果你在一个无限循环中启动了一个超时,你会非常快地消耗内存。我不够勇敢去尝试看看Chrome是否会非常迅速地中止它,或者我的电脑是否会首先以可悲的磁盘交换堆积而崩溃。 - Amadan
即使在 while 循环内部调用 setTimeout,它的回调函数也只会在 while 循环结束时执行? - connexo
你正在告诉浏览器,在3秒后当它空闲时应该执行你的函数。 - Amadan

2

JavaScript是单线程的。当您调用setTimeout时,作为参数传递的方法会被放置到异步调用堆栈中。这意味着在setTimeout调用之后,您代码块中的下一行代码立即执行,而作为参数传递的函数将在等待方法退出后执行。

当wait函数正在运行时,您的while循环正在等待永远不会发生的条件,因为设置标志的函数将在wait函数完成后才运行。

实现wait的正确方法是:

var wait = function (milliseconds, onEnd) {

    window.setTimeout(function () { onEnd(); }, milliseconds);

};

wait(1000, function(){alert('hi')});

在这里,您需要传入一个回调函数,在超时后执行。

如果您有多个异步样式的调用,可以使用promises。Promises将使您的代码易于阅读,并且很容易将多个异步调用链接在一起。有非常好的Promise库:JQuery内置了$ .Deferred,但是如果您正在编写node.js代码,则可以使用Q。

Promise样式的实现可能如下所示:

var wait = function (milliseconds) {

    var onEnd = null;

    window.setTimeout(function () { onEnd(); }, milliseconds);

    return {

    then: function(action){
            onEnd = action;
    }

    }
};

wait(1000).then(function(){alert('hi')});

以下书籍对我理解这个主题有很大帮助:
《Async JavaScript: Build More Responsive Apps with Less Code》 by Trevor Burnham https://pragprog.com/book/tbajs/async-javascript 此外,以下链接也可能对您有所帮助: https://api.jquery.com/jquery.deferred/ https://github.com/kriskowal/q

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