JavaScript匿名函数和全局变量

4

我想尝试聪明地创建一个自己的等待函数(我意识到还有其他方法可以实现这一点)。所以我写了以下代码:

var interval_id;
var countdowntimer = 0;

function Wait(wait_interval) {
  countdowntimer = wait_interval;

  interval_id = setInterval(function() {
    --countdowntimer <=0 ? clearInterval(interval_id) : null;
  }, 1000);

  do {} while (countdowntimer >= 0);
}

// Wait a bit: 5 secs
Wait(5);

这个程序可以正常工作,但是存在无限循环的问题。经过检查,如果我将While循环去掉,匿名函数会被调用5次,符合预期。因此,全局变量countdowntimer被减少了。
然而,在While循环中检查countdowntimer的值时,它从未下降。这尽管在While循环中匿名函数正在被调用!
显然,有两个countdowntimer的值在浮动,但为什么?
编辑:
好的,现在我明白了,Javascript是单线程的。这 - 某种程度上 - 回答了我的问题。但是,在这个单线程的处理过程中,使用setInterval进行的所谓异步调用发生在哪个时刻?只是在函数调用之间吗?肯定不是,那么长时间执行的函数呢?

针对您的编辑,请查看这篇文章 - Matt Ball
4个回答

5

变量不会存在两份。除非你使用新的Web Workers,否则Web浏览器中的Javascript是单线程的。因此,匿名函数永远没有机会运行,因为Wait正在占用解释器。

在基于浏览器的Javascript中,不能使用忙等待函数;其他任何操作都将被阻塞(即使在大多数其他环境中,它们也是一个坏主意)。必须使用回调函数。以下是对此的最简化改写:

var interval_id;
var countdowntimer = 0;

function Wait(wait_interval, callback) {
    countdowntimer = wait_interval;

    interval_id = setInterval(function() {
        if (--countdowntimer <=0) {
            clearInterval(interval_id);
            interval_id = 0;
            callback();
        }
    }, 1000);
}

// Wait a bit: 5 secs
Wait(5, function() {
    alert("Done waiting");
});

// Any code here happens immediately, it doesn't wait for the callback

编辑 回答您的后续问题:

但是,在处理此单个线程的过程中,所谓的使用setInterval的异步调用在哪个时刻实际发生?仅在函数调用之间吗?当然不是,对于执行时间长的函数怎么办?

基本上是这样的,因此重要的是函数不要运行时间过长。(严格来说甚至不是在函数调用之间,因为如果您有一个调用三个其他函数的函数,则解释器在运行该(外部)函数时无法执行任何其他操作。)解释器实际上维护了一个它需要执行的函数队列。它从执行任何全局代码(很像一个大型函数调用)开始。然后,当事件发生时(用户输入事件、达到通过setTimeout安排的回调调用的时间等),解释器将它需要进行的调用推到队列中。它总是处理队列前面的调用,因此事情可能会堆积起来(比如您的setInterval调用,尽管setInterval有点特殊——如果先前的回调仍在队列中等待处理,则它不会排队下一个回调)。因此,请考虑您的代码何时获取控制权以及何时释放控制权(例如,通过返回)。只有在您释放控制权并在将其再次返回给您之前,解释器才可以执行其他操作。此外,在一些浏览器上(例如IE),同一线程还用于绘制UI等方面,因此DOM插入(例如)将不会显示,直到您将控制权释放回浏览器,以便它继续进行绘制。

在Web浏览器中使用Javascript时,您确实需要采用基于事件驱动的方法来设计和编写解决方案。典型的例子是提示用户提供信息。在非事件驱动的世界中,您可以这样做:

// Non-functional non-event-driven pseudo-example
askTheQuestion();
answer = readTheAnswer();      // Script pauses here
doSomethingWithAnswer(answer); // This doesn't happen until we have an answer
doSomethingElse();

在事件驱动的世界中,这种方法行不通。相反,你需要这样做:
askTheQuestion();
setCallbackForQuestionAnsweredEvent(doSomethingWithAnswer);
// If we had code here, it would happen *immediately*,
// it wouldn't wait for the answer

因此,例如,askTheQuestion 可能在页面上覆盖一个 div,并提示用户填写各种信息的字段,并为他们提供“确定”按钮,以便在完成后单击。 setCallbackForQuestionAnswered 实际上是钩住“确定”按钮上的 click 事件。 doSomethingWithAnswer 将收集字段中的信息,移除或隐藏 div,并对信息进行处理。

1
哈哈!你把这称作极简重构?那这个怎么样:setTimeout,function(){alert("Done Waiting");}, 5000); ;) - Sean Kinsey
函数 Wait(interval, callback) { return setTimeout(callback, interval * 1000); } - Pablo Cabrera
1
@Sean:我所说的“极简主义”是指“尽可能少地更改以使其功能正常”(例如,通过解释),而不是“制作最小化的东西”。他已经拥有了最小化的东西:setIntervalsetTimeout本身! :-) - T.J. Crowder
我会接受你的答案,因为你给出了最详细的回答。 - Guillermo Phillips

3
大多数JavaScript实现都是单线程的,因此当它执行while循环时,它不会让其他任何东西执行,因此interval在while运行时永远不会运行,从而造成无限循环。
有许多类似的尝试来创建JavaScript中的sleep/wait/pause函数,但由于大多数实现都是单线程的,因此在睡眠期间它根本不会让您做其他事情(!)。
延迟的替代方法是编写超时。它们可以推迟一段代码的执行,但您必须将其分成许多函数。您始终可以内联函数,因此更容易跟踪(并在同一执行上下文中共享变量)。
还有一些库可以为JavaScript添加一些语法糖,使其更易读。
编辑:John Resig本人有一篇关于JavaScript计时器如何工作的博客文章。他几乎详细解释了它。希望对你有所帮助。

2
实际上,由于JavaScript是单线程的,所以可以保证间隔函数不会在循环运行时运行。
没有人之前制作过Wait的原因(也有很多人尝试过)是它根本无法实现。
您将不得不将函数分解为几个部分,并使用setTimeout或setInterval进行安排。
//first part
...
setTimeout(function(){
    //next part
}, 5000/*ms*/);

根据您的需求,这应该被实现为状态机。


0

为什么不直接更改setInterval的毫秒属性,而不是使用全局countdowntimer变量? 像这样:

var waitId;

function Wait(waitSeconds)
{
    waitId= setInterval(function(){clearInterval(waitId);}, waitSeconds * 1000);
}

因为我会使用setTimeout。关键是我想要同步等待而不是异步等待。 - Guillermo Phillips

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