代码改变世界

js 执行机制

2018-06-04 16:10  Tony、  阅读(1057)  评论(0编辑  收藏  举报

浏览器中每个一个窗口都是一个单独的进程。这就需要分析浏览器与Javascript解释引擎之间的关系。先给出结论,浏览器本身是多线程的,Javascript解释引擎是单线程的。

先说说浏览器有哪些线程,可以从其功能上分析,浏览器针对Javascript需要支持解释执行、响应事件、渲染UI、下载资源等。可见,浏览器至少需要4个线程,我们着重分析跟Javascript有关的3个线程,解释器线程、交互线程(事件触发线程)、GUI线程。

JS单线程、异步、同步概念

  众所周知,JS是单线程(如果一个线程删DOM,一个线程增DOM,浏览器傻逼了~所以只能单着了),虽然有webworker酱紫的多线程出现,但也是在主线程的控制下。webworker仅仅能进行计算任务,不能操作DOM,所以本质上还是单线程。

  单线程即任务是串行的,后一个任务需要等待前一个任务的执行,这就可能出现长时间的等待。但由于类似ajax网络请求、setTimeout时间延迟、DOM事件的用户交互等,这些任务并不消耗 CPU,是一种空等,资源浪费,因此出现了异步。通过将任务交给相应的异步模块去处理,主线程的效率大大提升,可以并行的去处理其他的操作。当异步处理完成,主线程空闲时,主线程读取相应的callback,进行后续的操作,最大程度的利用CPU。此时出现了同步执行和异步执行的概念,同步执行是主线程按照顺序,串行执行任务;异步执行就是cpu跳过等待,先处理后续的任务(CPU与网络模块、timer等并行进行任务)。由此产生了任务队列与事件循环,来协调主线程与异步模块之间的工作。
 

 

任务队列

js运作在浏览器中,是单线程的,js代码始终在一个线程上执行,此线程被称为js引擎线程, javascript引擎是基于事件驱动单线程执行的,JS引擎一直等待着任务队列中任务的到来,然后加以处理,浏览器无论什么时候都只有一个JS线程在运行JS程序。每一段JS程序都可以看做是一个任务。

因为js引擎是单线程的,单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。如果前一个任务耗时很长,后一个任务就不得不一直等着。如果排队是因为计算量大,CPU忙不过来,倒也算了,但是很多时候CPU是闲着的,因为IO设备(输入输出设备)很慢(比如Ajax操作从网络读取数据),不得不等着结果出来,再往下执行。

JavaScript语言的设计者意识到,这时主线程完全可以不管IO设备,挂起处于等待中的任务,先运行排在后面的任务。等到IO设备返回了结果,再回过头,把挂起的任务继续执行下去。

于是,所有任务可以分成两种,一种是同步任务(synchronous),另一种是异步任务(asynchronous)。同步任务指的是,在JS引擎上排队执行的任务。异步任务指的是,不进入JS引擎、而进入"任务队列"(task queue)的任务。

任务队列又分为 宏任务队列和微任务队列

宏任务队列setTimeout、setInterval、 setImmediate(Node)、requestAnimation(浏览器)、IO、UI rendering

微任务队列Promise.then、Object.observe、MutationObserver

Event Loop(事件循环)

Event Loop主要包含三大块 :一个函数执行站、一个宏任务队列和一个微任务队列。

在JS引擎上的任务,只有前一个任务执行完毕,才能执行后一个任务,当JS引擎中的任务执行完成了,就会去查询异步的任务队列中是否有可以执行的任务,一旦这些异步任务可以执行了,就会将它添加到JS引擎中,以此循环。由于JS引擎从“任务队列”中读取事件的这个过程是不断循环的,所以整个的这种运行机制又称为 Event Loop(事件循环)。

在执行栈上的任务,只有前一个任务执行完毕,才能执行后一个任务,当执行栈上的任务执行完毕之后,检查微任务队列,取出队列中所有事件压入执行栈执行,完毕之后,检查宏任务队列,取出一个事件压入执行栈执行,完毕之后,再次检查微任务队列,取出所有的事件压入执行栈执行,完毕之后,检查宏任务队列,取出一个事件压入执行栈执行,重复该过程。这整个过程是不断循环执行,所以这种运行机制又称未Event Loop(事件循环)。

注意:对于宏任务每次只从宏任务队列种取一个事件压入执行栈执行,微任务每次从微任务队列种取出所有的事件压入执行栈执行。

 

任务队列数据来源

GUI事件触发线程:JavaScript引擎脚本的执行不影响html元素事件的触发,点击、放大、拖拽浏览器或DOM元素,触发线程捕捉对应的回调函数,添加到任务队列末尾。

定时触发线程::当定时时刻达到的时候,定时线程会把对应的函数添加到任务队列末尾。

HTTP异步请求线程:请求线程执行完毕之后 ,会把对应的函数(success、error)添加任务队列末尾。

 最后来个练习题

  

console.log('script start');

setTimeout(function() {
console.log('setTimeout');
Promise.resolve().then(function() {
console.log('setTimeout-Promise1');
return 1
}).then(function() {
console.log('setTimeout-Promise2');
})
}, 0);

Promise.resolve().then(function() {
console.log('promise1');
return 1
}).then(function() {
console.log('promise2');
}).then(function() {
console.log('promise3');
});

setTimeout(function() {
console.log('setTimeout2');

}, 0);

console.log('script end');

 

参考资料:

https://www.cnblogs.com/xhz-dalalala/p/5661955.html

http://www.ruanyifeng.com/blog/2014/10/event-loop.html

http://www.makaidong.com/%E5%8D%9A%E5%AE%A2%E5%9B%AD%E7%89%9B/26456.shtml

https://www.cnblogs.com/tesky0125/p/4619549.html

http://www.cnblogs.com/hity-tt/p/6733062.html

https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/