javascript是一门单线程的非阻塞的脚本语言。单线程意味着javascript在执行代码的任何时候,都只有一个主线程来处理所有的任务。
那么javascript引擎是如何实现这一点的呢?
因为事件循环(event loop)。先上图:
图片解读:
- 同步和异步任务分别进入不同的执行场所,同步的进入主线程,异步的进入
Event Table
并注册函数
- 当指定的事情完成时(重点),
Event Table
会将这个函数移入Event Queue
中
- 主线程内的任务执行完毕为空,会去
Event Queue
读取对应的函数,进入主线程执行
- 上述的过程会不断的重复,也就是常常说的
Event Loop(事件循环)
。
简单例子
我们来一个简单的例子来说明下:
console.log('1'); setTimeout(() => { console.log('2'); }, 0) console.log('3');
上面的代码将输出下面的结果:
1 3 2
因为setTimeout是一个异步的任务,所以会在最后才执行。
那么,我们来个复杂点的例子:
复杂例子
console.log('1'); setTimeout(() => { console.log('2') }, 1000); new Promise((resolve, reject) => { setTimeout(() => { console.log('3'); }, 0); console.log('4'); resolve(); console.log('5'); }).then(() => { console.log('6'); }); console.log('7');
上面的代码输出的结果是:
1 4 5 7 6 3 2
看到这代码的时候是不是有些蒙圈?在我们揭开谜底之前,先来了解下微任务和宏任务。
微任务和宏任务
微任务和宏任务都是异步的任务,他们都属于队列,主要区别是它们的执行顺序--微任务会比宏任务先执行。
宏任务包含有:setTimeout, setInterval, setImmediate, I/O, UI rendering
微任务包含有:process.nextTick, promise.then, MutationObserver
嗯~回到上面的代码,如下:
console.log('1'); setTimeout(() => { console.log('2') }, 1000); new Promise((resolve, reject) => { setTimeout(() => { console.log('3'); }, 0); console.log('4'); resolve(); console.log('5'); }).then(() => { console.log('6'); }); console.log('7');
在执行到new Promise
的时候会立马新建一个promise对象并立即执行
。所以会输出 1,4,5
,而then则会在Event Table
中注册成回调函数并放在微任务队列中,而两个setTimeout(输出3)和setTimeout(输出2,1s后完成的啊)
会被先后注册成回调函数并放在宏任务队列中。
理解了上面的一些原理之后,我们再来练下手...
console.log(1) process.nextTick(() => { console.log(8) setTimeout(() => { console.log(9) }) }) setTimeout(() => { console.log(2) new Promise(() => { console.log(11) }) }) let promise = new Promise((resolve,reject) => { setTimeout(() => { console.log(10) }) resolve() console.log(4) }) fn() console.log(3) promise.then(() => { console.log(12) }) function fn(){ console.log(6) }
得到的结果是:
1 4 6 3 8 12 2 11 10 9
客官可以画下图整理下思路,然后代码运行验证一下啊💨