Javascript事件以错误的顺序触发?

时间:2013-09-20 02:43:04

标签: javascript events timer settimeout

有人可以向我解释为什么在运行程序时我得到的警报总是“aaa”后面跟着“bbb”?我希望它遵循以下步骤:

  1. 我启动程序

  2. 程序运行并进入setTimeout行。它设置了计时器,这个计时器将在5秒内从此时间点开始(而不是在程序结束时)。事件队列中还没有任何内容

  3. 然后,在5秒之前,我点击该文件。当程序繁忙且仍在运行循环时,它将在事件队列中为此单击事件设置回调。这是目前偶数队列中唯一的事件(之前设置的计时器尚未解雇)

  4. 5秒过去,循环可能仍在运行,我们使用setTimeout设置的计时器触发。这使得计时器将“aaa()”回调到事件队列中,作为要执行的第二个事件回调,紧接在已经在队列中的click事件后面。

  5. 因此,当程序完成时,我希望它首先警告“bbb”,因为此事件首先被触发(5秒前点击),然后是“aaa”(setTimeout事件触发),但我总是得到“ aaa“接着是”bbb“,为什么会这样?

  6. FIFO中是不是事件队列第一个(先进先出)?它依赖于浏览器吗?这是怎么回事?

    function aaa() {
        alert("aaa");
    }
    
    setTimeout(aaa, 5000);
    
    document.onclick = function () {
        alert("bbb");
    }
    
    for (i = 0; i < 1000000; i++) {
        console.log(i);
    }
    

    JSFiddle在这里http://jsfiddle.net/sQvYG/1/

    编辑:除非我遗漏了某些内容(可能非常令人尴尬),否则上面的代码与John Resigs博客文章在http://ejohn.org/blog/how-javascript-timers-work/详细解释的内容非常相似。完全相同的事情,计时器启动,单击发生并添加到队列中。计时器触发并添加到队列中。执行结束并首先执行click处理程序,这就是我所期望的。任何人都可以解释为什么这对我来说没有按照正确的顺序发生?

    编辑:感谢bfavaretto提供了接受的答案并且非常友好地详细解释了队列,我开始明白没有人,但是有多个“任务队列”来放置事件。所以我上面最初描述的5个步骤实际上就是这些:

    步骤1.(这是相同的)我启动程序

    步骤2.(这是相同的)程序运行并进入setTimeout行。它设置了计时器,这个计时器将在5秒内从此时间点开始(而不是在程序结束时)。事件队列中还没有任何内容。

    此外,我相信此时没有任何内容放在任务队列中,因为我在mozilla dev页面https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/EventLoop上找到了这个:

    “调用setTimeout会在作为第二个参数传递的时间后向队列添加一条消息。”

    我还在Chrome中对此进行了验证,并且在实际触发时,似乎将回调添加到任务队列中。

    步骤3.然后,在通过5秒之前,我单击该文档。当程序繁忙且仍在运行循环时,它将在浏览器为此类事件(鼠标单击,ui任务队列)提供的任务队列之一中对此单击事件进行回调。这是此任务队列中唯一的事件。其他任务排队,例如目前暂停时间是空的。

    步骤4. 5秒过去,循环可能仍在运行,我们设置的计时器setTimeout触发。这使得计时器将“aaa()”回调到由浏览器提供的专门用于此类事件(超时)的任务队列中。所以现在我们有一个处理用户交互的任务队列,我们​​在那里有一个回调等待(我们点击前面第3点的回调)。我们有另一个处理超时的任务队列,我们​​在那里等待一个回调。这是aaa()方法在5秒后解雇的方法。

    步骤5.因此,当程序完成时,我们有两个任务队列,每个队列都有一个回调。现在,即使点击首先放入其任务队列,也无法保证此任务队列(用户交互或处理鼠标点击的内容)将首先得到处理。这完全取决于浏览器的实现,这是让我头疼的事情,直到bfavaretto在他的回答中解释了这一点。

    所以在这个例子中我只提到了两个任务队列,但浏览器可能还有其他任务队列。当程序运行并且繁忙时,每种类型的事件都放在任务队列中以用于其类型。程序完成后,浏览器决定哪些任务排队处理,从那里抓取回调,执行,一旦完成从同一个或另一个任务中获取某些内容,等等。因此实际上无法知道接下来将处理哪个队列。但是,每个特定队列中的任务总是按顺序处理。

    Ooff ..这似乎现在有意义了,Resig的文章给了我关于事情如何完成的整体想法,但事实证明它还有更多的东西,希望最终我能理解它。

2 个答案:

答案 0 :(得分:4)

  

程序运行并进入setTimeout行。它设置了计时器,这个计时器将在5秒内从此时间点开始(而不是在程序结束时)。事件队列中还没有任何内容

计时器回调会立即添加到队列中。如果事件循环在5000ms通过之前滴答,它将被推迟。这意味着如果for循环尚未完成,计时器将花费超过5秒。

  

然后,在通过5秒之前,我单击该文档。

这是有缺陷的。如果循环仍在运行,则UI将被阻止,并且您的点击没有机会在第一个警报(“aaa”)之前注册。

  

过了5秒,循环可能仍在运行,我们用setTimeout设置的计时器触发。

也有缺陷。在for循环结束之前,我们仍处于事件循环的第一个刻度线上。直到那时才能触发计时器回调。

答案 1 :(得分:3)

确切的行为取决于用户代理。在Chrome中,您的小提琴首先警告“aaa”,但在Firefox中它首先警告“bbb”(假设在两个情况下,当for循环阻止UI时执行鼠标点击,并且计时器到期在那个循环结束之前)。

我相信Resig的文章是他观察Web浏览器(或者某个特定的Web浏览器)在编写时(早在2008年)的表现的结果。它没有详细介绍某些行为,也许是因为当时没有明确的规范(我不确定)。采取以下段落,这是你的问题的关键(强调我的):

  

初始JavaScript程序段完成后,执行浏览器会立即询问:什么等待执行?在这种情况下,鼠标单击处理程序和计时器回调都在等待。 然后浏览器选择一个(鼠标点击回调)并立即执行。计时器将等到下一个可能的时间,以便执行。

本文没有解释为什么首先选择鼠标点击。也许Resig表示基于Firefox的行为方式。另一方面,我的直觉更接近Chrome的作用:计时器回调首先被添加到队列中,因此它首先触发。 Resig的一些陈述含糊不清,有些陈述不准确;他说定时器和间隔“触发”和“执行”而没有定义这些术语的含义(这里,例如:“当鼠标点击处理程序正在执行第一个间隔回调执行时”;“执行”似乎有两个不同的含义,即同步代码的执行,以及计时器的超时到期)。

编辑
我刚检查了Resig's book。他在该书中纳入了该文章的修订版和扩展版,以更清晰的方式处理概念和术语。他还引入了关于某些依赖于浏览器的行为的额外警告。然而,他还介绍了一些东西(可能是一个错误),使整个事情仍然令人困惑;截至今天(2013-09-20),errata中没有任何内容。

感谢您提出的问题,我现在意识到了这一点;当我最初几次阅读Resig的文章时,我以为我终于理解了计时器和事件循环的内部工作原理。但现在是时候寻找更准确的答案了,让我们转向HTML5规范,看看它的内容。

我建议你看看timers section,但关键是event loop and queued tasks上的部分:

  

事件循环具有一个或多个任务队列。任务队列是一个有序的任务列表,可以是:

     

<强>事件
  异步调度特定EventTarget对象的Event对象是一项任务。

     

注意:并非所有事件都使用任务队列调度,许多事件在其他任务期间同步调度。

     

<强>解析
  HTML解析器标记一个或多个字节,然后处理任何生成的标记,通常是一项任务。

     

<强>回调
  异步调用回调是一项任务。

     

使用资源
  当算法获取资源时,如果提取是异步发生的,那么一旦部分或全部资源可用,资源的处理就是一项任务。

     

对DOM操作做出反应
  一些元素具有响应于DOM操作而触发的任务,例如,何时将该元素插入文档中。

     

当用户代理要对任务进行排队时,它必须将给定任务添加到相关事件循环的任务队列之一。来自一个特定任务源的所有任务(例如,计时器生成的回调,为鼠标移动调度的事件,排队等待解析器的任务)必须始终添加到同一任务队列中,但可以将来自不同任务源的任务放入不同的任务队列。

     

例如,用户代理可以为鼠标和键事件(用户交互任务源)创建一个任务队列,为其他所有事件设置另一个任务队列。然后,用户代理可以在四分之三的时间内为其他任务提供键盘和鼠标事件首选项,保持界面响应但不会使其他任务队列处于饥饿状态,并且永远不会处理来自任何一个任务源的事件。

因此,我们有任务属于许多任务队列(或任务源)之一。定时器回调被添加到一个队列,并且单击事件被添加到不同的队列。每个队列都是有序的,但是用户代理可以自由决定队列如何相互优先排序。注意最后一段。 Firefox似乎优先考虑UI Events队列。 Chrome似乎优先考虑其他任务来源,其中定时器回调任务是。