JavaScript 中的事件循环是一种处理异步操作的机制,它允许执行任务而不会阻塞主线程。
主要组成部分:
-
调用栈(Call Stack):用于存储执行上下文(函数调用)的栈结构。当函数被调用时,会创建一个执行上下文并被推入调用栈,当函数执行完毕时,其执行上下文会从栈中弹出。
-
消息队列(Message Queue):用于存储待执行的消息(任务)。每个消息都与一个函数相关联,当满足执行条件时,消息会被推入消息队列中。
-
事件循环(Event Loop):负责检查调用栈和消息队列的状态,当调用栈为空时,会从消息队列中取出消息并推入调用栈,以便执行相关的函数。这个过程是持续不断的,所以被称为“循环”。
-
微任务队列(Microtask Queue):用于存储微任务,微任务通常包括 Promise 的回调函数、MutationObserver 的回调函数等。微任务队列具有更高的优先级,会在每个宏任务执行完毕后立即执行。
事件循环步骤
- 执行当前调用栈中的任务,直到调用栈为空。
- 检查微任务队列,依次执行所有微任务,直到微任务队列为空。
- 如果存在宏任务,从消息队列中取出一个宏任务并推入调用栈,执行宏任务中的任务。
- 返回第一步。
宏任务(macrotasks)
1.setTimeout 和 setInterval:定时器,指定一段时间后执行回调函数或者周期性地执行回调函数。
setTimeout(function() {
console.log('This is a setTimeout callback');
}, 1000);
2.DOM 事件:例如点击事件、鼠标移动事件等,当用户与页面交互时触发。
document.addEventListener('click', function() {
console.log('This is a click event');
});
3.XHR 请求:发送网络请求并在获取响应后执行回调函数。
var xhr = new XMLHttpRequest();
xhr.open('GET', 'url');
xhr.onload = function() {
console.log('XHR request completed');
};
xhr.send();
4.I/O 操作:例如文件读取、写入等操作,通常是异步的,会被作为宏任务执行。
fs.readFile('file.txt', 'utf8', function(err, data) {
if (err) throw err;
console.log('File content:', data);
});
5.requestAnimationFrame:用于在下次重绘之前安排一次回调,通常用于执行动画效果。
微任务(microtasks)
1.Promise 的 then 方法:在 Promise 对象状态改变时执行对应的回调函数。
var promise = new Promise(function(resolve, reject) {
resolve('This is a resolved promise');
});
promise.then(function(value) {
console.log(value);
});
2.MutationObserver 的回调函数:监视 DOM 节点的变化并执行相应的操作。
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
console.log('DOM mutation observed');
});
});
observer.observe(document.body, { attributes: true, childList: true, subtree: true });
3.Object.observe 的回调函数:监视 JavaScript 对象的变化并执行相应的操作。
var obj = { name: 'John', age: 30 };
Object.observe(obj, function(changes) {
changes.forEach(function(change) {
console.log('Object change observed:', change);
});
});
obj.name = 'Jane'; // Triggers microtask
4.process.nextTick:在 Node.js 中将回调函数推入微任务队列,以便在事件循环的下一个阶段执行。
process.nextTick(function() {
console.log('This is a process.nextTick callback');
});
5.async/await:在JavaScript中,async
函数允许使用 await
关键字来暂停和等待一个 Promise
。但是,需要注意的是,await
只能用在 async
函数内部,并且它等待的是一个 Promise
。非 Promise
类型的值(如普通函数调用或者 console.log
)被 await
时,会立即执行,并返回该值(在这种情况下是一个 undefined
,因为 console.log
不返回任何值)。
Promise输出顺序
async function async1() {
console.log('async1 start')//同步代码2
await async2()//同步代码3
console.log('async1 end')//*****微任务7****
}
async function async2() {
console.log('async2')
}
console.log('script start')//同步代码1
setTimeout(function () {
console.log('settimeout')
}) //宏任务9
async1()
const promise1=new Promise(function (resolve) {
console.log('promise1')//同步代码4
resolve('sucess')//改变状态
console.log('after')//同步代码5
})
promise1.then(function () {
console.log('promise2')//微任务8
// console.log(promise1)//Promise {<fulfilled>: 'sucess'}
})//.then只有在改变状态后才执行
console.log('script end')//同步代码6
Promise.resolve(1)
.then(2)//数字
.then(Promise.resolve(3))//对象
.then(console.log("4"))
.then()
.then(console.log)//透传,将resolve(1) 的值直接传到最后一个then里
//4 1
.then
或者 .catch
的参数期望是函数,传入非函数则会发生值透传。
new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve('sucess')
},1000)
reject('rejected')
}).then(rs=>console.log(rs))
.catch(er=>console.log(er))
.then(rs=>console.log(rs))
.catch(er=>console.log(er))