所有 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
});
虽然我不会将这些竞争条件称为竞争条件,但它们属于设计代码以控制不确定顺序的一般系列。
在浏览器的某些情况下可能会出现一种特殊情况。这并不是真正的竞争条件,但如果您使用大量具有临时状态的全局变量,则可能需要注意。当您自己的代码导致另一个事件发生时,浏览器有时会同步调用该事件处理程序,而不是等到当前执行线程完成。一个例子是:
- 点击
- click 事件处理程序将焦点更改为另一个字段
- 其他字段有 onfocus 的事件处理程序
- 浏览器立即调用 onfocus 事件处理程序
- onfocus 事件处理程序运行
- 单击事件处理程序的其余部分运行(在 .focus() 调用之后)
这在技术上不是竞争条件,因为它 100% 知道 onfocus 事件处理程序何时执行(在 .focus() 调用期间)。但是,它可能会造成一个事件处理程序运行而另一个事件处理程序正在执行的情况。