繁体   English   中英

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

[英]Javascript events fire in wrong order?

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

  1. 我启动程序

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

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

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

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

事件队列不是先进先出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.因此,当程序完成时,我们有两个任务队列,每个队列都有一个回调。 现在,即使click首先被放入其任务队列,也无法保证将首先处理此任务队列(用户交互或处理鼠标点击的事物)。 这完全取决于浏览器的实现 ,这是令我头疼的事情,直到bfavaretto在他的回答中解释了这一点。

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

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

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

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

然后,在5秒之前,我点击文档。

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

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

也有缺陷。 我们仍然在事件循环的第一个刻度上,直到for循环结束。 在此之前无法触发计时器回调。

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

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

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

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

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

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

我建议你看一下定时器部分 ,但关键是在事件循环和排队任务的部分找到:

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

活动
异步调度特定EventTarget对象的Event对象是一项任务。

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

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

回调
异步调用回调是一项任务。

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

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

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

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM