几乎在每一本JS相关的书籍中,都会说JS是单线程的,JS是经过事件队列(Event Loop)的方式来实现异步回调的。 对不少初学JS的人来讲,根本搞不清楚单线程的JS为何拥有异步的能力,因此,我试图从进程、线程的角度来解释这个问题。
javascript
它就像一座工厂,时刻在运行。html
从上文咱们已经简单了解了CPU、进程、线程,简单汇总一下前端
咱们已经知道了CPU、进程、线程之间的关系,对于计算机来讲,每个应用程序都是一个进程, 而每个应用程序都会分别有不少的功能模块,这些功能模块其实是经过子进程来实现的。 对于这种子进程的扩展方式,咱们能够称这个应用程序是多进程的。
而对于浏览器来讲,浏览器就是多进程的,我在Chrome浏览器中打开了多个tab,而后打开mac中的活动监视器: java
答案:固然是渲染进程啦,也就是咱们常说的浏览器内核ajax
从前文咱们得知,进程和线程是一对多的关系,也就是说一个进程包含了多条线程。
而对于渲染进程来讲,它固然也是多线程的了,接下来咱们来看一下渲染进程包含哪些线程。算法
首先是历史缘由,在建立 javascript 这门语言时,多进程多线程的架构并不流行,硬件支持并很差。
其次是由于多线程的复杂性,多线程操做须要加锁,编码的复杂性会增高。
并且,若是同时操做 DOM ,在多线程不加锁的状况下,最终会致使 DOM 渲染的结果不可预期。segmentfault
这是因为 JS 是能够操做 DOM 的,若是同时修改元素属性并同时渲染界面(即 JS线程和UI线程同时运行), 那么渲染线程先后得到的元素就可能不一致了。
所以,为了防止渲染出现不可预期的结果,浏览器设定 GUI渲染线程和JS引擎线程为互斥关系, 当JS引擎线程执行时GUI渲染线程会被挂起,GUI更新则会被保存在一个队列中等待JS引擎线程空闲时当即被执行。promise
到了这里,终于要进入咱们的主题,什么是 Event Loop
先理解一些概念:
浏览器
let timerCallback = function() {
console.log('wait one second');
};
let httpCallback = function() {
console.log('get server data success');
}
同步任务
console.log('hello');
同步任务
通知定时器线程 1s 后将 timerCallback 交由事件触发线程处理
1s 后事件触发线程将 timerCallback 加入到事件队列中
setTimeout(timerCallback,1000);
同步任务
通知异步http请求线程发送网络请求,请求成功后将 httpCallback 交由事件触发线程处理
请求成功后事件触发线程将 httpCallback 加入到事件队列中
$.get('www.xxxx.com',httpCallback);
同步任务
console.log('world');
全部同步任务执行完后
询问事件触发线程在事件事件队列中是否有须要执行的回调函数
若是没有,一直询问,直到有为止
若是有,将回调事件加入执行栈中,开始执行回调代码
复制代码
总结一下:bash
当咱们基本了解了什么是执行栈,什么是事件队列以后,咱们深刻了解一下事件循环中宏任务、微任务
咱们能够将每次执行栈执行的代码当作是一个宏任务(包括每次从事件队列中获取一个事件回调并放到执行栈中执行), 每个宏任务会从头至尾执行完毕,不会执行其余。
咱们前文提到过JS引擎线程和GUI渲染线程是互斥的关系,浏览器为了可以使宏任务和DOM任务有序的进行,会在一个宏任务执行结果后,在下一个宏任务执行前,GUI渲染线程开始工做,对页面进行渲染。
// 宏任务-->渲染-->宏任务-->渲染-->渲染...
复制代码
主代码块,setTimeout,setInterval等,都属于宏任务
第一个例子
document.body.style = 'background:black';
document.body.style = 'background:red';
document.body.style = 'background:blue';
document.body.style = 'background:grey';
复制代码
咱们能够将这段代码放到浏览器的控制台执行如下,看一下效果:
document.body.style = 'background:blue';
setTimeout(function(){
document.body.style = 'background:black'
},0)
复制代码
执行一下,再看效果:
咱们已经知道宏任务结束后,会执行渲染,而后执行下一个宏任务, 而微任务能够理解成在当前宏任务执行后当即执行的任务。
也就是说,当宏任务执行完,会在渲染前,将执行期间所产生的全部微任务都执行完。
Promise,process.nextTick等,属于微任务。
第一个例子
document.body.style = 'background:blue'
console.log(1);
Promise.resolve().then(()=>{
console.log(2);
document.body.style = 'background:black'
});
console.log(3);
复制代码
执行一下,再看效果:
页面的背景色直接变成黑色,没有通过蓝色的阶段,是由于,咱们在宏任务中将背景设置为蓝色,但在进行渲染前执行了微任务, 在微任务中将背景变成了黑色,而后才执行的渲染
第二个例子
setTimeout(() => {
console.log(1)
Promise.resolve(3).then(data => console.log(data))
}, 0)
setTimeout(() => {
console.log(2)
}, 0)
// print : 1 3 2
复制代码
上面代码共包含两个 setTimeout ,也就是说除主代码块外,共有两个宏任务, 其中第一个宏任务执行中,输出 1 ,而且建立了微任务队列,因此在下一个宏任务队列执行前, 先执行微任务,在微任务执行中,输出 3 ,微任务执行后,执行下一次宏任务,执行中输出 2
看到这里,不知道对JS的运行机制是否是更加理解了,从头至尾梳理,而不是就某一个碎片化知识应该是会更清晰的吧?
同时,也应该注意到了JS根本就没有想象的那么简单,前端的知识也是无穷无尽,层出不穷的概念、N多易忘的知识点、各式各样的框架、 底层原理方面也是能够无限的往下深挖,而后你就会发现,你知道的太少了。。。
最后,喜欢的话,就请给个赞吧!