所有 Javascript 事件处理程序脚本都由一个主事件队列系统处理。这意味着事件处理程序一次运行一个,一个运行直到完成,然后准备运行的下一个开始运行。因此,在 Javascript 中不存在人们在多线程语言中会看到的典型竞争条件,其中该语言的多个线程可以同时运行(或时间片)并为访问变量创建实时冲突。
javascript 中的任何单个执行线程都将在下一个线程开始之前运行完成。这就是 Javascript 的工作原理。从事件队列中提取一个事件,然后代码开始运行以处理该事件。该代码自行运行,直到将控制权返回给系统,然后系统将从事件队列中提取下一个事件并运行该代码,直到将控制权返回给系统。
因此,由两个同时执行的线程引起的典型竞争条件不会在 Javascript 中发生。
这包括所有形式的 Javascript 事件,包括:用户事件(鼠标、按键等)、定时器事件、网络事件(ajax 回调)等...
唯一可以在 Javascript 中实际执行多线程的地方是HTML5 Web Workers或Worker Threads(在 node.js 中),但它们与常规 javascript 非常隔离(它们只能通过消息传递与常规 javascript 通信)并且不能完全操作 DOM 并且必须有自己的脚本和命名空间等...
虽然我不会在技术上将其称为竞争条件,但在 Javascript 中存在一些情况,因为它的某些异步操作可能同时进行两个或多个异步操作(实际上不是执行 Javascript,但底层异步操作是同时运行本机代码),并且每个操作相对于其他操作何时完成可能是不可预测的。这会造成时间的不确定性(如果操作的相对时间对您的代码很重要)会创建一些您必须手动编码的东西。您可能需要对操作进行排序,以便运行一个,然后在开始下一个之前等待它完成。或者,您可以启动所有三个操作,然后编写一些代码来收集所有三个结果,当它们都准备好时,
在现代 Javascript 中,promise 通常用于管理这些类型的异步操作。
因此,如果您有三个异步操作,每个操作都返回一个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
});
虽然我不会将这些竞争条件称为竞争条件,但它们与设计代码以控制不确定排序的通用系列相同。
在浏览器中的某些情况下可能会发生一种特殊情况。这不是真正的竞争条件,但如果您使用大量具有临时状态的全局变量,则可能需要注意。当您自己的代码导致另一个事件发生时,浏览器有时会同步调用该事件处理程序,而不是等到当前执行线程完成。这方面的一个例子是:
- 点击
- 单击事件处理程序将焦点更改为另一个字段
- 其他字段具有用于 onfocus 的事件处理程序
- 浏览器立即调用 onfocus 事件处理程序
- onfocus 事件处理程序运行
- 点击事件处理程序的其余部分运行(在 .focus() 调用之后)
这在技术上不是竞争条件,因为 onfocus 事件处理程序何时执行(在.focus()调用期间)是 100% 知道的。但是,它可能会造成一个事件处理程序运行而另一个正在执行中的情况。