文章来自个人 github 博客,包括技术输出和学习笔记,欢迎star。javascript
先来明白些概念性内容。java
浏览器内核有多种线程在工做。git
GUI 渲染线程:github
JS 引擎线程:web
事件触发线程:segmentfault
定时器触发线程:promise
http 请求线程:浏览器
JavaScript 引擎是单线程,也就是说每次只能执行一项任务,其余任务都得按照顺序排队等待被执行,只有当前的任务执行完成以后才会往下执行下一个任务。数据结构
HTML5 中提出了 Web-Worker API,主要是为了解决页面阻塞问题,可是并无改变 JavaScript 是单线程的本质。了解 Web-Worker。异步
JavaScript 事件循环机制分为浏览器和 Node 事件循环机制,二者的实现技术不同,浏览器 Event Loop 是 HTML 中定义的规范,Node Event Loop 是由 libuv 库实现。这里主要讲的是浏览器部分。
Javascript 有一个 main thread 主线程和 call-stack 调用栈(执行栈),全部的任务都会被放到调用栈等待主线程执行。
JS 调用栈是一种后进先出的数据结构。当函数被调用时,会被添加到栈中的顶部,执行完成以后就从栈顶部移出该函数,直到栈内被清空。
JavaScript 单线程中的任务分为同步任务和异步任务。同步任务会在调用栈中按照顺序排队等待主线程执行,异步任务则会在异步有告终果后将注册的回调函数添加到任务队列(消息队列)中等待主线程空闲的时候,也就是栈内被清空的时候,被读取到栈中等待主线程执行。任务队列是先进先出的数据结构。
调用栈中的同步任务都执行完毕,栈内被清空了,就表明主线程空闲了,这个时候就会去任务队列中按照顺序读取一个任务放入到栈中执行。每次栈内被清空,都会去读取任务队列有没有任务,有就读取执行,一直循环读取-执行的操做,就造成了事件循环。
定时器会开启一条定时器触发线程来触发计时,定时器会在等待了指定的时间后将事件放入到任务队列中等待读取到主线程执行。
定时器指定的延时毫秒数其实并不许确,由于定时器只是在到了指定的时间时将事件放入到任务队列中,必需要等到同步的任务和现有的任务队列中的事件所有执行完成以后,才会去读取定时器的事件到主线程执行,中间可能会存在耗时比较久的任务,那么就不可能保证在指定的时间执行。
宏任务(macro-task)、微任务(micro-task)
除了广义的同步任务和异步任务,JavaScript 单线程中的任务能够细分为宏任务和微任务。
macro-task包括:script(总体代码), setTimeout, setInterval, setImmediate, I/O, UI rendering。
micro-task包括:process.nextTick, Promises, Object.observe, MutationObserver。
console.log(1); setTimeout(function() { console.log(2); }) var promise = new Promise(function(resolve, reject) { console.log(3); resolve(); }) promise.then(function() { console.log(4); }) console.log(5);
示例中,setTimeout 和 Promise被称为任务源,来自不一样的任务源注册的回调函数会被放入到不一样的任务队列中。
有了宏任务和微任务的概念后,那 JS 的执行顺序是怎样的?是宏任务先仍是微任务先?
第一次事件循环中,JavaScript 引擎会把整个 script 代码当成一个宏任务执行,执行完成以后,再检测本次循环中是否寻在微任务,存在的话就依次从微任务的任务队列中读取执行完全部的微任务,再读取宏任务的任务队列中的任务执行,再执行全部的微任务,如此循环。JS 的执行顺序就是每次事件循环中的宏任务-微任务。