事件循环可以拆为“事件”+“循环” 。先来聊聊“事件”:
如果你有一定的前端开发经验,对于下面的“事件”一定不陌生:
- click、mouseover等等交互事件
- 事件冒泡、事件捕获、事件委托等等
- addEventListener、removeEventListener()
- CustomEvent(自定义事件实现自定义交互)
- EventEmitter、EventBus(on,emit,once,off,这种东西经常出面试题)
- 第三方库的事件系统
那么浏览器怎么知道有事件发生了呢?怎么知道用户对某个button做了一次click呢?
如果我们的主线程只是静态的,没有循环的话,可以用js伪代码将其表述为:
function mainThread() {console.log("Hello World!");console.log("Hello JavaScript!");}mainThread();执行完一次mainThread()之后,这段代码就无效了,mainThread并不是一种激活状态,对于I/O事件是没有办法捕获到的 。因此对事件加入了“循环”,将渲染进程的主线程变为激活状态,可以用js伪代码表述如下:
// click eventfunction clickTrigger() {return "我点击按钮了"}// 可以是while循环function mainThread(){while(true){if(clickTrigger()) { console.log(“通知click事件监听器”) }clickTrigger = null;}}mainThread();也可以是for循环for(;;){if(clickTrigger()) { console.log(“通知click事件监听器”) }clickTrigger = null;}在事件监听器中做出响应:button.addEventListener('click', ()=>{console.log("多亏了事件循环,我(浏览器)才能知道用户做了什么操作");})什么是消息队列?消息队列可以拆为“消息”+“队列” 。消息可以理解为用户I/O;队列就是先进先出的数据结构 。而消息队列,则是用于连接用户I/O与事件循环的桥梁 。队列数据结构图

文章插图
入队出队图

文章插图
在js中,如何发现出队列FIFO的特性?下面这个结构大家都熟悉,瞬间体现出队列FIFO的特性 。
// 定义一个队列let queue = [1,2,3];// 入队queue.push(4); // queue[1,2,3,4]// 出队queue.shift(); // 1 queue [2,3,4]假设用户做出了"click button1","click button3","click button 2"的操作 。事件队列定义为:const taskQueue = ["click button1","click button3","click button 2"];while(taskQueue.length>0){taskQueue.shift(); // 任务依次出队}任务依次出队:"click button1""click button3""click button 2"此时由于mainThread有事件循环,它会被浏览器渲染进程的主线程事件循环系统捕获,并在对应的事件处理器做出响应 。
button1.addEventListener('click', ()=>{console.log("click button1");})button2.addEventListener('click', ()=>{console.log("click button 2");})button3.addEventListener('click', ()=>{console.log("click button3")})依次打印:"click button1","click button3","click button 2" 。因此,可以将消息队列理解为连接用户I/O操作和浏览器事件循环系统的任务队列 。
如何实现一个 EventEmitter(支持 on,once,off,emit)?
/** * 说明:简单实现一个事件订阅机制,具有监听on和触发emit方法 * 示例: * on(event, func){ ... } * emit(event, ...args){ ... } * once(event, func){ ... } * off(event, func){ ... } * const event = new EventEmitter(); * event.on('someEvent', (...args) => { *console.log('some_event triggered', ...args); * }); * event.emit('someEvent', 'abc', '123'); * event.once('someEvent', (...args) => { *console.log('some_event triggered', ...args); * }); * event.off('someEvent', callbackPointer); // callbackPointer为回调指针,不能是匿名函数 */class EventEmitter {constructor() {this.listeners = [];}on(event, func) {const callback = (listener) => listener.name === event;const idx = this.listeners.findIndex(callback);if (idx === -1) {this.listeners.push({name: event,callbacks: [func],});} else {this.listeners[idx].callbacks.push(func);}}emit(event, ...args) {if (this.listeners.length === 0) return;const callback = (listener) => listener.name === event;const idx = this.listeners.findIndex(callback);if (idx === -1) return;const listener = this.listeners[idx];if (listener.isOnce) {listener.callbacks[0](...args);this.listeners.splice(idx, 1);} else {listener.callbacks.forEach((cb) => {cb(...args);});}}once(event, func) {const callback = (listener) => listener.name === event;let idx = this.listeners.findIndex(callback);if (idx !== -1) return;this.listeners.push({name: event,callbacks: [func],isOnce: true,});}off(event, func) {if (this.listeners.length === 0) return;const callback = (listener) => listener.name === event;let idx = this.listeners.findIndex(callback);if (idx === -1) return;let callbacks = this.listeners[idx].callbacks;for (let i = 0; i < callbacks.length; i++) {if (callbacks[i] === func) {callbacks.splice(i, 1);break;}}}}// let event = new EventEmitter();// let onceCallback = (...args) => {//console.log("once_event triggered", ...args);// };// let onceCallback1 = (...args) => {//console.log("once_event 1 triggered", ...args);// };// // once仅监听一次// event.once("onceEvent", onceCallback);// event.once("onceEvent", onceCallback1);// event.emit("onceEvent", "abc", "123");// event.emit("onceEvent", "abc", "456");// let onCallback = (...args) => {//console.log("on_event triggered", ...args);// };// let onCallback1 = (...args) => {//console.log("on_event 1 triggered", ...args);// };// event.on("onEvent", onCallback);// event.on("onEvent", onCallback1);// event.emit("onEvent", "abc", "123");// // off销毁指定回调// event.off("onEvent", onCallback);// event.emit("onEvent", "abc", "123");
推荐阅读
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 服务器概念、组成、分类和架构之争
- 如何把网页设置成极速?
- 今日头条技术架构到底有多牛?
- 生意参谋子账号专用浏览器 生意参谋可以开多少个子账号
- 80后架构师教你学ApacheBeam,一个开源统一分布式数据处理编程库
- 架构的腐化是必然的
- 浅谈数据库分布式架构设计
- MySQL数据库架构和同步复制流程
- 基于LAMP环境搭建论坛
- 软件架构概述
