事件循环机制

事件循环机制

一.任务

单线程就意味着,所有的任务都需要排队,前面一个任务结束,才会执行后一个任务.如果前一个任务耗时很长,后一个任务就不得不一直等着
任务分为2种:

1.同步任务(synchronous)

同步任务是指:在主线程上排队执行的任务,只有当前一个任务执行完毕,才能执行后一个任务

var num = 0;
console.log('任务一');
for (let index = 0; index < 100000000; index++) {
    num += index;
}
console.log(num);
console.log('任务二');

以上代码是一个同步任务.当任务一执行之后,进入for循环去计算,但是这个for循环计算需要很长的时间,所以不得不等着,只有当for循环计算完成之后,才可以去执行任务二,这种形式的任务,就是同步任务.

2 异步任务(asynchronous)

异步任务是指:不进入主线程,而是进入任务队列,只有主线程通知任务队列,某个异步任务可以执行了,那么该任务才会进入主线程执行.

console.log('任务一');
setTimeout(() => {
    console.log('任务二');
}, 3000);
console.log('任务三');
//打印结果:任务一 > 任务三 > 任务二

以上代码是一个异步任务.不按顺序执行,同时执行多个任务.因为setTimeout是一个异步任务,所以会先执行同步任务,然后再执行异步任务
所以呢,可以得到一个结论:同步任务和异步任务同时存在时,一定先执行完同步任务再执行异步任务.

setTimeout(() => {
    console.log('任务一');
}, 0);
console.log('任务二');
var num = 0;
for (let index = 0; index < 100000000; index++) {
    num += index;
}
console.log(num);
console.log('任务三');

执行结果:
任务二
4999999950000000
任务三
任务一

从上面代码可以看出,setTimeout这个异步任务不管写在哪里,都会先执行同步任务,再执行异步任务
2.1 异步任务的执行机制
同步任务执行也可以这么认为,因为它可以被视为没有异步任务的异步执行
所有同步任务都在主线程上执行,形成一个执行栈
主线程之外,还存在一个"任务队列",只要异步任务有了运行结果,就在"任务队列"之中放置一个事件
一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件.哪些对应的异步任务,于是结束等待状态,进入执行栈,开始执行
主线程不断的重复上面这一步

二.Javascript中的异步任务

3.1 setTimeout和 setInterval

setTimeout(function() { console.log(‘b’); }, 10)

3.2 dom事件

console.log("1")
dom.onclick = function () { alert(123) }
console.log("2")

Javascript中的事件基本上都是异步的,上面代码,会先执行同步代码,打印结果1,2之后,等待触发点击事件后,才会执行,所以也是个异步任务
3.3 ajax
ajax请求,不用说了,都知道,它是异步请求数据
3.4 promise

var promise = new Promise(function(resolve, reject) {//这里是同步任务
    console.log(3);
    resolve();
})
promise.then(function() {//这里是异步任务
    console.log(4);
})

三.调用栈 Call Stack(执行栈)

调用栈是一种后进先出的数据结构,当一个脚本执行的时候,js引擎会解析这段代码,并将其中同步代码按照执行顺序加入调用栈中,然后从头开始执行.
在谷歌浏览器中,我们F12调试的时候,可以看到右边的 Call Stack,也就是调用栈,所有的代码都会进出于这个调用栈.
后进先出的意思是:就像子弹壳装弹,一粒一粒的进去,但是打出来的时候,是从上面打出来的,最先压进去的最后弹出来,也就是说进去的顺序的123,打出来的顺序是321,这就是后进先出.

四.事件队列(Task Queue)

​ js引擎遇到一个异步任务之后,并不会一直等待其返回结果,而是会将这个任务交给浏览器的其他模块进行处理(以谷歌浏览器的webkit为例,是webcore模块) 继续执行调用栈中的其他任务.当一个异步任务返回结果后,js引擎会将这个任务加入与当前调用栈不同的另一个队列,我们称之为事件队列也有叫"任务队列".

五.事件循环机制

1.整体的script(作为第一个宏任务),开始执行的时候,会把所有代码分为两部分:同步任务和异步任务
2.同步任务会直接进入主线程依次执行
3.异步任务会再分为宏任务和微任务
4.宏任务进入到Event Table(事件表格)中,并在里面注册回调函数,每当指定的事件完成时,Event Table会将这个函数移入大Event Queue(事件队列)中
5.微任务也会进入到另一个Event Table中,并在里面注册回调函数,每当指定的事件完成时,Event Table会将这个函数移到Event Queue中
6.当主线程内的任务执行完毕,主线程为空时,会检查微任务的Event Queue,如果有任务,就全部执行,如果没有就执行下一个宏任务.所以,
上述过程会不断重复,这就是Event Loop事件循环
可以得到几个结论:
1.同步任务和异步任务同时存在时,一定先执行完同步任务再执行异步任务.
2.微任务的优先级高于宏任务
3.执行栈(调用栈)是后进先出的数据结构
4.事件队列是先进先出的数据结构

六.微任务和宏任务

上面说的事件循环过程是一个宏观的表述,实际上因为异步任务之间并不相同,因此他们的执行优先级也有区别.
不同的异步任务被分为两类:
微任务(micro task):promise.then、promise.nextTick(node),MutationObserver(html5 新特性)async、await

宏任务(macro task)整体代码script、setTimeout、setInterval…
需要注意的是:new Promise是会进入到主线程中立刻执行,而promise.then则属于微任务
先执行整体的宏任务,再执行异步任务中的微任务,然后执行异步任务中的宏任务

七.示例代码解读

1.示例一

console.log(1);
var timer = setTimeout(function () {//异步任务的宏任务
    console.log(2);
}, 0)
console.log(timer);//延时器的id 值为1
var promise = new Promise(function (resolve, reject) {//同步任务
    console.log(3);
    resolve();
})
promise.then(function () { //异步任务的微任务
    console.log(4);
})
console.log(5);
//1,3,5,4,2

2.示例二

async function async1() {//同步任务
    console.log('async1 start');
    await async2();
    console.log('async1 end');//异步任务的微任务
}
async function async2() {//同步任务
    console.log('async2');
}
console.log('script start');
setTimeout(function() {//异步任务的宏任务
    console.log('setTimeout');
}, 0)
async1();
new Promise(function(resolve) {//同步任务
    console.log('promise1');
    resolve();
}).then(function() {//异步任务的微任务
    console.log('promise2');
});
console.log('script end');

先执行宏任务(当前代码块也算是宏任务),然后执行当前宏任务产生的微任务,然后接着执行宏任务
从上往下执行代码,先执行同步代码,输出 script start遇到setTimeout,现把 setTimeout 的代码放到宏任务队列中,在执行 async1(),输出 async1 start, 然后执行 async2(), 输出 async2,
把 async2() 后面的代码 console.log(‘async1 end’)放到异步的微任务队列中接着往下执行,输出 promise1,把 .then()放到异步的微任务队列中;注意Promise本身是同步的立即执行函数,.then是异步执行函数
接着往下执行, 输出 script end
同步代码(同时也是宏任务)执行完成,接下来开始执行刚才放到微任务中的代码
依次执行微任务中的代码,依次输出 async1 end、 promise2, 微任务中的代码执行完成后,开始执行宏任务中的代码,输出 setTimeout
最后的执行结果如下
script start
async1 start
async2
promise1
script end
async1 end
promise2
setTimeout
3.示例三

console.log('start');
setTimeout(() => {
    console.log('children2');
    Promise.resolve().then(() => {
        console.log('children3');
    })
}, 0);

new Promise(function(resolve, reject) {//同步任务
    console.log('children4');
    setTimeout(function() {
        console.log('children5');
        resolve('children6')//这个执行了,才会执行下面的.then()
    }, 0)
}).then((res) => {
    console.log('children7');
    setTimeout(() => {
        console.log(res);
    }, 0)
})

这道题跟上面题目不同之处在于,执行代码会产生很多个宏任务,每个宏任务中又会产生微任务
从上往下执行代码,先执行同步代码,输出 start遇到setTimeout,先把 setTimeout 的代码放到宏任务队列①中接着往下执行,输出 children4
遇到setTimeout,先把 setTimeout 的代码放到宏任务队列②中,此时.then并不会被放到微任务队列中,因为 resolve是放到 setTimeout中执行的
代码执行完成之后,会查找微任务队列中的事件,发现并没有,于是开始执行宏任务①,即第一个 setTimeout, 输出 children2,此时,会把 Promise.resolve().then放到微任务队列中。
宏任务①中的代码执行完成后,会查找微任务队列,于是输出 children3;然后开始执行宏任务②,即第二个 setTimeout,输出 children5
,此时将.then放到微任务队列中。宏任务②中的代码执行完成后,会查找微任务队列,于是输出 children7,遇到 setTimeout,放到宏任务队列中。此时微任务执行完成,开始执行宏任务,输出 children6;
最后的执行结果如下
start
children4
children2
children3
children5
children7
children6

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值