所有 Javascript 事件处理程序脚本均由一个主事件队列系统处理。这意味着事件处理程序一次运行一个事件处理程序,并且一个事件处理程序会运行直至完成,然后下一个准备就绪的事件处理程序才会开始运行。因此,Javascript 中不存在在多线程语言中会出现的典型竞争条件,在多线程语言中,该语言的多个线程可以同时运行(或时间切片)并在访问变量时产生实时冲突。
JavaScript 中的任何单个执行线程都会在下一个线程开始之前运行完成。这就是 JavaScript 的工作原理。从事件队列中提取事件,然后开始运行代码来处理该事件。该代码自行运行,直到将控制权返回给系统,然后系统将从事件队列中提取下一个事件并运行该代码,直到将控制权返回给系统。
因此,由两个同时执行的线程引起的典型竞争条件在 Javascript 中不会发生。
这包括所有形式的 Javascript 事件,包括:用户事件(鼠标、按键等)、计时器事件、网络事件(ajax 回调)等...
在 Javascript 中真正可以进行多线程处理的唯一地方是使用HTML5 网络工作者 http://blogs.msdn.com/b/davrous/archive/2011/07/15/introduction-to-the-html5-web-workers-the-javascript-multithreading-approach.aspx or 工作线程 https://nodejs.org/api/worker_threads.html(在node.js中),但它们与常规javascript非常隔离(它们只能通过消息传递与常规javascript通信)并且根本无法操作DOM并且必须有自己的脚本和命名空间等......
虽然从技术上讲我不会将其称为竞争条件,但 Javascript 中存在一些情况,因为它的一些异步操作可能会同时执行两个或多个异步操作(实际上并未执行 Javascript,但底层异步操作是同时运行本机代码),并且每个操作相对于其他操作何时完成可能是不可预测的。这会造成时间的不确定性(如果操作的相对时间对您的代码很重要),从而导致您必须手动编码。您可能需要对操作进行排序,以便一个操作运行,然后您实际上等待它完成,然后再开始下一个操作。或者,您可以启动所有三个操作,然后使用一些代码来收集所有三个结果,当它们全部准备好时,您的代码就会继续。
在现代 Javascript 中,promise 通常用于管理这些类型的异步操作。
因此,如果您有三个异步操作,每个操作都返回一个承诺(例如从数据库读取、从另一个服务器获取请求等),您可以像这样手动排序:
a().then(b).then(c).then(result => {
// result here
}).catch(err => {
// error here
});
或者,如果您希望它们全部一起运行(全部同时飞行)并且只知道它们何时完成,您可以这样做:
Promise.all([a(), b(), c()])..then(results => {
// results here
}).catch(err => {
// error here
});
虽然我不会将这些竞争条件称为竞争条件,但它们属于设计代码以控制不确定排序的同一个通用系列。
在浏览器中的某些情况下可能会出现一种特殊情况。这并不是真正的竞争条件,但如果您使用大量具有临时状态的全局变量,则可能需要注意。当您自己的代码导致另一个事件发生时,浏览器有时会同步调用该事件处理程序,而不是等到当前执行线程完成。一个例子是:
- click
- 单击事件处理程序将焦点更改为另一个字段
- 另一个字段有一个 onfocus 事件处理程序
- 浏览器立即调用 onfocus 事件处理程序
- onfocus 事件处理程序运行
- 单击事件处理程序的其余部分运行(在 .focus() 调用之后)
从技术上讲,这不是竞争条件,因为 onfocus 事件处理程序何时执行(在.focus()
称呼)。但是,它可能会造成一种情况,即一个事件处理程序运行,而另一个事件处理程序正在执行。