浅谈浏览器架构、单线程js、事件循环、消息队列、宏任务和微任务( 三 )


事件循环可以拆为“事件”+“循环” 。先来聊聊“事件”:
如果你有一定的前端开发经验,对于下面的“事件”一定不陌生:

  • 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、事件循环、消息队列、宏任务和微任务

文章插图
 
入队出队图
浅谈浏览器架构、单线程js、事件循环、消息队列、宏任务和微任务

文章插图
 
在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");


推荐阅读