Event
EventLoop
事件循环(Event Loop)是现代编程中一个核心的概念,尤其在异步编程中起着至关重要的作用。它在浏览器、Node.js 和其他基于异步 I/O 的环境中广泛使用。本教程将带你从基础到深入理解事件循环。
- 什么是事件循环?
事件循环是一种调度机制,用来管理程序的执行顺序。它使得非阻塞 I/O 和异步任务得以实现,通过以下步骤工作:
- 检查是否有任务需要处理。
- 依次执行任务。
- 等待新任务并重复。
事件循环是单线程的,但它能通过异步任务模拟多任务运行。
- 事件循环的工作模型
以下是事件循环的几个关键组成部分:
任务队列(Task Queue)
存放需要执行的任务。任务可以分为:
- 宏任务(Macro Task):主程序、
setTimeout、setInterval、I/O。 - 微任务(Micro Task):
Promise.then、queueMicrotask。
事件循环的步骤
- 执行一个宏任务(如主线程代码)。
- 检查微任务队列,执行所有微任务。
- 如果有渲染更新,则进行渲染。
- 取出下一个宏任务,重复上述过程。
- 事件循环的代码实例(JavaScript)
以下是一个例子,演示任务队列的顺序:
console.log("开始");
// 宏任务
setTimeout(() => {
console.log("setTimeout");
}, 0);
// 微任务
Promise.resolve().then(() => {
console.log("Promise.then");
});
console.log("结束");
输出顺序:
开始
结束
Promise.then
setTimeout
解析:
- 主线程首先执行同步代码。
setTimeout的回调被放入宏任务队列。Promise.then的回调被放入微任务队列。- 主线程代码执行完后,先执行微任务,再执行宏任务。
- 在浏览器环境中的事件循环
浏览器的事件循环还涉及到渲染过程:
- 每次事件循环的末尾会检查是否需要重新渲染。
- 渲染通常在 16ms(即 60fps)内完成。
示例
setTimeout(() => {
console.log("setTimeout");
}, 0);
requestAnimationFrame(() => {
console.log("requestAnimationFrame");
});
Promise.resolve().then(() => {
console.log("Promise");
});
输出顺序:
Promise
requestAnimationFrame
setTimeout
- 在 Node.js 中的事件循环
Node.js 的事件循环分为六个阶段:
- timers:执行
setTimeout和setInterval的回调。 - I/O callbacks:处理一些系统操作的回调,如文件 I/O。
- idle, prepare:仅供内部使用。
- poll:检索新的 I/O 事件。
- check:执行
setImmediate的回调。 - close callbacks:执行
close事件的回调。
示例
setTimeout(() => {
console.log("setTimeout");
}, 0);
setImmediate(() => {
console.log("setImmediate");
});
输出顺序:
setTimeout
setImmediate
或
setImmediate
setTimeout
解析:
- 顺序可能取决于事件循环进入
timers还是check阶段。
- 应用场景
- 非阻塞 I/O:处理网络请求、文件操作。
- 动画更新:在浏览器中,通过
requestAnimationFrame和微任务队列。 - 任务调度:使用
setTimeout、Promise来控制代码执行的先后顺序。
- 进阶:自定义事件循环
你可以通过代码实现一个简单的事件循环:
class EventLoop {
constructor() {
this.queue = [];
}
add(task) {
this.queue.push(task);
}
run() {
while (this.queue.length > 0) {
const task = this.queue.shift();
task();
}
}
}
const loop = new EventLoop();
loop.add(() => console.log("Task 1"));
loop.add(() => console.log("Task 2"));
loop.run();
输出:
Task 1
Task 2
通过以上内容,你应该对事件循环有了一个全面的了解。如果你希望更深入某部分(如浏览器 vs Node.js 或异步编程模式),可以进一步交流!
EventTarget 接口
事件的本质是程序各个组成部分之间的一种通信方式,也是异步编程的一种实现。DOM 支持大量的事件,本章开始介绍 DOM 的事件编程。
介绍具体的事件之前,先来看看如何让 DOM 节点监听事件。
DOM节点的事件操作
DOM 节点的事件操作(监听和触发),都定义在EventTarget接口。所有节点对象都部署了这个接口,其他一些需要事件通信的浏览器内置对象(比如,XMLHttpRequest、AudioNode、AudioContext)也部署了这个接口。
该接口主要提供三个实例方法。
addEventListener():绑定事件的监听函数removeEventListener():移除事件的监听函数dispatchEvent():触发事件
EventTarget.addEventListener()
用于在当前节点或对象上(即部署了 EventTarget 接口的对象),定义一个特定事件的监听函数。一旦这个事件发生,就会执行监听函数。该方法没有返回值。
target.addEventListener(type, listener[, useCapture]);
该方法接受三个参数。
type:事件名称,大小写敏感。listener:监听函数。事件发生时,会调用该监听函数。useCapture:布尔值,如果设为true,表示监听函数将在捕获阶段(capture)触发(参见后文《事件的传播》部分)。该参数可选,默认值为false(监听函数只在冒泡阶段被触发)。