事件循环(Event loop)

一、什么叫事件循环
事件循环也就是Event loop, 是JavaScript或Node为解决单线程代码执行不阻塞主进程一种机制,也就是我们所说的异步原理。事件循环负责执行代码、收集和处理事件以及执行队列中的子任务。

二、什么是进程与线程?
进程是计算机中正在运行的程序的一个实例;每个进程都是有独立的内存空间,彼此之间互不影响;是进行资源调度的一个基本单位。类似于安装在手机上的一个应用。
线程是进程中执行的一个实体单位;复杂应用中一个进程通常包括多个线程,用于同时执行不同的任务,例如游戏应用中,有的线程负责网络通讯,有的线程负责游戏交互和渲染等。类似应用中的很多任务线。

三、什么是线程阻塞?
JavaScript的一大特点就是单线程,也就是说,同一个时间只能做一件事。任何在主线程上执行的长时间运行的任务(例如复杂的计算、大量的循环等)都会导致执行栈无法处理其他事件,包括用户输入、UI 更新和异步回调。这种情况下,浏览器可能会出现卡顿或无响应的情况。

四、JS如何解决线程阻塞?
使用异步编程是解决线程阻塞的主要方法之一。通过将耗时的任务放入异步回调中,可以让主线程继续处理其他任务。如下图:

JavaScript任务分类

1、同步任务: 在主线程上排队执行的任务只有前一个任务执行完毕,才能执行后一个任务,形成一个执行栈。(promise是同步任务)
2、异步任务: 不进入主线程,而是进入任务队列,当主线程中的任务执行完毕,就从任务队列中取出任务放进主线程中来进行执行。在异步模式下,创建异步任务主要分为宏任务与微任务两种。
(1) 宏任务:是由宿主(浏览器、Node)发起的,而微任务由 JS 自身发起。
宏任务(Macrotask)包括:

1. 整体代码script;
2. 定时器任务: 如setTimeout、setInterval、setImmediate (NodeJS独有);
3. I/O操作:  网络请求,文件读写;
4. 渲染任务:dom渲染,当浏览器需要重绘或重新布局时触发的任务;
5. 异步ajax等;
6. 用户交互任务:例如点击事件、输入事件等与用户交互的相关任务;
7. 请求动画帧任务:通过requestAnimationFrame()方法设置的任务,用于在每一帧进行绘画或动画操作;

这些任务都是比较耗时的操作,在事件循环中被视为宏任务,需要等待一定时间或特定的触发条件才会执行。
宏任务的执行顺序:setImmediate --> setTimeout --> setInterval --> i/o操作 --> 异步ajax。

(2) 微任务(Microtask)包括:

1. Promise回调:Promise对象的resolve或reject方法的回调函数;Promise的then、catch、finally回调;
2. MutationObserver回调:当DOM发生变化时触发的回调函数;
3. async/await函数中的后续操作:在async函数中使用await等待的操作完成后,紧接着的代码块中的任务;
4. process.nextTick:进程对象process中的一个方法。nextTick会在上一次事件循环结束,然后在下一次事件循环开始之前执行。比setTimeout(fn,0)效率高多了。

微任务的执行顺序:process.nextTick --> Promise

五、JavaScript 运行机制
1、整体的script(作为第一个宏任务)开始执行的时候,会把所有代码分为两部分:“同步任务”、“异步任务”;
2、同步任务会直接进入主线程依次执行;
3、异步任务会再分为宏任务(进入宏任务队列) 和 微任务(进入微任务队列)。
4、当主线程内的任务执行完毕(主线程为空时),会检查微任务的任务队列,如果有任务,就进入主线程全部执行,如果没有就从宏任务队列读取下一个宏任务执行;
5、每执行完一个宏任务就清空一次微任务队列,此过程会不断重复,这就是Event Loop。

六、vue中的nextTick
由于vue的更新机制是异步的,所以当数据修改之后,dom还停留在更新之前,此时想要获取更新后的dom,可以使用nextTick,表示的是下次dom更新循环结束后执行的回调。
应用场景:created 中获取dom可以使用nextTick

created() {
    // 使用nextTick可以在created生命周期获取dom节点
    this.$nextTick(() => {
        console.log(this.$refs.container);
    })
}

七、题目练习

练习一

setTimeout(function () {//宏任务放到队列中
 console.log('1');
})
new Promise(function (resolve) {
 console.log('2'); //实例化过程是同步任务,直接执行
 resolve();
}).then(function () { //放到微任务队列中
 console.log('3');
})
console.log('4'); //同步任务,直接执行
//打印顺序 2 4 3 1

分析:

1. 遇到setTimeout,异步宏任务将其放到宏任务列表中,命名为time1;
2. new Promise 在实例化过程中所执行的代码都是同步执行的( function 中的代码),输出2 ;
3.  将 Promise 中注册的回调函数放到微任务队列中,命名为 then1 ;
4.  执行同步任务 console.log('4') ,输出 4 ,至此执行栈中的代码执⾏完毕;
5. 从微任务队列取出任务 then1 到主线程中,输出 3 ,至此微任务队列为空;
6. 从宏任务队列中取出任务 time1 到主线程中,输出 1 ,至此宏任务队列为空;

练习二

console.log(1); //同步任务
setTimeout(function () { //宏任务
  console.log(2); //宏任务中的同步任务
  let promise = new Promise(function (resolve, reject) {
    console.log(3); //宏任务中的同步任务
    resolve();
  }).then(function () {
    console.log(4); //宏任务中的微任务
  });
}, 1000);
setTimeout(function () { //宏任务
  console.log(5); //宏任务中的同步任务
  let promise = new Promise(function (resolve, reject) {
    console.log(6); //宏任务中的同步任务
    resolve();
  }).then(function () {
    console.log(7); //宏任务中的微任务
  });
}, 0);
let promise = new Promise(function (resolve, reject) {
  console.log(8); //同步任务
  resolve()
}).then(function () {
  console.log(9); //微任务
}).then(function () {
  console.log(10); //微任务
});
console.log(11); //同步任务
//执行顺序:1 8 11 9 10 5 6 7 2 3 4

分析:

1. 执⾏同步任务 console.log(1) ,输出 `1` ;
2. 遇到 setTimeout 放到宏任务队列中,命名 time1 ;
3. 遇到 setTimeout 放到宏任务队列中,命名 time2 ;
4. new Promise 在实例化过程中所执⾏的代码都是同步执⾏的( function 中的代码),输出`8` ;
5. 将 Promise 中注册的回调函数放到微任务队列中,命名为 then1 ;
6. 将 Promise 中注册的回调函数放到微任务队列中,命名为 then2 ;
7. 执⾏同步任务 console.log(11), 输出 `11` ;
8. 从微任务队列取出任务 then1 到主线程中,输出` 9` ;
9. 从微任务队列取出任务 then2 到主线程中,输出 `10` ,⾄此微任务队列为空;
10. 从宏任务队列中取出 time2( 注意这⾥不是 time1 的原因是 time2 的执⾏时间为 0);
11. 执⾏同步任务 console.log(5) ,输出 `5` ;
12. new Promise 在实例化过程中所执⾏的代码都是同步执⾏的( function 中的代码),输出`6` ;
13. 将 Promise 中注册的回调函数放到微任务队列中,命名为 then3 ,⾄此宏任务time2执⾏完成;
14. 从微任务队列取出任务 then3 到主线程中,输出 `7` ,⾄此微任务队列为空;
15. 从宏任务队列中取出 time1 ,⾄此宏任务队列为空;
16. 执⾏同步任务 console.log(2) ,输出` 2` ;
17. new Promise 在实例化过程中所执⾏的代码都是同步执⾏的( function 中的代码),输出`3` ;
18. 将 Promise 中注册的回调函数放到微任务队列中,命名为 then4 ,⾄此宏任务time1执⾏完成;
19. 从微任务队列取出任务 then4 到主线程中,输出` 4 `,⾄此微任务队列为空。

练习三

//宏任务执行顺序: setImmediate --> setTimeout --> setInterval --> i/o操作 --> 异步ajax
let axios = require('axios');
let fs = require('fs');
console.log('begin'); //同步任务
fs.readFile('1.txt',(err,data)=>{ //宏任务-读写
    console.log('fs');
});
axios.get('https://api.muxiaoguo.cn/api/xiaohua?api_key=fd3270a0a9833e20').then(res=>{ 
    console.log('axios'); //宏任务-异步的Ajax
});
setTimeout(()=>{ //宏任务-setTimeout
    console.log('setTimeout')
},0);
setImmediate(()=>{ //宏任务-setImmediate
  console.log('setImmediate');
});
(async function (){
    console.log('async') //微任务?
})();
console.log('end'); //同步任务
//执行顺序:begin async end setTimeout setImmediate fs axios

分析:

setImmediate没有时间参数,它与延迟 0 毫秒的 setTimeout() 回调⾮常相似。所以当setTimeout延迟时间也是0毫秒时,谁在前面就先执行谁。此外如果setTimeout延迟时间不是0毫秒,它的执行顺序会在 i/o 操作之后。

练习四

//微任务之间的执行顺序:process.nextTick --> Promise
console.log('begin');
Promise.resolve().then(()=>{
  console.log('promise');
})
process.nextTick(()=>{
    console.log('nextTick');
});
console.log('end');
//执行顺序:begin end nextTick promise

练习五

// 宏任务队列 1
setTimeout(() => {
  // 宏任务队列 2.1
  console.log('timer_1');
  setTimeout(() => {
    // 宏任务队列 3
    console.log('timer_3')
  }, 0)
  new Promise(resolve => {
    resolve()
    console.log('new promise')
  }).then(() => {
    // 微任务队列 1
    console.log('promise then')
  })
}, 0)
 
setTimeout(() => {
  // 宏任务队列 2.2
  console.log('timer_2')
}, 0)
console.log('===== Sync queue =====')
//执行顺序:===== Sync queue =====;timer_1; new promise;promise then;timer_2;timer_

练习六

async function async1() { //async--声明一个函数是异步的
   console.log('async1 start');
   await async2(); //await--等待一个异步函数执行完成
   console.log('async1 end'); //异步微任务---await等待的操作完成后,紧接着的代码块中的任务
}
async function async2() {
   console.log('async2');
}

console.log('script start'); //同步任务

setTimeout(function() {
   console.log('setTimeout'); //异步宏任务
}, 0)

new Promise(function(resolve) {
   console.log('promise1'); //同步任务
   resolve();
}).then(function() {
   console.log('promise2'); //异步微任务
});

async1();

console.log('script end'); //同步任务
//执行顺序:script start;promise1;async1 start;async2;script end;promise2;async1 end;setTimeout;

分析:

1. 声明一个异步函数async1;
2. 声明一个异步函数async2;
3. 执行同步任务console.log('script start'),输出`script start`;
4. 遇到 setTimeout 放到宏任务队列中,命名 time1 ;
5. new Promise 在实例化过程中所执⾏的代码都是同步执⾏的( function 中的代码),输出`promise1` ;
6. 将 Promise 中注册的回调函数放到微任务队列中,命名为 w1 ;
7. 执行async1函数,执行内部同步任务,输出`async1 start`;
8. 执行async2函数,执行async2内部同步任务,输出`async2`;
9. await任务后的console.log('async1 end')任务属于微任务,加入微任务队列,命名w2;
10. 执行同步任务,输出`script end`;
11. 同步任务执行完毕,从微任务队列取出任务 w1到主线程中,  输出`promise2`;
12. 从微任务队列取出任务 w2到主线程中,  输出`async1 end`;
13. 从宏任务队列中取出 time1到主线程中,输出`setTimeout`;

练习七

const promise = new Promise((resolve, reject) => {
  console.log(1); //同步任务
    
  setTimeout(() => { //异步宏任务
    console.log('timerStart');  //异步宏任务中的同步任务
    resolve('success'); //异步微任务
    console.log('timerEnd'); //异步宏任务中的同步任务
  }, 0)

  console.log(2); //同步任务
});

promise.then((res) => {
  console.log(res); //异步微任务
});

console.log(4); //同步任务
//执行顺序:1,2,4,timerStart,timerEnd,success
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容