一.js是一门单线程的语言,js是按照语句出现的顺序执行的
二.Javascript事件循环
因为js是单线程,所有js任务要一个一个顺序执行,任务分为:
同步任务
异步任务
流程图:
任务进入执行栈——>同步任务还是异步任务?
同步任务——>主线程——>任务全部执行完毕——>读取任务队列中的结果,进入主线程执行
异步任务——>event table——>注册回调函数——>event queue——>读取任务队列中的结果,进入主线程执行
上面的过程不断重复——就是常说的Event Loop(事件循环)
let data = [];
$.ajax({
url:www.javascript.com,
data:data,
success:() => { console.log('发送成功!'); } })
console.log('代码执行结束')
分析:1.ajax进入Event Table,注册回调函数 success()
2.执行同步任务 console.log('代码执行结束')
3.ajax事件完成,回调函数 进入event queue
4.主线程 从event queue中读取 回调函数success 并执行
setTimeout
setTimeout(() => { task() },3000)
sleep(10000000)
分析:1.task()进入event table并注册,计时开始
2.执行同步任务 sleep()
3.3秒到了,但是同步任务未完成,所以event queue中仍需等待
4.执行完,task()从 event queue进入主线程执行,延迟时间大于3秒
setTimeout(fn,0)
指某个任务在主线程最早可得的空闲时间执行,即主线程执行栈中的同步任务完成后,立即执行fn,0毫秒是达不到的,最低是4毫秒
setInterval
会每隔指定的时间将注册的函数置入 event queue
setInterval(fn, ms): 不是每过ms 会执行一次fn,而是,没过ms,会有fn进入event queue,一旦fn执行时间超过了延迟时间ms,那就看不出来有时间间隔了
Promise 与 process.nextTick(callback)
process.nextTick(callback)类似 node.js版的“setTimeout”,在事件循环的下一次循环中调用callback回调函数
任务更精细的定义:
宏任务: 包括整体代码script, setTimeout, setInterval
微任务:promise, process.nextTick
事件循环循序:进入整体代码(宏任务)后,开始第一次循环,接着执行所有的微任务,然后再次从宏任务开始,找到其中一个任务队列执行完毕,再执行所有的微任务
setTimeout(function() { console.log('setTimeout'); })
new Promise(function(resolve) { console.log('promise');
}).then(function() { console.log('then'); })
console.log('console')
// promise
//console
//undefined
//setTimeout
分析:1.这段代码作为宏任务,进入主线程
2.先遇到settimout,那么将其回调函数注册后分发到宏任务event queue
3.接下来promise,立即执行,并将then函数分发到微任务event queue
4.遇到 console.log并执行
5.整体代码script作为第一个宏任务执行结束,看看有哪些微任务?then函数在微任务中,并执行
6.第一轮事件循环结束后,开始第二轮循环,从宏任务event queue开始,发现 了宏任务 setimout对应的回调函数,立即执行
7.结束
console.log('1');
setTimeout(function() {
console.log('2');
process.nextTick(function() { console.log('3'); })
new Promise(function(resolve) {
console.log('4');
resolve();
}).then(function() { console.log('5') }) })
process.nextTick(function() { console.log('6'); })
new Promise(function(resolve) {
console.log('7');
resolve(); }).
then(function() { console.log('8') })
setTimeout(function() { console.log('9');
process.nextTick(function() { console.log('10'); })
new Promise(function(resolve) { console.log('11');
resolve();
}).then(function() { console.log('12') }) })
分析
第一轮事件循环流程分析如下:
整体script作为第一个宏任务进入主线程,遇到console.log,输出1。
遇到setTimeout,其回调函数被分发到宏任务Event Queue中。我们暂且记为setTimeout1。
遇到process.nextTick(),其回调函数被分发到微任务Event Queue中。我们记为process1。
遇到Promise,new Promise直接执行,输出7。then被分发到微任务Event Queue中。我们记为then1。
又遇到了setTimeout,其回调函数被分发到宏任务Event Queue中,我们记为setTimeout2。
宏任务Event Queue微任务Event Queue
setTimeout1process1
setTimeout2then1
上表是第一轮事件循环宏任务结束时各Event Queue的情况,此时已经输出了1和7。
我们发现了process1和then1两个微任务。
执行process1,输出6。
执行then1,输出8。
好了,第一轮事件循环正式结束,这一轮的结果是输出1,7,6,8。那么第二轮时间循环从setTimeout1宏任务开始:
首先输出2。接下来遇到了process.nextTick(),同样将其分发到微任务Event Queue中,记为process2。new Promise立即执行输出4,then也分发到微任务Event Queue中,记为then2。
宏任务Event Queue微任务Event Queue
setTimeout2process2
then2
第二轮事件循环宏任务结束,我们发现有process2和then2两个微任务可以执行。
输出3。
输出5。
第二轮事件循环结束,第二轮输出2,4,3,5。
第三轮事件循环开始,此时只剩setTimeout2了,执行。
直接输出9。
将process.nextTick()分发到微任务Event Queue中。记为process3。
直接执行new Promise,输出11。
将then分发到微任务Event Queue中,记为then3。
宏任务Event Queue微任务Event Queue
process3
then3
第三轮事件循环宏任务执行结束,执行两个微任务process3和then3。
输出10。
输出12。
第三轮事件循环结束,第三轮输出9,11,10,12。
整段代码,共进行了三次事件循环,完整的输出为1,7,6,8,2,4,3,5,9,11,10,12。
(请注意,node环境下的事件监听依赖libuv与前端环境不完全相同,输出顺序可能会有误差)
总结
1.js的异步
js是一门单线程的语言,无论是什么新框架新语法实现的所谓异步,都是用同步的方法去模拟的
2.事件循环event Loop
事件循环是js实现异步的一种方法,也是js的执行机制
3.js的执行与运行
js在不同的环境下,不如node,浏览器等,执行方式是不同的
运行大多指js解析引擎,是统一的
作者:ssssyoki
链接:https://juejin.im/post/59e85eebf265da430d571f89