话不多说题目入手:
setTimeout(function(){
console.log(1);
},0)
console.log(2);
上面这一道题目测试两个语句的输出顺序,有基础的同学估计很快就得出答案,2 1,那么入门的同学就好奇了,js不是按顺序一步一步执行吗,这里就且罢听我道说。
浏览器的工作原理
名称 | 作用 |
---|---|
进程 | cpu资源分配的最小单位 |
线程 | cpu调度的最小单位 |
就是说,进程包括线程,一个进程包含多个线程,浏览器是多进程:
进程名称 | 作用 |
---|---|
Browser进程 | 浏览器的主线程,主要负责浏览器的页面,功能包括书签、前进后退、资源下载管理等 |
渲染进程 | 内核进程、负责页面渲染、JS执行,一个浏览器可以包含多个渲染进程 |
GPU进程 | 负责GPU渲染,整个浏览器应用程序只有一个 |
插件进程 | 每个插件对应一个进程 |
在渲染引擎中又包括:
渲染引擎中的线程 | 作用 |
---|---|
GUI线程 | 解析HTML、CSS和渲染页面 |
JS引擎线程 | js内核,解析和执行JS代码 |
事件线程 | 控制事件循环 |
定时器线程 | 处理定时器相关逻辑 |
异步请求线程 | 发起Ajax时会生成该线程 |
这里可以看到定时器是有单独的线程,有专门的执行它的地方,而这个地方是个异步执行环境,js引擎执行是一个同步的过程,同步和异步不过多阐述,前面文章有讲:js时间线以及异步,异步执行环境就不得不提一下事件队列
任务队列
当js引擎开始解析代码时,把任务压入执行栈,然后判断栈中任务类型,异步就把任务丢到异步处理模块中,有结果时把回调函数放到任务队列中,可以理解成分类,把各自放到对应类型的执行环境,执行栈秉承先进后出的顺序,任务队列则是先进先出,执行栈先执行任务,当执行栈清空会把任务队列中的任务拉到执行栈执行,而执行栈执行任务队列中任务的过程又叫做事件循环
事件循环
宏任务和微任务
读取任务队列中的任务不是说优先级一样,这里就诞生了宏任务和微任务的概念
任务 | 代表 |
---|---|
宏任务 | script环境 setTimeout、setInterval、I/O、postMessage、 MessageChannel、setImmediate |
微任务 | Promise,process.nextTick,MutaionObserver |
任务队列进入执行栈中会以优先级高的先入栈,这里注意一点,script整体环境都是一个宏任务,所以微任务由宏任务执行过程中产生,除去同步代码执行完毕后,微任务执行优先级总是要优于剩余的异步宏任务。
总结
综合上诉,那么开头那个题就相当简单不过了,先入栈,然后定时器丢到任务队列,任务栈只剩log(2),出栈读取任务队列log(1),清空栈输出,再来一个综合性的题目恢复一下
setTimeout(function(){
console.log(1)
},1)
new Promise((res,rej) => {
console.log(2);
res("ok")
}).then(() => {
console.log(3)
})
console.log(4)
输出的顺序那就应该是2431了,别看promse是异步处理,但是new promise()是同步,只有then回调才是异步,所以同步顺序中先输出24,任务栈中读取promise优先级大于settimeout,输出31