本文主要根据网上资源总结而来,若有不对,请斧正。javascript
参考html
synchronous:同步任务java
asynchronous:异步任务node
task queue/callback queue:任务队列web
execution context stack:执行栈canvas
heap:堆后端
stack:栈api
macro-task:宏任务promise
micro-task:微任务浏览器
在进入正式的主题以前咱们来探究探究这个历史问题,嗯,仍是有点价值的哈,由于JS若不是单线程的也就不会衍生出后来的异步任务以及事件环了嘛。
js最开始时是跑在浏览器端的,主要的做用是与用户互动(接收用户输入并给出自定义的响应)以及操做DOM(各类特效,控制输入输出)。
So网上有一种说法,举了个栗子说,两个线程,有一个线程在添加一个dom元素 a
,还有一个线程在删除一个dom元素a
,那么浏览器就须要决策该听谁的,这样的话就增长了语言设计的复杂性。
嗯。。。若是这例子以及说法还不能令你信服,我以为你能够再想一想javascript当初开发时的情景
其实我觉的这也是很重要的一个缘由之一,嗯,这是我比较能代入的,那就是
我大javascript当初10天就给弄出来了。。。so,你还想咋地?
h5有一个新api,它叫webworker
,利用它,能帮助咱们建立子线程。
//index.html
let worker = new Worker('./worker.js');
//把内容发给 工做线程,工做线程能够把结果丢回来
worker.postMessage('向你雇佣的工人发送指令');
worker.onmessage = function(e){
console.log(e.data); //数据在data属性下
}
//worker.js
window.onmessage = function(e){
console.log(e);
this.postMessage('你的工人告诉你他收到命令开始干活了!');
};
复制代码
注意: 他和js主线程不是平级的,主线程能够控制webworker,webworker不能操做dom,不能获取document以及window。
单线程虽然简单,不容易出错,可是有一个问题,这货一直是一我的在干活,倘若涉及到读取写入这种I/O行为,那么不只CPU资源是妥妥的浪费(webworker的出现其实多是为了尝试解决这部分问题),咱们后面的代码还要等待它读写完毕才能执行,这就是所谓的站着那撒不拉那撒了- -!
So,为了解决这个问题,Javascript将任务的执行方式分为两种:同步/synchronous
和 异步/asynchronous
,遇到像上面那种须要长时间等待的I/O操做,咱们就将它做为一个异步任务分发出去,等待它执行完毕后再通知咱们。
咱们说遇到异步任务会分发出去,那么分发分发究竟分发给了谁呢?
在浏览器内核中,除了JS线程
和用户界面后端/UI Backend线程
,还有一些其它的线程,好比说浏览器事件触发线程
、定时器触发线程
、异步HTTP请求线程
,而异步任务就是分发给这些线程来处理的。(若是有不大了解这方面的同窗能够查看本文的最后部分)
当这些异步任务在对应的线程中处理完成获得结果后,这些任务的回调就会被加入到callback queue
队列当中。另外一方面,JS主线程的执行栈中一旦全部同步代码执行完毕后就会开始不停的检测callback queue
,只要队列中存在任务,就会被提取到执行栈中执行。
下图是网上流传很广的一张示意图
其中JS主线程从callback queue
中不断读取事件到执行栈中执行的这种循环的过程又被称之为EventLoop
,即事件循环。
node中的事件环和浏览器中的是不同的,node的事件环分为六个阶段,每一个阶段都一有一个callbcak queue
,只有当一个阶段的queue清理干净后才会进入到下一个阶段。
异步任务主要分为两种,
一种称之为macro task
,即宏任务,像setTimeout、I/O读写、AJAX这类耗时灰常长的。
另一种则称之为micro task
,即微任务,例如nextTick
、promise
。(即便定时器的delayt时间设置为0,也是宏任务,会在本轮的微任务执行完毕后再执行)
宏任务和微任务的区别在于,微任务是会被加入本轮循环的
,而宏任务都是在次轮循环
中被执行。
本轮循环是指什么呢?JS主线程会从任务队列中提取任务到执行栈中执行,每一次执行均可能会再产生一个新的任务,对于这些任务来讲此次执行到下一次从任务队列中提取新的任务到执行栈以前就是这些新生任务的本轮。
[danger] 注意: 在node中微任务的触发时机是在进入事件环以前以及当状态转换的时才会触发,这意味着若是一个状态中的callbcak queue中的cb尚未所有清空完毕,那么微任务并不会像浏览器中同样加入本轮后在一个回调执行完毕后就会当即执行,而会等待queue清空后才执行。
1. setTimeout,setImmediate谁先谁后?
setTimeout(function(){
console.log('Timeout');
})
setImmediate(function(){
console.log('Immediate');
})
复制代码
网上有有一种说法,由于setTimeout虽然在node事件循环中的第一个阶段,但setTimeout即便将delay
设置为0也会有1ms+(node作不到那么精确),那么当第一次事件循环时,setTimeout可能尚未准备好,就将会让setImmediate先执行。
但,真会发生这种状况吗?
嗯。。。我没事点了几十下,全是setTimeout。
另外还有以下一种状况,必定是setImmediate会先走的
fs.readFile('./1.txt',function(){
console.log('fs');
setTimeout(function(){
console.log('timeout');
});
setImmediate(function(){
console.log('setImmediate');
});
})
复制代码
由于当fs的I/O回调执行执行时是处于事件循环中的poll
阶段,而下一个阶段为check
是存放setImmediate
的阶段。
2. nextTick和promise.then谁快?
nextTick快,就是这么设计的
3. nextTick和其它的定时器嵌套
setImmediate(function(){
console.log(1);
process.nextTick(function(){
console.log(4);
})
})
process.nextTick(function(){
console.log(2);
setImmediate(function(){
console.log(3);
})
})
<<<
2134
复制代码
缘由在于nextTick在node中的执行实际和浏览器中不彻底同样,虽然它们在第一次进入事件环时都会先执行,但若是后续还有nextTick加入,node中只会在阶段转换时才会去执行,而浏览器中则是一有nextTick加入就会当即执行。
形成这样区别的缘由在于,node中的事件环是有6种状态的,每种状态都是一个callbcak queue
,只有当一个状态的callback queue
中存放的回调都清空后才会执行nextTick。
4. 定时器指定的回调函数必定会在指定的时间内执行吗?
不必定,先不说node中事件环六中状态之间转化时的猫腻,光是浏览器中的事件环也可能由于本轮循环的执行时间过长,长得比定时器指定的事件还长从而致使定时器的回调触发被延误。
嗯,先上图。。也是很火的一张
浏览器是多进程
的,每一个进程管理着浏览器不一样的部分,主要分为如下几种
其中渲染引擎内部有三个线程
是咱们注重须要关注的
其中js线程和ui线程是互斥的,
当js执行的时候可能ui还在渲染,那么这时ui线程会把更改放到队列中 当js线程空闲下来 ui线程再继续渲染
除此以外还有一些其它的线程,这也是咱们分发异步任务时用到的线程
--- End ---