众所周知学过JS的人都知道JS是单线程运行的机制,并不是多线程的机制。所以接下来我要讲解的是JS的执行机制。希望对观看过本文章的小可爱们有帮助。 JavaScript是按照语句的出现的顺序执行的
刚开始学习接触JS的小可爱是不是都是以为从上到下的执行JS代码呢?刚开始我就是这么认为的,觉得JS是一行一行执行的。
console.log(a)
var a=1;
var b=2
console.log(b)
复制代码
大家看这段代码的时候是不是都是从上到下执行的 其实并不然 大家都知道var是存在变量提升的 在内部的真正执行机制是
var a;
console.log(a) ; //underfind
a=1;
var b;
b=2;
console.log(b); //b=2
复制代码
上面的代码时同步的 当我们遇到同步和异步在一起的时候 JS的执行机制又是怎么样的呢 下面通过一段代码了解下
setTimeout(function(){
console.log('定时器开始啦')
},1000);
new Promise(function(resolve){
console.log('马上执行for循环啦');
for(var i = 0; i < 10000; i++){
i == 99 && resolve();
}
}).then(function(){
console.log('执行then函数啦')
});
console.log('代码执行结束');
复制代码
如果我们按照刚开始学的时候自己的想法 但是我们打开浏览器看控制台的时候 输出的顺序并不是
定时器开始啦
马上执行for循环啦
执行then函数
代码执行结束
复制代码
而是
马上执行for循环啦
代码执行结束
执行then函数啦
定时器开始啦
复制代码
JS的事件
既然js是单线程,所以就像你去做核算的时候,必须得一个一个排队。同理js任务也要一个一个顺序执行
如果一个任务耗时过长,那么后一个任务也必须等着。那么问题来了,假如我们想一个购物网站,但是商品详情页的图片很大,加载很慢。难道我们的网页就一直都加载那张图片嘛,后面的任务都在等图片的加载嘛?其实并么有 因此将任务分为两类
- 同步任务
- 异步任务
当我们打开网站时,网页的渲染过程就是一大堆同步任务比如页面骨架和页面元素的渲染。而像加载图片音乐之类占用资源大耗时久的任务,就是异步任务。关于这部分有严格的文字定义,但本文的目的是用最小的学习成本彻底弄懂执行机制,所以我们用导图来说明:
导图要表达的内容要用文字来表述的话
-
同步和异步任务分别进入不同的执行"场所",同步的进入主线程,异步的进入Event Table并注册函数。
-
当指定的事情完成时,Event Table会将这个函数移入Event Queue。
-
主线程内的任务执行完毕为空,会去Event Queue读取对应的函数,进入主线程执行。
-
上述过程会不断重复,也就是常说的Event Loop(事件循环)。
说了这么多 还是代码更加清晰
let data=[]; $ajax({ url:"请求数据的地址" data:data; success:()=>{ consolelog('发送成功!')}; }) cosole.log('代码执行完毕') 复制代码
大家都知道AJAX是一个异步操作 所以ajax会进入Event Table,注册一个回调函数success
然后控制台会输出cosole.log('代码执行完毕') 。当同步事件完成之后,success回调函数进入Event Queue 之后就会输出consolelog('发送成功!')};这段代码
setTimeout
setTimeout是一个最熟悉不过的异步函数。可以进行延时执行任务
setTimeout(()=>{
console.log('延时5秒')
},5000)
复制代码
如果setTimeout用的地方比较多,那么问题也就出现了,有时候会超过5秒才会执行函数,这是为什么呢? 我们先看一个例子:
setTimeout(()=>{
for(var i=0,i<1000000000,i++)
{
i=2*i
console.log(i)
}
},2000)
复制代码
当我们把这段代码去浏览器的控制台查看的时候,打印出来i的时间实际是超过两秒的。
- 当setTimeout事件进入Event Table并注册,计时开始
- 执行for循环的时候因为电脑的计算时间是大于2秒的
- 等2秒到了,setTimeout完成了,但是for循环还在计算。只能等着for循环执行完毕才可以进入主线程执行
通过上面的代码我们知道setTimeout这个函数,并不是能按照我们设置的时间而输出
setInterval
setInterval和setTimeout是一对龙凤胎,都差不多,只不过后者循环的执行。对于执行顺序说,setInterval是规定一段时间执行函数 和上面说的setTimeout差不多
Promise与process.nextTick(callback)
接着我们探究Promise与process.nextTick(callback)的表现。
Promise的定义和功能本文不再赘述,不了解的读者可以学习一下阮一峰老师的Promise。而process.nextTick(callback)类似node.js版的"setTimeout",在事件循环的下一次循环中调用 callback 回调函数。
我们进入正题,除了广义的同步任务和异步任务,我们对任务有更精细的定义:
- macro-task(宏任务):包括整体代码script,setTimeout,setInterval
- micro-task(微任务):Promise,process.nextTick
事件循环的顺序,决定js代码的执行顺序。进入整体代码(宏任务)后,开始第一次循环。接着执行所有的微任务。然后再次从宏任务开始,找到其中一个任务队列执行完毕,再执行所有的微任务。听起来有点绕,我们用文章最开始的一段代码说明:
setTimeout(function() {
console.log('setTimeout');
})
new Promise(function(resolve) {
console.log('promise');
}).then(function() {
console.log('then');
})
console.log('console');
复制代码
在js的执行机制是从上到下
- 首先会遇到setTimeout是一个异步,会将其回调函数注册后分发到宏任务Event Queue
- 接下来会遇到new Promise 会立即执行,.then函数分发到微任务Event Queue中
- 遇到同步任务 立即执行console.log('console');
- 第一轮执行完毕 同步任务以及全部执行完毕,接下来我们要看有什么异步任务(宏任务、微任务)
- 第二轮开始(只有异步了) 首先setTimeout 是一个异步中的宏任务 放弃执行 看到到。then微任务执行 打印then
- 第二轮执行完毕 只剩下异步任务中的宏任务 执行setTimeout 打印setTimeout
在来一个例子 让读者深入了解
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')
})
})
复制代码
读者可以先自己尝试者写 答案我在下面会给出
第一轮开始:
-
console.log('1'); 同步 时间打印出来 1
-
setTimeout 是异步中的宏任务 放入Event Queue 我们先记录为setTimeout 1
-
process.nextTick 是异步中的微任务 放入Event Queue中 我们记录process.nextTick1
-
new Promise 直接执行 是一个同步任务 输出7
-
.then微任务 放入Event Queue 记录为.then1
-
setTimeout宏任务放入Event Queue 记录setTimeout 2
第一轮完毕(打印出来了1、7 两个宏任务setTimeout 1 setTimeout 2 个微任务.then1 process.nextTick1)
第二轮开始
-
先把微任务执行完
-
执行process.nextTick1,输出6。
-
执行then1,输出8
现在执行宏任务
-
setTimeout 1宏任务开始
-
遇到同步 console.log('2'); 输出2
-
遇到process.nextTick 是一个微任务在次放入Event Queue中 记录为process.nextTick2
-
new Promise立即执行输出4
-
then也分发到微任务Event Queue中,记为then2。
因为执行完setTimeout 1 又有了微任务 所以第2个setTimeout 得先排队
第三轮开始
-
发现有微任务 process.nextTick2和then2
-
process.nextTick2输出为3
-
then2输出为5
第一个宏任务执行完毕
第四轮开始
- 此时只剩setTimeout2了
- console.log('9'); 直接输出9
- 将process.nextTick()分发到微任务Event Queue中。记为process3。
- new Promise 直接输入11
- 将then分发到微任务Event Queue中,记为then3
第五轮开始
- 把剩下得微任务执行process3和then3
- 输出 10 12
结果为1,7,6,8,2,4,3,5,9,11,10,12 看完这篇文章希望可以理解JS得运行机制(本文得图片来自SSSYOK)