JS 中的 Event Loop 是什么?

​大家好,我是前端西瓜哥,今天来认识一下什么是 Event Loop。

Event Loop,简单翻译就是 事件循环,是 JS 语言下实现运行时的一个机制。

JS 的异步并不像其他语言(比如 Java)的异步那样可以实现真正的并发执行,本身其实是个单线程。

JS 是维护了一个 任务队列,每当要执行一些异步任务,比如定时器或者是点击按钮触发的事件响应函数。它们不会立即执行,而是放到这个队列里,等待已经在排队的其他任务先执行完,才轮到它们。

队列是一个操作受限的有序列表,表现为为先进入的元素必须先出去,亿华云计算即 “先进先出”,很像排队的感觉。

不过也有一些特殊的队列,比如优先级队列,它是优先级高的元素先出队。

之所以叫 Event Loop,因为它的逻辑可以描述为下面代码:​

while (queue.waitForMessage()) {

queue.processNextMessage();

}

当一个任务被完成后,队列会变成等待下一个任务状态,然后处理下一个任务,如此循环往复。

因为 JS 的代码执行本身是一个单线程,为了不让执行阻塞,JS 会把网络请求操作、渲染浏览器页面等操作,交给其他的线程,等待其他线程处理好把结果返回给 JS。

所以 JS 不合适 CPU 密集型,更适合 IO 密集型的场景。因为它只有一个线程,如果计算耗时太长,就会阻塞其他要执行的任务,导致卡顿,甚至崩溃。高防服务器

setTimeout 定时器并不准

setTimeout 在特定时间后要执行的函数,并不会立即执行,而是会先放到任务队列中,先等待前面的任务同步执行完成了,才能执行我们这个。

下面看一个例子,因为第一个 setTimout 有一个非常耗时的同步任务,导致下一个 setTimeout 的执行阻塞,比前面一个 setTimeout 执行要慢半秒。

const start = new Date().getTime();

setTimeout(() => {

console.log(1:, new Date().getTime() - start);

let num = 0;

for (let i = 0; i < 999999999; i++) {

num = i;

}

}, 1000);

setTimeout(() => {

console.log(2:, new Date().getTime() - start);

}, 1000);

/

**

* 输出结果:

* 1: 1001

* 2: 1505

*/

定时器的时间,指的是能执行的最早时间,但不能保证一定能在这个时间点立即执行。

宏任务和微任务

任务队列并不是严格意义上的先进先出的正常队列,是可以调整执行顺序的。

我们将要执行的任务分为宏任务和微任务,其中宏任务是正常的先进先出,站群服务器而微任务则是可以插队,优先于宏任务先执行。宏任务必须在所有微任务执行后才能执行。

当我们给任务队列添加一个微任务时,它会跑到任务队列宏任务前。多个微任务入队时,会保持它们的相对顺序。

宏任务有:

script,即 HTML 嵌入的脚本。setTimeout / setInterval 定时器。setImmediate,这是 nodejs 特有的 API。requestAnimationFrame,会在页面重绘前执行。I/O 操作,比如网络请求完成的回调函数执行任务、还比如点击按钮要执行的回调等。这些操作其实是其他的线程完成后触发的,暂且归纳为 I/O 操作。

微任务有:

Promise 从 pending 状态转换为其他状态时,触发 then/catch/finaly 中的函数,比如 Promise.resolve().then(fn)。这是最常见的微任务。MutationObserver,用于监听 DOM 的变化。process.nextTick,nodejs 特有的 API。

任务队列,理论上一个就够了,但也可以是多个队列的组合,没有强行要求。

多个任务队列的实现可以更好地实现优先级的控制,比如对于定时器任务,理论上应该是在多个宏任务中最先执行比较好。浏览器没考虑这种情况,但 nodejs 给宏任务中也设置优先级,会让定时器任务最先执行。

Event Loop 还是挺复杂的,标准文档也比较长,我也没怎么看,感兴趣可以看看。

https://html.spec.whatwg.org/multipage/webappapis.html#event-loops。

一道经典异步题

async function async1() {

console.log("async1 start");

await async2();

console.log("async1 end");

}

async function async2() {

console.log("async2");

}

console.log("script start");

setTimeout(function() {

console.log("setTimeout");

}, 0);

async1();

new Promise(function(resolve) {

console.log("promise1");

resolve();

}).then(function() {

console.log("promise2");

});

console.log(script end)

解题思路为:

找到同步代码。同步代码有:普通同步代码、new Promise(fn) 执行传入的回调函数、async 执行时遇到 await 的前面部分(包括 await 的右侧函数执行也是同步的,这里是易错点)。看看任务队列中有哪些微任务和宏任务,记住微任务全执行完了才会执行宏任务。执行任务,任务里面的异步任务又按顺序进入到任务队列。

结果是:

// 同步代码

script start

async1 start

async2

promise1

script end

// 微任务

async1 end

promise2

// 宏任务

setTimeout

结尾

JS 运行机制是单线程,当有多个异步任务要同时执行,并不能并发执行,必须让优先级高的任务执行完才能执行后面的。如果正在执行的任务比较耗时,会导致后面的任务被阻塞。

Event Loop 的机制中,最基本的一条就是:微任务比宏任务先执行。

人工智能
上一篇:以小狐鲜的水果(优质水果的首选品牌,让您尽享天然之美)
下一篇:创建的热点手机也是可以连接的,这里将分享两个方法一,kde-nm-connection-editor工具开启热点在ubuntu软件中心搜索kde nm connection注意搜索的关键词,不要出现横杠“-”,结果中出现kde-nm-connection-editor安装即可配置终端输入kde-nm-connection-editor跳出连接编辑器的图形界面在图形界面点击 添加-->Wireless(shared)弹出New Connection (New 802-11-wireless connection)图形界面Connection name:更改为mywifi(默认为New 802-11-wireless connection)SSID:更改为mywifi(默认为my_shared_connection)模式:选择Access PointRestrict to device:选择你的无线网卡点击无线安全选项卡安全:选择WPA & WPA2 Personal密码:填入你要设置的密码(至少8位)(这里就不上图了,相信大家看着提示可以设置成功!)连接刚才wifi,只有这样才能建立热点我们点击最上方网络管理的图标,选择 连接到隐藏的Wi-Fi网络Wifi适配器:选择我们设置了的无线网络连接:选择我们刚刚建立的连接名称(mywifi)之后网络名称(SSID),Wi-Fi安全性,密码会自动填写并变成灰色点击确定(这一步可能会出现解锁钥匙环的提示,输入你的电脑登陆密码就能解锁了)现在wifi热点就建立好了,试试你的手机能不能连上吧二,修改配置文件点右上角图标NetworkManager-->编辑连接在弹出的网络连接窗口中点添加在弹出的选择连接类型窗口中选择Wi-Fi,点击新建连接名称:填wifitestconfSSID:填wifitestconf模式:选择架构(注意不要选Ad-hoc)设备MAC地址:选择你要建立AP的网卡切换到Wi-Fi安全性选项卡安全:选WPA及WAP2个人密码:自己设置切换到IPv4设置选项卡方法:选与其它计算机共享点击保存更改配置文件然后我们来到刚才这个连接对应的配置文件终端执行命令ls -l /etc/NetworkManager/system-connections/-rw------- 1 root root 314  9月 19 15:34 360ap -rw------- 1 root root 215  9月 18 09:40 i-LiaoNing -rw------- 1 root root 316  9月 19 18:05 mywifi-rw-r--r-- 1 root root 318  9月 19 19:33 mywifi2-rw------- 1 root root 295  9月 19 19:46 Wi-Fitest -rw------- 1 root root 307  9月 19 19:45 Wi-Fitest~ -rw------- 1 root root 313  9月 19 20:07 wifitestconf-rw------- 1 root root 365  9月 19 19:39 Wi-Fi 连接 1-rw------- 1 root root 337  9月 19 19:37 Wi-Fi 连接 1~-rw------- 1 root root 252  9月 19 19:28 自动以太网可以看到第7个就是我们刚才的配置文件,我们打开它命令输入 sudo gedit /etc/NetworkManager/system-connections/wifitestconf在[802-11-wireless]下面找到mode=infrastructure把它改为mode=ap这里有两个需要注意的问题(1)假如保存之后在退出时仍然提示在关闭前将更改保存到文档“XXX”吗?说明此时文件已经被锁定:强制保存会导致关联的配置文件丢失解决方法如下:断开这个网卡的连接,继续保存。另一个解决方法,前面的编辑连接界面不关闭,完成后续更改,但是大多数人都会关闭的吧。假如保存之后从网络连接界面和连接隐藏的Wi-Fi网络的界面找不到这个连接的名字,说明关联的配置文件丢失解决方法。:编辑配置文件的时候请务必确保网络连接界面是打开的3.连接刚才wifi,只有这样才能建立热点我们点击最上方网络管理的图标,选择 连接到隐藏的Wi-Fi网络Wifi适配器:选择我们设置了的无线网络连接:选择我们刚刚建立的连接名称(wifitestconf)之后网络名称(SSID),Wi-Fi安全性,密码会自动填写并变成灰色点击确定,试试你的手机能不能连上吧。