共计 2374 个字符,预计需要花费 6 分钟才能阅读完成。
一文搞懂 javascript 事件循环原理?「前端每日一题 v22.11.16」
理解 javascript 的第一步,就是要理解事件循环机制。然而要真正的理解 javascript 的事件循环机制并不容易,因为它是 javascript 引擎最根底的局部。它能够让单线程的 javascript 以非阻塞形式执行
事件循环机制 Event Loop,要真正理解这些,咱们可能须要晓得 javascript 引擎相干内容,比方调用栈,宏工作,微工作,工作队列等相干,一起看一下这些概念
调用栈
调用栈是一种跟踪 javascript 代码执行的数据结构,它是一个栈,因而遵循先进后出的数据结构,执行的每个函数都示意为调用栈中的一个帧,并放在前一个函数的顶部
举个例子,这是最常见的一段 javascript 代码
function foo(){console.log('foo')
bar()}
function bar(){console.log('bar')
}
foo()
咱们具体的讲述一下上述代码的执行流程
- 调用栈最后是空的,疏忽后面所有的函数,直到 foo 被调用
- 函数 foo 被压入调用栈,调用栈列表为 foo
- 执行 foo 中的代码 console.log,栈列表增加 console.log 函数,这个函数执行完就会立即被推出栈,所以栈列表还是 foo
- 代码执行到 bar 时候,该函数被调用
- bar 函数被压入调用栈,调用栈列表为 bar-foo
- 执行 bar 中的代码,直到全副执行结束,调用栈列表没变
- 返回来继续执行 foo 函数中 bar 之后的代码
- 删除调用栈列表中 bar 函数,调用栈列表为 foo
- foo 中的 bar 函数后续没有可执行代码
- 删除调用栈列表中的 foo,此时调用栈列表为空
一开始咱们会往空的调用栈中推入函数,执行完函数代码之后,调用栈又会移除这个函数,最终又会失去一个空的调用栈,如果在执行过程中,
工作和工作队列
下面的案例次要是针对 javascript 单线程的执行形式,然而这种形式是不可取的,比方如果咱们碰到了定时器,或者申请数据等执行工夫比拟长的代码的话,前面的逻辑就得等这些代码执行实现能力进行下一步
所以就有了工作队列的概念
工作分为同步工作和异步工作,区别就是同步工作执行后立即就能取到后果,异步工作须要等一会儿能力取到后果
同步工作执行依照调用栈执行逻辑,执行实现移出调用栈,异步工作执行也会进入调用栈,不同的是它会将其回调函数或者回调工作放入一个工作队列,工作队列遵循先进先出的准则,放入工作队列的函数不会立即执行,须要期待调用栈中同步的工作执行实现
当调用栈清空之后,也就是所有同步工作完结之后,解释器开始读取工作队列执行,将 曾经实现 的异步工作放入调用栈执行
看代码
console.log('foo')
setTimeout(() => {console.log('bar')
}, 1000)
console.log('loo')
- console.log(‘foo’)压入调用栈,执行实现推出调用栈
- setTimeout 压入调用栈
- 浏览器的定时线程会等工夫完结后将 setTimeout 中的回调函数箭头函数放入工作队列
- setTimeout 推出调用栈
- console.log(‘loo’)压入调用栈,执行并推出
- 此时同步工作全副执行实现,调用栈为空
- 1s 过后,浏览器的定时线程将匿名函数放入工作队列
- 调用栈为空,执行工作队列匿名函数内容
- console.log(‘bar’)执行
- 匿名函数推出调用栈
- 调用栈为空
宏工作和微工作
后面说到的工作队列,其实还分为宏工作和微工作,这两种都属于异步工作,它们的区别就在于它们的执行程序。
为什么会辨别宏工作和微工作,最次要的一点就是如果工作队列中某一个工作须要 1 个小时,后续的工作都须要期待一个小时,这显然是不合理的,所以宏工作和微工作最次要的一个目标就是辨别工作的优先级,微工作优先级高于宏工作
宏工作
- 整体代码都属于宏工作
- UI 交互事件
- I/O
- setTimeout
- setInterval
- setImmediate
- requestAnimationFrame
微工作
- process.nextTick
- MutationObserver
- Promise.then catch finally
console.log('foo')
setTimeout(() => {console.log('bar')
}, 0)
new Promise((res) => {res(1)}).then((d) => {console.log(d)})
程序为 foo、1、bar
事件循环
下面的内容联合起来,基本上就是整个事件循环的机制了
- 先从调用栈开始
- 同步工作压入调用栈,开始执行
- 碰到异步工作,将有后果的回调压入工作队列,留神辨别宏工作和微工作
- 调用栈清空之后,微工作队列依照先入先执行程序开始执行
- 微工作队列清空之后,一个循环完结
- 进入宏工作队列直到宏工作队列清空
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();
console.log('promise2')
}).then(function () {console.log('promise3');
});
console.log('script end');
依照以上逻辑,做完这个题就能够出师了,当前碰到所有的这种执行程序的都不怕了