javascript技巧

关注公众号 jb51net

关闭
首页 > 网络编程 > JavaScript > javascript技巧 > JavaScript EventLoop机制

一文带你掌握JavaScript中的EventLoop机制

作者:柏油

JavaScript是 单线程、非阻塞 的,它通过事件队列 (Event Loop) 的方式来实现异步回调,所以本文小编就带大家来深入了解一下JavaScript中的EventLoop机制,需要的可以了解下

EventLoop

JavaScript是 单线程非阻塞 的,它通过事件队列 (Event Loop) 的方式来实现异步回调。

关键点

为什么 javascript 是单线程的

我们知道多线程可以提高效率啊,为什么JavaScript被设计为单线程,主要原因是它创建的初衷:与用户的交互以及操作DOM。

在Web页面中,脚本需要响应用户的操作:如点击按钮、提交表单等,这些操作涉及到对DOM的读写。假如JavaScript是多线程的,那么就会引入复杂的同步问题。

例如:两个线程同时尝试修改同一个DOM节点,那么会产生竞态条件,导致不可预测的结果。所以js一诞生就是单线程并且未来也不会改变

为了利用多核CPU的计算能力,HTML5提出Web Worker标准,允许JavaScript脚本创建多个线程,但是子线程完全受主线程控制,且不得操作DOM。所以,这个新标准并没有改变JavaScript单线程的本质。

简化开发

单线程模型简化了JavaScript的设计和使用。开发者可以更专注于实现功能,而不用担心如线程同步、死锁等多线程编程中常见的问题。

避免DOM操作冲突

javascript选择只用一个主线程来执行代码,任何时刻只有一个操作可以被执行,从而保证了DOM操作的一致性和可预测性。

事件循环和异步编程

尽管JavaScript是单线程的,但它通过事件循环(Event Loop)机制支持异步编程。事件循环允许JavaScript在执行I/O密集型或耗时任务(如Ajax请求、文件操作等)时,不会阻塞主线程。

这是通过将这些任务设置为异步操作并配合回调函数、Promise或async/await来实现的。事件循环和异步编程模型使JavaScript能够高效地处理多种操作,而无需引入多线程的复杂性。

什么是非阻塞

JavaScript的非阻塞特性是指在执行耗时操作(如I/O操作、请求数据等)时,不会阻塞程序的其他部分继续执行。

这种特性是通过异步编程模式实现的,它允许JavaScript在等待某个操作完成的同时,继续执行代码的其他部分,从而提高程序的整体性能和响应能力。

为什么需要非阻塞

在浏览器环境中,JavaScript运行在单线程中,这意味着在同一时间内只能执行一个任务。如果JavaScript执行一个长时间运行的任务,如从服务器下载大量数据,它将阻塞后续代码的执行,导致整个页面无法响应用户操作,甚至出现卡顿。

通过非阻塞异步编程,JavaScript可以在等待某个长时间运行的任务完成的同时,继续执行其他任务,提高应用的响应性和用户体验。

实现非阻塞的方式有哪些?

1) 回调函数(Callbacks)

最初,JavaScript通过回调函数实现非阻塞行为。当一个异步操作开始时,会传入一个函数(回调函数),这个函数会在异步操作完成时被调用。

这种方式虽然解决了非阻塞的问题,但当有多个异步操作需要协同工作时,会导致所谓的“回调地狱”(Callback Hell),使得代码难以阅读和维护。

2) Promise

为了解决回调函数带来的问题,ES6引入了Promise对象。Promise提供了一种更优雅的方式来处理异步操作。

它代表了一个异步操作的最终完成(或失败)及其结果值。通过.then()和.catch()方法,可以更容易地组织和管理异步操作及其结果。

3)async/await

ES7进一步引入了asyncawait关键字,使得使用Promise的代码可以像写同步代码那样简洁明了。

async函数声明一个函数是异步的,而await关键字用于等待一个Promise解决(resolve)。使用asyncawait可以以同步的方式写出清晰、易读的代码,同时保持异步操作的非阻塞特性。

同步任务、异步任务、宏任务、微任务之间的概念和关系

JS 分为同步任务和异步任务;同步任务都在JS引擎线程上执行,形成一个执行栈,它们与事件循环无关;异步任务被分为宏任务和微任务,它们都依赖事件循环进行调度,但执行时机不同;

事件触发线程管理一个任务队列,异步任务触发条件达成,将回调事件放到任务队列中。

同步任务:这些任务在主线程上按顺序执行,执行过程会阻塞后续任务的执行,直到当前任务完成。同步任务的执行不依赖事件循环,它们直接在调用栈(执行栈)中按顺序执行。

异步任务:异步任务的执行不会立即完成,它们依赖事件循环进行调度。异步任务包括宏任务和微任务,它们在特定的时间点被推入各自的队列中等待执行。

宏任务(Macrotasks)

宏任务是一类异步任务,它们代表了一些较大的、独立的工作单元。每个宏任务的执行会在一个新的事件循环中进行,包括:setTimeoutsetIntervalI/O 操作UI 渲染(在浏览器环境中)、postMessage

微任务(Microtasks)

微任务也是异步任务,但它们用于处理一些需要尽快执行的较小的工作单元。**微任务在当前宏任务执行完毕后、下一个宏任务开始之前执行。包括:Promise(⚠️promise本身是同步的,回调函数才是异步的)的回调(.then.catch.finally)、MutationObserver的回调、queueMicrotask

一次事件循环的执行

在同一次事件循环中,微任务(Microtasks)总是在当前宏任务(Macrotasks)之后执行。

上图中,主线程运行的时候,产生堆(heap)和栈(stack),栈中的代码调用各种外部API,它们在"任务队列"中加入各种事件(click,load,done)。

只要栈中的代码执行完毕,主线程就会去读取"任务队列",依次执行那些事件所对应的回调函数。

实践

setTimeout(() => { 
  console.log(1)
}, 0) 
console.log(2)
const promise2 = new Promise((resolve) => {
  console.log(3)
  resolve(3) 
}) 
promise2.then((res) => { 
  console.log(4) 
})

执行步骤和输出结果的解释:

所以,输出结果为:2341

js整体执行顺序是:同步任务 -> 微任务 -> 宏任务 -> 微任务 -> 宏任务 -> ...

总结

分享一个实用工具:Event Loop可视化面板

到此这篇关于一文带你掌握JavaScript中的EventLoop机制的文章就介绍到这了,更多相关JavaScript EventLoop机制内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文