1.为什么要有Event Loop?
因为Javascript设计之初就是一门单线程语言,因此为了实现主线程的不阻塞,Event Loop这样的方案应运而生。
因为js是单线程运行的,所以异步要基于回调来实现,而event loop就是异步回调的实现原理
JS先把同步代码执行完再去执行异步代码,如果某一行执行报错,则停止下面代码的执行。
通过例子来讲event loop机制
console.log("Hi");
setTimeout(function cb1() {
console.log("cb1"); // cb即callback
}, 5000);
console.log("Bye");
显示的Web APIs只有宏任务,异步任务分为宏任务和微任务。
1.同步代码(栈里面的代码)顺序执行,遇到异步代码就记录一下,在此过程中异步代码如果是宏任务移动到Web APIs,直到定时的时间到就放入宏任务队列,即图中的Callback Queue。
如果是微任务则放入微任务队列(本例子没有微任务),不会经过Web APIs。如果同步代码执行完,调用栈call stack为空,去查看微任务队列,每执行完一个微任务,它就会从微任务队列出队,直到微任务队列微空后,尝试DOM渲染(如果DOM结构发生变化)。
2.然后Event Loop开始工作,然后轮询查找宏任务队列Callback Queue,如有则移动到Call Stack执行...
3.每执行完一个宏任务,就会去检查微任务队列,若微任务队列有,就执行到微任务为空,再尝试DOM渲染,然后去看宏任务队列,继续轮询查找(永动机一样不停地重复操作)。
注意:
1.这里的Web APIs就是处理定时或者异步API的。
2.微任务是ES6语法规定的,宏任务是由浏览器规定的。
3.执行的顺序是
执行栈中的代码 => 微任务 => 宏任务(后面会展开讲)
那如果代码是如下
<button id="btn1">提交</button>
<script>
console.log("Hi");
$("#btn1").click(function(e) {
console.log("button clicked");
})
console.log("Bye");
</script>这个和上面的例子几乎一样,只不过回调函数放在Web APIs,点击按钮的时候回调函数就放在Callback Queue。从这里可以看出DOM事件的触发也是基于event loop的。
2.Promise三种状态
Promise三种状态:pending、fulfilled、rejected
状态变化:
1.pending-->fulfilled(成功了)
2.pending-->rejected(失败了)
状态变化是不可逆的
状态的表现
1.pending状态不会触发then和catch
2.fulfilled状态的Promise会触发后续的then回调函数
3.rejected状态的Promise会触发后续的catch回调函数
then和catch改变状态
then正常返回fulfilled的Promise对象,里面有报错则返回rejected的Promise对象
catch正常返回fulfilled的Promise对象,里面有报错则返回rejected的Promise对象
Promise.reject(reason)返回一个状态为失败的Promise对象,并将给定的失败信息传递给对应的处理方法catch
Promise.resolve(value)返回一个状态为成功的Promise对象,并将成功信息传递给对应方法then
Promise.resolve(obj):从一个then或一个值创建一个新的promise
Promise.reject(err):创建一个被拒绝的promise,err作为原因
const p1 = Promise.resolve().then(()=>{
return 100;
})
//Promise.resolve()返回fulfilled状态的Promise对象,然后then执行完不报错,还是返回一个fulfilled状态的Promise
// console.log('p1', p1)
p1.then(()=>{ // 传进来p1返回的100,但是没有使用,打印123
console.log("123");
})
const p2 = Promise.resolve().then(()=>{
throw new Error("then error");
})
//Promise.resolve()返回fulfilled状态的Promise对象,然后then执行完报错了!返回一个rejected状态的Promise
// console.log('p2', p2); // rejected触发后续的catch回调
p2.then(()=>{
console.log('456');
}).catch(err=>{
console.error('err100', err);
}) // rejected状态的Promise后续会触发catch而不是then运行结果
123
err100 Error: then error
at <anonymous>const p3 = Promise.reject("my error").catch(err=>{
console.error(err);
})
console.log('p3', p3);
p3.then(()=>{
console.log(100);
})
const p4 = Promise.reject('my error').catch(err => {
throw new Error("catch err");
})
console.log('p4', p4);
p4.then(()=>{
console.log(200);
}).catch(()=>{
console.error("some err");
})这个和上面的例子几乎一样,只不过回调函数放在Web APIs,点击按钮的时候回调函数就放在Callback Queue。从这里可以看出DOM事件的触发也是基于event loop的。
Promise.resolve().then(()=>{
console.log(1);
}).catch(()=>{
console.log(2);
}).then(()=>{
console.log(3);
})返回
1
3Promise.resolve()返回一个fulfilled状态的Promise后续触发then回调,然后打印1,then执行完返回fulfilled状态的Promise,然后再执行then,打印3,返回返回fulfilled状态的Promise。因为没Error,没有rejected状态的Promise,所以不会触发catch回调。
Promise.resolve().then(()=>{
console.log(1);
throw new Error('erro1');
}).catch(()=>{
console.log(2);
}).then(()=>{
console.log(3);
})返回
1
2
3与上例的不同就是多了throw new Error('erro1');
Promise.resolve()返回一个fulfilled状态的Promise后续触发then回调,然后打印1,执行throw new Error('erro1');返回一个rejected状态的Promise,触发catch回调函数,打印2,接着返回fulfilled状态的Promise,触发后面then的回调,打印3,返回一个fulfilled状态的Promise。
Promise.resolve().then(()=>{
console.log(1);
throw new Error('erro1');
}).catch(()=>{
console.log(2);
}).catch(()=>{
console.log(3);
})返回
1
2解释:这个和上题的不同就是最后一个回调是catch的,不是then的回调。
理由和上面一模一样,只不过打印2之后返回的是fulfilled状态的Promise,后面没有then,所以不打印3。catch里面只要没报错Error,那么就是fulfilled状态的Promise。
我个人觉得需要额外注意的点:大家不要忽略最后的返回值,返回值会链式传递给下一个回调,只不过我们这里的例子没有强调返回值,等于return undefined;如果then/catch回调函数有形参,而上一个回调函数有返回值,那么返回值会作为下一个回调的形参。
3.async/await
因为是之前的异步回调会有callback hell(回调地狱)的问题,所有ES6出来了Promise,但是Promise的的then/catch也是基于回调函数,后来ES8出来了async/await,看起来用同步语法消灭了回调函数。
function loadImg(src) {
const p = new Promise(
(resolve, reject) => {
const img = document.createElement('img')
img.onload = () => {
resolve(img)
}
img.onerror = () => {
const err = new Error(`图片加载失败 ${src}`)
reject(err)
}
img.src = src
}
)
return p
}
const src1 = 'http://www.imooc.com/static/img/index/logo_new.png'
const src2 = 'https://avatars3.githubusercontent.com/u/9583120'
loadImg(src1).then(img => {
console.log(img.width)
return img
}).then(img => {
console.log(img.height)
}).catch(ex => console.error(ex))
// ================上面用Promise也是不断的处理回调=================
// 立即执行函数前面有个!,是为了避免和上面最后一句不写分号导致当成函数的冲突
!(async function() {
// img1
const img1 = await loadImg(src1);
console.log(img1.height, img1.width);
// img2
const img2 = await loadImg(src2);
console.log(img2.height, img2.width)
})();await后面可以跟Promise对象或者async函数执行
额外提示:立即执行函数前面有个!,是为了避免和上面最后一句不写分号导致当成函数的冲突,比如下面的例子"abc"没有分号,就把它当成了一个函数,因为后面跟着"abc"(...)(),alert换行后跟着括号还是认为是函数。你会发现平时引入js文件的时候,前面可能很多都有!就是这个原因,
3-1 async/await和Promise的关系
1.执行async函数,返回的是Promise对象
2.await相当于Promise的then
3.try...catch可捕获异常,代替了Promise的catch
综上所述
如果await等待的是一个Promise对象,那么它只想等fulfilled状态的Promise,后续的语句相当与then回调才会执行
如果等来的是rejected状态的Promise,await接不住,必须try...catch,在catch中接住它,然后可以进行一定的自定义说明。
3-2 async+表达式
await 后面跟 Promise 对象:会阻断后续代码,等待状态变为 fulfilled ,才获取结果并继续执行
await 后续跟非 Promise 对象:会直接返回
(async function () {
const p1 = new Promise(() => {})
await p1
console.log('p1') // 不会执行
})()这里的Promise里面没有resolve(), 导致await后面的表达式得不到结果,而await后面的语句都相当于Promise的then回调,只要await这里不执行,那么后面所有的callback都不会执行,所以不会打印"p1"
3-3 关于异步独立知识点
function muti(num) {
return new Promise(resolve => {
setTimeout(() => {
resolve(num * num);
}, 1000)
})
}
const nums = [1, 2, 3];
nums.forEach(async (i) =>{
const res = await muti(i);
console.log(res);
})根据上图可以看到,等待1s后3个结果同时打印,那是因为forEach循环3次已经结束了,1s的时间其实是3次循环执行到await这里卡住了,await后面的语句相当于callback,await这里不执行完是不会执行后面的,之后3次循环的await几乎同时结束,瞬间打印出1,4,9
那么如果我想要每间隔1s打印一个结果应该怎么做呢,执行异步的循环可以用for...of
function muti(num) {
return new Promise(resolve => {
setTimeout(() => {
resolve(num * num);
}, 1000)
})
}
const nums = [1, 2, 3];
!(async function() {
for (let i of nums) {
const res = await muti(i);
console.log(res);
}
})()准备
实列对象和函数对象
函数对象:将函数作为对象使用,简称函数对象
实列对象:new 函数产生的对象,简称对象
function Fn(){
// Fn函数
}
const fn = new Fn() // Fn为构造函数,fn为实列对象(简称对象)
- 关于javascript
javascript是一门单线程语言,在最新的HTML5中提出了Web-Worker,但javascript是单线程这一核心仍未改变。所以一切javascript版的"多线程"都是用单线程模拟出来的
- javascript事件循环
既然js是单线程,那就像只有一个窗口的银行,客户需要排队一个一个办理业务,同理js任务也要一个一个顺序执行。如果一个任务耗时过长,那么后一个任务也必须等着。那么问题来了,假如我们想浏览新闻,但是新闻包含的超清图片加载很慢,难道我们的网页要一直卡着直到图片完全显示出来?因此聪明的程序员将任务分为两类:
同步任务
异步任务
当我们打开网站时,网页的渲染过程就是一大堆同步任务,比如页面骨架和页面元素的渲染。而像加载图片音乐之类占用资源大耗时久的任务,就是异步任务。关于这部分有严格的文字定义,但本文的目的是用最小的学习成本彻底弄懂执行机制,所以我们用导图来说明:
导图要表达的内容用文字来表述的话:
同步和异步任务分别进入不同的执行"场所",同步的进入主线程,异步的进入Event Table并注册函数。
当指定的事情完成时,Event Table会将这个函数移入Event Queue。
主线程内的任务执行完毕为空,会去Event Queue读取对应的函数,进入主线程执行。
上述过程会不断重复,也就是常说的Event Loop(事件循环)。
我们不禁要问了,那怎么知道主线程执行栈为空啊?js引擎存在monitoring process进程,会持续不断的检查主线程执行栈是否为空,一旦为空,就会去Event Queue那里检查是否有等待被调用的函数。
- Promise与process.nextTick(callback)
传统的定时器我们已经研究过了,接着我们探究Promise与process.nextTick(callback)的表现。
Promise的定义和功能本文不再赘述,不了解的读者可以学习一下阮一峰老师的Promise。而process.nextTick(callback)类似node.js版的"setTimeout",在事件循环的下一次循环中调用 callback 回调函数。
我们进入正题,除了广义的同步任务和异步任务,我们对任务有更精细的定义:
宏任务:setTimeout、setInterval、Ajax、
I/O、UI交互事件(比如DOM事件)微任务:Promise回调、async/await、
process.nextTick(Node独有,注册函数的优先级比Promise回调函数要高)、MutaionObserver微任务执行时机比宏任务要早(记住)
注意:script全部代码、(这个是执行栈的代码,属于同步代码),包括new Promise(function(){...})里面的代码,只有then、catch回调才是微任务
为什么微任务在渲染前,宏任务在渲染后?
宏任务:DOM渲染后触发,如setTimeout
微任务:DOM渲染前触发,如Promise
- 微任务:ES 语法标准之内,JS 引擎来统一处理。即不用浏览器有任何干预,可一次性处理完,更快更及时。
- 宏任务:ES 语法没有,JS 引擎不处理,浏览器(或 nodejs)干预处理。
不同类型的任务会进入对应的Event Queue,比如setTimeout和setInterval会进入相同的Event Queue。
事件循环的顺序,决定js代码的执行顺序。进入整体代码(宏任务)后,开始第一次循环。接着执行所有的微任务。然后再次从宏任务开始,找到其中一个任务队列执行完毕,再执行所有的微任务。听起来有点绕,我们用文章最开始的一段代码说明:
setTimeout(function() {
console.log('setTimeout');
})
new Promise(function(resolve) {
console.log('promise');
}).then(function() {
console.log('then');
})
console.log('console');1.promise 和 console 是同步执行的,所以它们首先被打印出来。
2.then 中的代码是微任务,它会在当前宏任务(即整个脚本的执行)完成后立即执行。
3.setTimeout 中的代码是宏任务,它会在微任务队列清空后,在事件循环的下一次迭代中执行。由于 setTimeout 没有指定延迟时间,它会在默认的最小延迟时间(通常是 4ms 或 1ms,具体取决于浏览器实现)后执行。
promise
console
then
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')
})
})
整体script作为第一个宏任务进入主线程,遇到console.log,输出1。
遇到setTimeout,其回调函数被分发到宏任务Event Queue中。我们暂且记为setTimeout1。
遇到process.nextTick(),其回调函数被分发到微任务Event Queue中。我们记为process1。
遇到Promise,new Promise直接执行,输出7。then被分发到微任务Event Queue中。我们记为then1。
又遇到了setTimeout,其回调函数被分发到宏任务Event Queue中,我们记为setTimeout2。
宏任务Event Queue微任务Event QueuesetTimeout1process1setTimeout2then1
上表是第一轮事件循环宏任务结束时各Event Queue的情况,此时已经输出了1和7。
我们发现了process1和then1两个微任务。
执行process1,输出6。
执行then1,输出8。
好了,第一轮事件循环正式结束,这一轮的结果是输出1,7,6,8。那么第二轮时间循环从setTimeout1宏任务开始:
首先输出2。接下来遇到了process.nextTick(),同样将其分发到微任务Event Queue中,记为process2。new Promise立即执行输出4,then也分发到微任务Event Queue中,记为then2。
宏任务Event Queue微任务Event QueuesetTimeout2process2then2
第二轮事件循环宏任务结束,我们发现有process2和then2两个微任务可以执行。
输出3。
输出5。
第二轮事件循环结束,第二轮输出2,4,3,5。
第三轮事件循环开始,此时只剩setTimeout2了,执行。
直接输出9。
将process.nextTick()分发到微任务Event Queue中。记为process3。
直接执行new Promise,输出11。
将then分发到微任务Event Queue中,记为then3。
宏任务Event Queue微任务Event Queueprocess3then3!
第三轮事件循环宏任务执行结束,执行两个微任务process3和then3。
输出10。
输出12。
第三轮事件循环结束,第三轮输出9,11,10,12。
整段代码,共进行了三次事件循环,完整的输出为1,7,6,8,2,4,3,5,9,11,10,12。
(请注意,node环境下的事件监听依赖libuv与前端环境不完全相同,输出顺序可能会有误差)

新车首发,新的一年,只带想赚米的人
2025年10月新盘 做第一批吃螃蟹的人coinsrore.com
新车新盘 嘎嘎稳 嘎嘎靠谱coinsrore.com
新车首发,新的一年,只带想赚米的人coinsrore.com
新盘 上车集合 留下 我要发发 立马进裙coinsrore.com
做了几十年的项目 我总结了最好的一个盘(纯干货)coinsrore.com
新车上路,只带前10个人coinsrore.com
新盘首开 新盘首开 征召客户!!!coinsrore.com
新项目准备上线,寻找志同道合 的合作伙伴coinsrore.com
新车即将上线 真正的项目,期待你的参与coinsrore.com
新盘新项目,不再等待,现在就是最佳上车机会!coinsrore.com
新盘新盘 这个月刚上新盘 新车第一个吃螃蟹!coinsrore.com
果博东方客服开户联系方式【182-8836-2750—】?薇- cxs20250806】
果博东方公司客服电话联系方式【182-8836-2750—】?薇- cxs20250806】
果博东方开户流程【182-8836-2750—】?薇- cxs20250806】
果博东方客服怎么联系【182-8836-2750—】?薇- cxs20250806】
华纳圣淘沙公司开户新手教程
零基础学会(183-8890-9465薇-STS5099)
华纳圣淘沙公司开户
华纳圣淘沙公司开户保姆级教程(183-8890-9465薇-STS5099)
一步步教你开通华纳圣淘沙公司账户(183-8890-9465薇-STS5099)
华纳圣淘沙公司开户分步图解
首次开户必看:(183-8890-9465薇-STS5099)
华纳圣淘沙全攻略
华纳圣淘沙公司开户实操手册(183-8890-9465薇-STS5099)
华纳圣淘沙开户流程视频教程
手把手教学:(183-8890-9465薇-STS5099)
华纳圣淘沙公司开户
华纳圣淘沙公司开户完全指南(183-8890-9465薇-STS5099)