You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
然后浏览器设置对网络响应的监听,当它有东西返回给你的时候,它将会把回调函数插入到事件循环队列里然后执行。
那图中的这些 Web API 是什么东西呢?从本质上讲,它们是你无法访问的线程,但是你能够调用它们。它们是浏览器并行启动的一部分。如果你是一个 Node.js 的开发者,这些就是 C++ 的一些 API。
为什么单线程会限制我们?
我们思考了一个问题 当调用栈中的函数调用需要花费我们非常多的时间,会发生什么?
比如,想象一下你的浏览器现在正在运行一个复杂的图像转换的算法。
当调用栈有函数在执行,浏览器就不能做任何事了 —— 它被阻塞了。这意味着浏览器不能渲染页面,不能运行任何其它的代码,它就这样被卡住了。那么问题来了 —— 你的应用不再高效和令人满意了。
你的应用卡住了。
解析事件循环
我们先从一个奇怪的说法谈起 —— 尽管 JavaScript 允许异步的代码(就像是我们刚刚说的 setTimeout) ,但直到 ES6,JavaScript 自身从未有过任何关于异步的直接概念。JavaScript 引擎只会在任意时刻执行一个程序。
那么,谁会告诉 JS 引擎去执行你的程序?事实上,JS 引擎不是单独运行的 —— 它运行在一个宿主环境中,对于大多数开发者来说就是典型的浏览器和 Node.js。实际上,如今,JavaScript 被应用到了从机器人到灯泡的各种设备上。每个设备都代表了一种不同类型的 JS 引擎的宿主环境。
所有的环境都有一个共同点,就是都拥有一个 事件循环 的内置机制,它随着时间的推移每次都去调用 JS 引擎去处理程序中多个块的执行。
这意味着 JS 引擎只是任意的 JS 代码按需执行的环境。是它周围的环境来调度这些事件(JS 代码执行)。
所以,比如当你的 JavaScript 程序发出了一个 Ajax 请求去服务器获取数据,你在一个函数(回调)中写了 “response” 代码,然后 JS 引擎就会告诉宿主环境:
“嘿,我现在要暂停执行了,但是当你完成了这个网络请求,并且获取到数据的时候,请回来调用这个函数。”
然后浏览器设置对网络响应的监听,当它有东西返回给你的时候,它将会把回调函数插入到事件循环队列里然后执行。
那图中的这些 Web API 是什么东西呢?从本质上讲,它们是你无法访问的线程,但是你能够调用它们。它们是浏览器并行启动的一部分。如果你是一个 Node.js 的开发者,这些就是 C++ 的一些 API。
那 事件循环 究竟是什么?
事件循环有一个简单的任务 —— 去监控调用栈和回调队列。如果调用栈是空的,它就会取出队列中的第一个事件,然后将它压入到调用栈中,然后运行它。
这样的迭代在事件循环中被称作一个 tick。每一个事件就是一个回调函数。
让我们执行一下这段代码,看看会发生什么:
1. 状态是干净的。浏览器 console 是干净的,并且调用栈是空的。
2.
console.log('Hi')
被添加到了调用栈里。3.
console.log('Hi')
被执行。4.
console.log('Hi')
被移出调用栈。5.
setTimeout(function cb1() { ... })
被添加到调用栈。6.
setTimeout(function cb1() { ... })
执行。浏览器创建了一个定时器(Web API 的一部分),并且开始倒计时。7.
setTimeout(function cb1() { ... })
本身执行完了,然后被移出调用栈。8.
console.log('Bye')
被添加到调用栈。9.
console.log('Bye')
执行。10.
console.log('Bye')
被移出调用栈。11. 在至少 5000ms 过后,定时器完成,然后将回调
cb1
压入到回调队列。12. 事件循环从回调队列取走
cb1
,然后把它压入调用栈。13.
cb1
被执行,然后把console.log('cb1')
压入调用栈。14.
console.log('cb1')
被执行。15.
console.log('cb1')
被移出调用栈。16.
cb1
被移出调用栈。回顾一下:
setTimeout(…) 是如何工作的
需要重点注意的是
setTimeout(…)
不会自动的把你的回调放到事件循环队列中。它设置了一个定时器。当定时器过期了,宿主环境会将你的回调放到事件循环队列中,以便在以后的循环中取走执行它。看看下面的代码:这并不意味着
myCallback
将会在 1,000ms 之后执行,而是,在 1,000ms 之后将被添加到事件队列。然而,这个队列中可能会拥有一些早一点添加进来的事件 —— 你的回调将会等待被执行。有很多文章或教程在介绍异步代码的时候都会从
setTimeout(callback, 0)
开始。好了,现在你知道了事件循环做了什么以及setTimeout
是怎么运行的:以第二个参数是 0 的方式调用setTimeout
就是推迟到调用栈为空才执行回调。来看看下面的代码:
尽管等待的事件设置成 0 了,但是浏览器
console
的结果将会是下面这样:The text was updated successfully, but these errors were encountered: