js执行规则
Q1:注册函数时会发生什么?
名字会写进event table里面
Q2:js的异步
js是一门单线程语言,全部的实现异步的方法,都是用同步去模拟的。这一点灰常重要!
Q3:事件循环Event Loop
这实际上就是js的执行机制了,也就是js实现异步的方法。
Q4:js的执行和运行
执行和运行区别很大,js在不同的环境下,如node,浏览器,Ringo等,执行方式是不同的。但是运行大多指js解析引擎,是统一的。
1,关于javascript
h5中提出了Web-Worker,但是js单线程这一点仍未改变,所谓的多线程都是用单线程模拟出来的!
2,Event Loop事件循环
js分同步任务和异步任务。
当我们打开网站时,网页的渲染过程就是一大堆同步任务,比如页面骨架和页面元素的渲染。而像加载图片音乐之类占用资源大耗时久的任务,就是异步任务。
(画图不易,且画且珍惜)
1586852433.png
如何判断主线程为空?
js引擎存在monitoring process进程,会持续不断的检查主线程执行栈是否为空,一旦为空,就会去Event Queue那里检查是否有等待被调用的函数。
let data = [];
$.ajax({
url:www.javascript.com,
data:data,
success:() => {
console.log('发送成功!');
}
})
console.log('代码执行结束');
分析:
(1) Event Table注册success函数
(2) 执行console.log('代码执行结束');
(3) ajax完成,success函数进入Event Queue
(4) 执行success函数
3,setTimeOut
function task(){
console.log('定时器')
}
function sleep(delay) {
var start = (new Date()).getTime();
while((new Date()).getTime() - start < delay) {
continue;
}
}
console.log('开始')
setTimeout(() => {
task
}, 3000)
sleep(4000);
console.log('睡眠之后')
分析:
(1)console.log('开始'),task进入Event Table
(2)执行sleep函数,主线程会卡在这儿,直到函数执行完成
(3)3秒之后,计时时间timeout结束,task进入Event Queue,但是sleep函数仍旧在执行,继续等待。
(4)4秒之后,console.log('睡眠之后');task进入主线程执行,结束。
4,setInterval
执行规则与setTimeOut类似,对于setInterval(fn,ms)来说,每隔ms时间,就会将fn推入Event Queue中,而不是每隔ms时间,就执行fn,这也意味着,如果fn的执行时间大于ms,那么就完全看不出有时间间隔了!!
5,Promise和process.nextTick(callback)
process.nextTick(callback)类似于node版的setTimeOut,在事件循环的下一次循环中调用callback。
续上回,除了广义的同步和异步任务,我们还有更精细的任务划分:
macro-task(宏任务):包括整体代码片段script,setTimeOut,setInterval
micro-task(微任务):Promise和process.nextTick
不同的任务类型会进入不同的Event Queue,比如setTimeOut,setInterval会进入相同的Event Queue。
时间循环的顺序,决定js的执行顺序。代码开始之后,从整体代码片段(宏任务)开始执行,同时将setTimeOut,setInterval中的函数置入Event Table中,随后执行微任务;下一轮开始,宏任务Event Queue开始执行,之后执行微任务。举个栗子:
setTimeout(function() {
console.log('setTimeout');
})
new Promise(function(resolve) {
console.log('promise');
resolve();
}).then(function() {
console.log('then');
})
console.log('console');
分析:
这段代码作为宏任务,进入主线程。
遇到setTimeout,将其回调函数注册后置入Event Queue。
遇到new Promise,立即执行console.log('promise')。then函数进入微任务Event Queue。
console.log('console');
第一个宏任务结束,检查微任务,执行then函数。
第一轮结束,开始第二轮。宏任务中setTimeout回调函数执行。
结束
关系图如下所示(抠图不自赏)
1586852481.png
下面,上主菜:
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。
-
new Promise,立即执行console.log('7'),then函数置入微任务Event Queue,记为then1.
-
又是一个setTimeout,其回调函数注册,置入宏任务Event Queue,记为setTimeout2。
-
查看微任务Event Queue,发现process1和then1.
-
执行process1,输出6
-
执行then1,输出8
-
第一轮输出 1,7,6,8
第二轮开始
-
查看宏任务Event Queue,发现setTimeout1和setTimeout2.
-
执行setTimeout1。console.log('2')。
-
process.nextTick,其回调函数注册,置入微任务Event Queue,记为process2。
-
new Promise,立即执行console.log('4');then函数置入微任务Event Queue,记为then2.
-
宏任务结束,查看微任务,发现process2和then2。
-
执行process2,输出3。
-
执行then2,输出5.
第二轮输出2,4,3,5。开始第三轮
-
执行setTimeout2。
-
console.log('9')。
-
process.nextTick,其回调函数注册,置入微任务Event Queue,记为process3。
-
new Promise,立即执行console.log('11');then函数置入微任务Event Queue,记为then3.
-
宏任务结束,查看微任务,发现process3和then3。
-
执行process2,输出10。
-
执行then2,输出12.
第三轮输出9,11,10,12
三轮结束,最后输出1,7,6,8,2,4,3,5,9,11,10,12。
(请注意,node环境下的事件监听依赖libuv与前端环境不完全相同,输出顺序可能会有误差)
6,总结
微任务和宏任务还有很多种类,比如setImmediate等等,执行都是有共同点的,有兴趣的同学可以自行了解
javascript是一门单线程语言
Event Loop是javascript的执行机制