JavaScript——Event Loop事件队列,同步异步操作

进程和线程

进程是cpu资源分配的最小单位(是能拥有资源和独立运行的最小单位)
线程是cpu调度的最小单位(线程是建立在进程的基础上的一次程序运行单位,一个进程中可以有多个线程)
浏览器是多进程的
浏览器之所以能够运行,是因为系统给它的进程分配了资源(cpu、内存)
简单点理解,每打开一个Tab页,就相当于创建了一个独立的浏览器进程。
JavaScript是单线程,同一个时间只能做一件事,否则会带来很复杂的同步问题。比如,假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?

因此为了解决异步的问题js用事件循环执行异步代码(https://segmentfault.com/a/1190000015806981)
在这里插入图片描述

理解Event Loop

Event Loop即事件循环,是指浏览器或Node的一种解决javaScript单线程运行时不会阻塞的一种机制,也就是我们经常使用异步的原理。
在这里插入图片描述
JavaScript代码的具体流程:

  1. 调用栈Stack中执行全局Script同步代码,这些同步代码有一些是同步语句,有一些是异步语句(比如setTimeout等);
  2. 全局Script代码执行完毕后,调用栈Stack会清空;

event loop就是看如果调用栈为空则从消息队列中取任务到调用栈中执行:

  1. 从微队列microtask queue中取出位于队首的回调任务,放入调用栈Stack中执行,执行完后microtask queue长度减1;继续取出位于队首的任务,放入调用栈Stack中执行,以此类推,直到直到把microtask queue中的所有任务都执行完毕。(注意,如果在执行microtask的过程中,又产生了microtask,那么会加入到队列的末尾,也会在这个周期被调用执行);microtask queue中的所有任务都执行完毕,此时microtask queue为空队列,调用栈Stack也为空;
  2. 取出依次宏队列macrotask queue中位于队首的任务,放入Stack中执行;执行完毕后,调用栈Stack为空;

规则

  1. 按先后顺序执行完所有同步任务
  2. 再执行异步任务,先按先后顺序执行微任务,再按先后顺序执行宏任务
  • macro-task: script (整体代码),setTimeout, setInterval, setImmediate, I/O, UI rendering.
  • micro-task: process.nextTick, Promise(原生),Object.observe,MutationObserver
    (一个简便常用的记法:Promise > set…)

async和await搭配相当于Promise,async的函数内要有await才会实行异步,await紧跟的任务执行完,再执行所有同步任务,然后才到await下一行的任务(其实就是await下一行都是异步任务,所有肯定是先执行所有同步任务)

例题

例题1 顺丰面试题

(function test() {
setTimeout(() => {
        console.log(1) 
}, 0)
new Promise((resolve, reject) => {
    console.log(2)
    resolve(null)
  }).then(v => {
    console.log(3)
 })
console.log(4);
})()
  1. 同步任务按顺序先输出2和4
  2. 异步任务先输出微任务promise.then的3,再宏任务setTimeout的1

例题2 比较全面的检测

setTimeout(function() {
    console.log("s1")
}, 0);
setTimeout(function() {
    console.log("s2")
}, 1000);
new Promise(function(resolve){
    console.log("p1");
    resolve();
    console.log("p2");
}).then(function(){
    console.log("p3");
});
console.log("w1");
async function test1() {
    console.log("a1");
    await test2();
    console.log("a2");
}
async function test2() {
    console.log("a3");
}
test1();
console.log("w2")
  1. 同步任务按顺序,输出p1 p2 w1 a1 a3 w2
  2. 异步任务,先微任务按顺序,输出p3 a2,后宏任务,输出s1 s2

参考解析

例题3 来自《深入浅出Node.js》

//加入两个nextTick的回调函数
process.nextTick(function () {
    console.log('n1');
});
process.nextTick(function () { 
    console.log('n2');
});
// 加入两个setImmediate()的回调函数
setImmediate(function () {
    console.log('s1'); 
    // 进入下次循环 
    process.nextTick(function () {
        console.log('n3');
    });
});
setImmediate(function () {
    console.log('s2'); 
});

console.log('w');
  1. 同步任务按顺序,输出 w
  2. 异步任务,先微任务输出 n1 n2,再宏任务输出 s1 s2,最后输出 n3

在新版的Node中,process.nextTick执行完后,会循环遍历setImmediate,将setImmediate都执行完毕后再跳出循环。所以两个setImmediate执行完后队列里只剩下第一个setImmediate里的process.nextTick。最后输出”n3”。(这里暂时还没弄懂为啥最后才到嵌在宏任务中的微任务)

例题4 Promise的异步

Promise.resolve(1)
    .then((res) => {
        console.log(res);
        return 2;
    })
    .catch((err) => {
        return 3;
    })
    .then((res) => {
        console.log(res);
    });
  1. 执行第一个异步任务输出1,返回2
  2. 因为是resolve函数,所以跳过catch,执行第二个then,输出2

例题4 setTimeout的时间变化

const promise = new Promise((resolve, reject) => {
	setTimeout(() => {
		console.log('开始');
		resolve('success');
	}, 5000);
});
	 
const start = Date.now();
console.log("w1")

promise.then((res) => {
	console.log(res, Date.now() - start);
});
 
promise.then((res) => {
	console.log(res, Date.now() - start);
});
  1. 先执行同步任务,Promise的executor函数,内部是异步任务setTimeout,先挂起;
    start赋值为当前时间;
    再输出w1
  2. 执行异步任务,虽然Promise.then是微任务优先于setTimeout,但是没有调用它就不会执行,所以先执行setTimeout,5s后输出“开始”;
    再调用resolve,执行第一个then,Date.now()是5s后的时间,输出success 5001;
    执行第二个then,输出success 5001

例题5 宏任务和微任务互相穿插

console.log(1);

setTimeout(() => {
  console.log(2);
  Promise.resolve().then(() => {
    console.log(3)
  });
});

new Promise((resolve, reject) => {
  console.log(4)
  resolve(5)
}).then((data) => {
  console.log(data);
  
  Promise.resolve().then(() => {
    console.log(6)
  }).then(() => {
    console.log(7)
    
    setTimeout(() => {
      console.log(8)
    }, 0);
  });
})

setTimeout(() => {
  console.log(9);
})

console.log(10);
  1. 先同步任务,输出 1 4 10
  2. 异步任务,
    先执行微任务队列,Promise.then输出5 6 7,此时里面的setTimeout是宏任务,挂到宏任务队列尾部;
    再执行宏任务队列,第一个setTimeout输出2,里面的Promise是微任务,挂到微任务队列后直接执行,输出3;第二个setTimeout输出9;最后一个是Promise中的setTimeout,输出8

参考
带你彻底弄懂Event Loop
JavaScript Event Loop Explained

一篇搞定(Js异步、事件循环与消息队列、微任务与宏任务)

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值