最近看了不少关于JS运行机制的文章,每篇都获益匪浅,但各有不一样,因此在这里对这几篇文章里说的很精辟的地方作一个总结,参考文章连接见最后。本文博客地址
进程
是cpu资源分配的最小单位(是能拥有资源和独立运行的最小单位)线程
是cpu调度的最小单位(线程是创建在进程的基础上的一次程序运行单位,一个进程中能够有多个线程)进程
之间也能够通讯,不过代价较大单线程
与多线程
,都是指在一个进程
内的单和多详情看我上篇总结浏览器执行机制的文章- 深刻前端-完全搞懂浏览器运行机制
既然js是单线程,那么问题来了,某一些很是耗时间的任务就会致使阻塞,难道必须等前面的任务一步一步执行玩吗?
好比我再排队就餐,前面很长的队列,我一直在那里等岂不是很傻逼,说以就会有排号系统产生,咱们订餐后给咱们一个号码,叫到号码直接去就好了,没交咱们以前咱们能够去干其余的事情。
所以聪明的程序员将任务分为两类:javascript
能够理解是每次执行栈执行的代码就是一个宏任务(包括每次从事件队列中获取一个事件回调并放到执行栈中执行)
每个task会从头至尾将这个任务执行完毕,不会执行其它
浏览器为了可以使得JS内部task与DOM任务可以有序的执行,会在一个task执行结束后,在下一个 task 执行开始前,对页面进行从新渲染
(task->渲染->task->...)html
也就是说,在当前task任务后,下一个task以前,在渲染以前
因此它的响应速度相比setTimeout(setTimeout是task)会更快,由于无需等渲染
也就是说,在某一个macrotask执行完后,就会将在它执行期间产生的全部microtask都执行完毕(在渲染前)前端
主线程运行的时候,产生堆(heap)和栈(stack),栈中的代码调用各类外部API,它们在"任务队列"中加入各类事件(click,load,done)。只要栈中的代码执行完毕,主线程就会去读取"任务队列",依次执行那些事件所对应的回调函数。 java
那怎么知道主线程执行栈为执行完毕?js引擎存在monitoring process进程,会持续不断的检查主线程执行栈是否为空,一旦为空,就会去Event Queue那里检查是否有等待被调用的函数。程序员
第一轮事件循环:
主线程执行js整段代码(宏任务),将ajax、setTimeout、promise等回调函数注册到Event Queue,并区分宏任务和微任务。
主线程提取并执行Event Queue 中的ajax、promise等全部微任务,并注册微任务中的异步任务到Event Queue。
第二轮事件循环:
主线程提取Event Queue 中的第一个宏任务(一般是setTimeout)。
主线程执行setTimeout宏任务,并注册setTimeout代码中的异步任务到Event Queue(若是有)。
执行Event Queue中的全部微任务,并注册微任务中的异步任务到Event Queue(若是有)。
相似的循环:宏任务每执行完一个,就清空一次事件队列中的微任务。ajax
注意:事件队列中分“宏任务队列”和“微任务队列”,每执行一次任务均可能注册新的宏任务或微任务到相应的任务队列中,只要遵循“每执行一个宏任务,就会清空一次事件队列中的全部微任务”这一循环规则,就不会弄乱。promise
let data = []; $.ajax({ url:www.javascript.com, data:data, success:() => { console.log('发送成功!'); } }) console.log('代码执行结束');
1.执行整个代码,遇到ajax异步操做
2.ajax进入Event Table,注册回调函数success。
3.执行console.log('代码执行结束')。
4.执行ajax异步操做
5.ajax事件完成,回调函数success进入Event Queue。
5.主线程从Event Queue读取回调函数success并执行。浏览器
new Promise((r) => { r(); }) .then(() => console.log(1)) .then(() => console.log(2)) .then(() => console.log(3)) new Promise((r) => { r(); }) .then(() => console.log(4)) .then(() => console.log(5)) .then(() => console.log(6))
执行的结果是 1 4 2 5 3 6数据结构
then()
链式调用,并非连续的建立了多个微任务并推入微任务队列,由于then()
的返回值必然是一个 Promise,然后续的then()
是上一步then()
返回的 Promise 的回调resolve()
,将 Promise 的状态改变为<resolved>: undefined
, 而后 then 中传入的回调函数console.log('1')
做为一个微任务被推入微任务队列then()
中传入的回调函数console.log('2')
此时尚未被推入微任务队列,只有上一个then()
中的console.log('1')
执行完毕后,console.log('2')
才会被推入微任务队列setTimeout(function(){ console.log('定时器开始啦') }); new Promise(function(resolve){ console.log('立刻执行for循环啦'); for(var i = 0; i < 10000; i++){ i == 99 && resolve(); } }).then(function(){ console.log('执行then函数啦') }); console.log('代码执行结束');
1.整段代码做为宏任务执行,遇到setTimeout宏任务分配到宏任务Event Queue中
2.遇到promise内部为同步方法直接执行-“立刻执行for循环啦”
3.注册then回调到Eventqueen
4.主代码宏任务执行完毕-“代码执行结束”
5.主代码宏任务结束被monitoring process进程监听到,主任务执行Event Queue的微任务
6.微任务执行完毕-“执行then函数啦”
7.执行宏任务console.log('定时器开始啦')多线程
// 1 2 6 4 3 5 //console.log(3)实际上是在async2函数返回的Promise的then语句中执行的 async function async1() { console.log(1); const result = await async2(); console.log(3); } async function async2() { console.log(2); } //console.log(async2()) Promise.resolve().then(() => { console.log(4); }); setTimeout(() => { console.log(5); }); async1(); console.log(6);
console.log('1'); // 1 6 7 2 4 5 9 10 11 8 3 // 记做 set1 setTimeout(function () { console.log('2'); // set4 setTimeout(function() { console.log('3'); }); // pro2 new Promise(function (resolve) { console.log('4'); resolve(); }).then(function () { console.log('5') }) }) // 记做 pro1 new Promise(function (resolve) { console.log('6'); resolve(); }).then(function () { console.log('7'); // set3 setTimeout(function() { console.log('8'); }); }) // 记做 set2 setTimeout(function () { console.log('9'); // 记做 pro3 new Promise(function (resolve) { console.log('10'); resolve(); }).then(function () { console.log('11'); }) })
1.总体script做为第一个宏任务进入主线程,遇到console.log,输出1。
2.遇到set1,其回调函数被分发到宏任务Event Queue中。
3.遇到pro1,new Promise直接执行,输出6。then被分发到微任务Event Queue中。
4.遇到了set2,其回调函数被分发到宏任务Event Queue中。
1.主线程执行队列中第一个宏任务set1,输出2;代码中遇到了set4,注册回调;又遇到了pro2,new promise()直接执行输出4,并注册回调;
2.set1宏任务执行完毕,开始清空微任务,主线程执行微任务pro2,输出5。
1.主线程执行队列中第一个宏任务set2,输出9;代码中遇到了pro3,new promise()直接输出10,并注册回调;
2.set2宏任务执行完毕,开始状况微任务,主线程执行微任务pro3,输出11。
相似循环...
因此最后输出结果为一、六、七、二、四、五、九、十、十一、八、3。