转载:https://juejin.cn/post/6844903556084924423#comment
一、同步与异步
1. 同步
如果在函数返回的时候,调用者就能够得到预期结果(即拿到了预期的返回值或者看到了预期的效果),那么这个函数就是同步的。
2. 异步
如果在函数返回的时候,调用者还不能够得到预期结果,而是需要在将来通过一定的手段得到,那么这个函数就是异步的。
二、单线程与多线程
javascript是单线程操作,那那些耗时操作到底交给谁了呢?
javascript其实就是一门语言,说是单线程还是多线程得结合具体运行环境。javascript的运行通常是在浏览器中进行的,具体由JS引擎解析和运行。
1、浏览器
目前流行的浏览器有:Chrome、FireFox、IE、Opera、Safari。浏览器的内核是多线程的。
一个浏览器通常有几个常驻线程:
- 渲染引擎线程:顾名思义,该线程负责页面的渲染
- JS引擎线程:负责JS的解析和执行
- 定时触发器线程:处理定时事件,比如setTimeout、setInterval
- 事件触发线程:处理DOM事件
- 异步http请求线程:处理http请求
需要注意的是,渲染线程和JS引擎线程是不能同时进行的。渲染线程在执行任务的时候,JS引擎线程会被挂起。因为JS可以操作DOM,若在渲染中JS处理了DOM,浏览器可能就不知所措了。
2. JS引擎
通常讲到浏览器的时候,我们会说到两个引擎:渲染引擎和JS引擎。渲染引擎就是如何渲染页面的,Chrome、Safari、Opera用的是Webkit引擎,IE用的是Trident引擎,FireFox用的是Gecko引擎。不同的引擎对同一个样式的实现效果不一致,这就导致了经常被人诟病的浏览器兼容性问题。
JS引擎可以说是JS虚拟机,负责JS代码的解析和执行。通常包括以前几个步骤:
- 词法分析:将源代码分解为有意义的分词
- 语法分析:用语法分析器将分词解析成语法树
- 代码生成:声场机器能运行的代码
- 代码执行
不同浏览器的JS引擎也各不相同,Chrome用的是V8,FireFox用的是SpiderMonkey,Safari用的是JavaScriptCore,IE用的是Chakra。
之所以说JavaScript是单线程,就是因为浏览器在运行时只开启了一个JS引擎线程来解析和执行JS。那为什么只有一个引擎呢?如果同时有两个线程去操作DOM,浏览器是不是又要不知所措了。
所以,虽然JavaScript是单线程的,可是浏览器内部不是单线程的。一些I/O操作、定时器的计时和事件监听(click,keydown)等都是由浏览器提供的其他线程来完成的。
三、消息队列和事件循环
通过以上了解,可以知道其实JavaScript也是通过JS引擎线程和浏览器中其他线程交互协作实现异步。但是回调函数具体何时加入到JS引擎线程中执行?执行顺序是怎么样的?
这一切的解释继续用继续了解消息队列和事件循环。
如上图所示,左边的栈存储的是同步任务,就是那些能立即执行、不耗时的任务,如变量和函数的初始化、事件的绑定等等那些不需要回调函数的操作都可归为这一类。
右边的堆用来存储声明的变量、对象。下面的队列就是消息队列,一旦某个异步任务有了响应就会被推入队列中。如用户的点击事件、浏览器收到服务的响应和setTimeout中待执行的事件,每个异步都和回调函数相关联。
JS引擎线程用来执行栈中的同步任务,当所有同步任务执行完毕后,栈被清空,然后读取消息队列中的一个待处理任务,并把相关回调函数压入栈中,单线程开始执行新的同步任务。
JS引擎线程从消息队列中读取任务是不断循环的,每次栈被清空后,都会在消息队列中的读取新的任务,如果没有新的任务,就会等待,直到有新的任务,这就叫事件循环。
上图以AJAX异步请求为例,发起异步任务后,由AJAx线程执行耗时的异步操作,而JS引擎线程继续执行堆中的其他同步任务,直到堆中的所有异步任务执行完毕。然后,从消息队列中依次按照顺序取出消息作为一个同步任务在JS引擎线程中执行,那么AJAX的回调函数就会在某一刻被执行调用执行。
四、实例
setTimeout(function(){
for(var i = 0; i < 100000000; i++){}
console.log('timer a');
}, 0)
for(var j = 0; j < 5; j++){
console.log(j);
}
setTimeout(function(){
console.log('timer b');
}, 0)
function waitFiveSeconds(){
var now = (new Date()).getTime();
while(((new Date()).getTime() - now) < 5000){}
console.log('finished waiting');
}
document.addEventListener('click', function(){
console.log('click');
})
console.log('click begin');
waitFiveSeconds();