欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 健康 > 养生 > js 中堆、栈、队列+宏任务+微任务+web workers

js 中堆、栈、队列+宏任务+微任务+web workers

2025/2/3 0:56:52 来源:https://blog.csdn.net/m0_51244077/article/details/144993621  浏览:    关键词:js 中堆、栈、队列+宏任务+微任务+web workers

文章目录

    • js 中堆、栈、队列介绍
    • js中 堆、栈、队列的区别与应用
    • 拓展(宏任务与微任务)
    • Web Workers

js 中堆、栈、队列介绍

  1. 栈(Stack)
    • 概念
      • 栈是一种遵循后进先出(LIFO - Last In First Out)原则的数据结构。就像一摞盘子,最后放上去的盘子最先被拿走。在JavaScript中,函数调用栈就是一个典型的栈结构。当一个函数被调用时,它的执行上下文(包括局部变量、参数等信息)就会被压入栈中,当函数执行完毕后,这个执行上下文就会从栈顶弹出。
    • 应用实例
      • 函数调用栈
        • 例如下面的代码:
    function a() {console.log("Function a is called");b();
    }
    function b() {console.log("Function b is called");
    }
    a();
    
  • a函数被调用时,a的执行上下文被压入栈中。然后a函数内部调用b函数,此时b的执行上下文被压入栈顶。b函数执行完毕后,它的执行上下文从栈顶弹出,接着a函数执行完毕,它的执行上下文也弹出栈。
  • 表达式求值(如四则运算)
  • 考虑一个简单的数学表达式求值器,它可以处理简单的四则运算,例如3 + 4 * 2。可以利用栈来实现运算符优先级的计算。先将数字34压入栈,当遇到运算符*时,从栈顶弹出两个数字进行乘法运算,将结果再压入栈。接着遇到+运算符,再从栈顶弹出两个数字进行加法运算。
  1. 堆(Heap)
    • 概念
      • 堆是一种用于动态分配内存的数据结构,在JavaScript中主要用于存储对象和数组(复杂数据类型)。它是一个无序的内存区域,与栈不同,堆中的内存分配和释放是由垃圾回收机制(Garbage Collection)来管理的。JavaScript的引擎会自动分配和回收堆中的内存,程序员一般不需要手动管理堆内存。
    • 应用实例
      • 对象存储
        • 例如创建一个对象:
let person = {name: "John",age: 30
};
  • 这个person对象就存储在堆中。它的内存空间是在运行时动态分配的,JavaScript引擎会在堆中找到一块足够大的空间来存储这个对象的属性和值。
  • 大型数组存储
  • 当创建一个大型数组,如let largeArray = new Array(1000000);,这个数组也是存储在堆中的。因为栈的空间相对较小,通常用于存储简单数据类型(如基本数据类型的值)和函数调用相关的信息,而像这种大型的数据结构就存储在堆中。
  1. 队列(Queue)
    • 概念
      • 队列是一种遵循先进先出(FIFO - First In First Out)原则的数据结构。就像排队买票,先到的人先买票离开。在JavaScript中,可以用数组来模拟队列的操作。
    • 应用实例
      • 任务队列(事件循环中的任务队列)
        • 在JavaScript的事件循环机制中,有宏任务队列和微任务队列。例如,当浏览器加载页面时,用户触发的事件(如点击事件)会被放入宏任务队列。当执行一个异步操作(如setTimeout),它的回调函数也会被放入宏任务队列。当执行栈为空时,事件循环会从宏任务队列中取出一个任务执行。
        • 例如:
console.log("Script start");
setTimeout(() => {console.log("Timeout callback");
}, 0);
console.log("Script end");
  • 在这里,console.log("Script start")先执行,然后setTimeout的回调函数被放入宏任务队列,接着console.log("Script end")执行。当执行栈为空时,从宏任务队列中取出setTimeout的回调函数执行。
  • 数据缓冲队列
  • 假设你正在开发一个网络应用,从服务器接收数据。数据可能是断断续续地到达的,你可以使用一个队列来缓冲这些数据。当接收到新的数据时,将其放入队列的尾部,当需要处理这些数据时,从队列的头部取出数据进行处理。例如:
let dataQueue = [];
function receiveData(data) {dataQueue.push(data);
}
function processData() {if (dataQueue.length > 0) {let data = dataQueue.shift();// 在这里进行数据处理,比如解析数据等操作console.log("Processing data:", data);}
}

js中 堆、栈、队列的区别与应用

  1. 区别
    • 存储数据类型
      • :主要存储基本数据类型(如numberstringbooleanundefinednull)以及函数调用的执行上下文。这些数据的大小是固定的,并且在编译阶段就可以确定所需的内存空间。例如,一个number类型的数据在栈中占用固定的字节数。
      • :用于存储复杂数据类型,也就是对象(包括普通对象、数组、函数等)。对象的大小不固定,因为其属性的数量和内容可以动态变化。比如一个包含多个属性的对象,其内存占用空间取决于属性的数量和每个属性值的大小。
      • 队列:本身不用于存储特定的数据类型,而是一种数据结构模式。在JavaScript实现中,通常可以用数组来存储各种类型的数据,无论是基本数据类型还是对象,重点在于按照先进先出的规则操作这些数据。
    • 内存管理方式
      • :内存的分配和释放是自动的,遵循后进先出原则。当一个函数被调用时,其相关的局部变量等信息被压入栈,函数执行结束后,这些信息就会从栈顶自动弹出,内存空间被释放。这种自动管理机制简单高效,但栈的空间相对较小。
      • :内存的分配是动态的,由JavaScript引擎的垃圾回收机制(Garbage Collection,GC)来管理。当对象不再被引用时,垃圾回收器会在适当的时候回收其占用的内存。由于堆的内存空间较大,但管理相对复杂,垃圾回收过程可能会对性能产生一定影响。
      • 队列:在内存管理方面没有特殊的机制,主要依赖于所使用的存储结构(如数组)自身的内存管理。如果用数组来模拟队列,数组的内存分配和释放遵循JavaScript中数组的规则。
    • 数据访问方式
      • :数据的访问遵循后进先出原则,只能从栈顶进行插入(压栈)和删除(出栈)操作。就像一个只有一个开口的容器,最后放入的东西最先被取出。
      • :对象在堆中的存储位置是通过引用(指针)来访问的。一个变量(存储在栈中)保存了对象在堆中的内存地址,通过这个引用可以访问和操作对象的属性。访问对象的属性是通过解引用的方式,从存储引用的变量找到堆中的对象。
      • 队列:按照先进先出的规则访问数据,从队列头部取出数据,从队列尾部插入数据。可以将其想象成一个管道,数据从一端进入,从另一端出去。
  2. 应用
  • 栈的应用
    - 函数调用和递归:在函数调用过程中,栈用于存储函数的执行上下文,包括局部变量、参数、返回地址等信息。递归函数的执行也依赖于栈,每一次递归调用都会将新的执行上下文压入栈中。例如,计算阶乘的递归函数:
function factorial(n) {if (n === 0 || n === 1) {return 1;}return n * factorial(n - 1);
}
  • 表达式求值:可以利用栈来实现算术表达式求值,特别是对于包含括号和不同优先级运算符的表达式。例如,将中缀表达式转换为后缀表达式并求值的过程中,栈可以用于存储运算符和操作数,按照运算符的优先级和括号规则进行计算。
  • 堆的应用
    • 对象和数组操作:在JavaScript中,几乎所有的对象和数组都存储在堆中。这包括创建和操作DOM节点(在浏览器环境中)、处理复杂的数据结构如树形结构(如HTML文档的DOM树)、存储和管理大量的数据集合(如数据库查询结果以对象数组形式存储)。
      • 动态内存分配:当需要在运行时动态创建大量的数据结构,如游戏中的角色属性对象、图形绘制中的图形对象等,这些对象的内存分配都在堆中进行。垃圾回收机制确保了在对象不再使用时释放内存,防止内存泄漏。
    • 队列的应用
      • 任务队列和事件循环:在JavaScript的事件驱动编程模型中,任务队列(宏任务队列和微任务队列)是核心概念。例如,setTimeoutsetInterval的回调函数、用户触发的事件处理函数(如点击事件、键盘事件等)都被放入任务队列中,等待执行栈空闲后按照先进先出的顺序执行。
      • 消息队列和异步处理:在一些异步编程场景中,如Web Workers(在浏览器中实现后台线程处理)或者Node.js中的异步I/O操作,消息队列可以用于传递和处理异步任务的结果。比如,一个Web Worker完成计算任务后,将结果通过消息队列发送回主线程进行处理。

拓展(宏任务与微任务)

  1. 概念
    • 宏任务队列(Macrotask Queue)
      • 宏任务队列是一个用于存储宏任务的队列。宏任务是指那些比较大型、执行时间相对较长的任务。在浏览器环境或者Node.js环境中,常见的宏任务包括setTimeoutsetIntervalI/O操作(如读取文件、网络请求)、DOM操作(如添加或删除节点)等。这些任务按照先进先出(FIFO)的原则排队等待执行。
      • 事件循环(Event Loop)会不断检查调用栈是否为空,当调用栈为空时,它会从宏任务队列中取出一个宏任务放入调用栈中执行。例如,当设置一个setTimeout函数时,其回调函数就会被放入宏任务队列中,等待合适的时机执行。
    • 微任务队列(Microtask Queue)
      • 微任务队列用于存储微任务。微任务通常是一些比较小的、执行速度相对较快的任务。在JavaScript中,常见的微任务包括Promisethen/catch/finally回调、MutationObserver(用于观察DOM变化)回调、queueMicrotask函数(用于手动添加微任务)等。
      • 微任务队列的优先级高于宏任务队列。当一个宏任务执行完毕后,在执行下一个宏任务之前,事件循环会先检查微任务队列是否为空。如果微任务队列中有任务,就会依次执行这些微任务,直到微任务队列清空后,才会从宏任务队列中取出下一个宏任务执行。
  2. 工作流程示例
    • 假设我们有以下代码:
console.log('Script start');
setTimeout(() => {console.log('SetTimeout callback');
}, 0);
Promise.resolve().then(() => {console.log('Promise then callback');
});
console.log('Script end');
  • 执行过程如下:
    • 首先执行console.log('Script start'),输出Script start
    • 遇到setTimeout,其回调函数被放入宏任务队列。
    • 遇到Promise.resolve().then(),其回调函数被放入微任务队列。
    • 执行console.log('Script end'),输出Script end
    • 此时当前宏任务(主代码块)执行完毕,事件循环检查微任务队列,发现有任务,执行Promisethen回调函数,输出Promise then callback
    • 微任务队列清空后,事件循环从宏任务队列中取出setTimeout的回调函数并执行,输出SetTimeout callback
  1. 应用场景
    • 微任务队列应用场景
      • 异步操作的顺序保证Promise广泛用于处理异步操作,通过微任务队列可以确保Promise的后续操作(thencatchfinally)按照正确的顺序执行。例如,在一系列连续的Promise操作中,后一个Promisethen回调可以在前一个Promise完成后立即执行,保证了异步操作的连贯性。
      • DOM更新的优化MutationObserver利用微任务队列来处理DOM的变化观察。当DOM发生变化时,MutationObserver的回调函数作为微任务执行。这使得DOM更新可以在当前宏任务执行完所有同步操作之后、下一个宏任务开始之前进行处理,避免了不必要的DOM重绘和回流,提高了性能。
    • 宏任务队列应用场景
      • 延迟执行和定时任务setTimeoutsetInterval是典型的宏任务应用,用于在指定的延迟时间后执行任务或者周期性地执行任务。例如,在网页中实现一个定时刷新数据的功能,或者在一定延迟后显示一个提示信息,都可以使用setTimeout将相应的任务放入宏任务队列。
      • I/O操作和长时间任务:在Node.js中,读取文件、网络请求等I/O操作通常作为宏任务。这些任务可能需要较长的时间来完成,将它们放入宏任务队列可以让其他任务(如处理用户输入、执行一些快速的计算等)在等待I/O完成的过程中继续执行,提高系统的整体效率。

Web Workers

  1. Web Workers概念

    • Web Workers是HTML5提供的一种在后台线程中运行脚本的机制。在传统的JavaScript编程中,所有的脚本都在单线程(主线程)中运行,这意味着如果一个任务需要花费很长时间,如复杂的计算或者大量的数据处理,会阻塞主线程,导致页面无响应(例如,浏览器的UI冻结,用户无法进行交互操作)。Web Workers允许你将一些耗时的任务放在独立于主线程的后台线程中执行,这样主线程可以继续响应用户的操作,从而提高页面的性能和用户体验。
    • Web Workers通过消息传递机制与主线程进行通信。它不能直接访问DOM元素,因为DOM操作不是线程安全的,多个线程同时操作DOM可能会导致不可预测的错误。每个Web Worker都有自己的全局对象(self),这个全局对象与主线程的window对象不同,它没有documentwindow等与DOM相关的属性。
  2. 应用场景

    • 复杂计算任务
      • 在一些需要进行大量数学计算的场景中,如数据加密/解密、图形渲染中的数学计算(例如3D图形的光线追踪算法)、金融数据计算(如风险评估模型)等。这些计算可能会花费大量时间,如果在主线程中进行,会导致页面卡顿。使用Web Workers可以将这些计算任务放在后台线程进行,不影响主线程的正常运行。
    • 数据处理任务
      • 当需要处理大量的数据,如大数据集的排序、过滤、分析等。例如,在一个数据分析Web应用中,用户上传一个包含大量数据点的文件,需要对这些数据进行统计分析。可以使用Web Workers在后台处理这些数据,同时主线程可以更新进度条或者响应用户的其他操作。
    • 多任务并发处理
      • 对于一些可以并行处理的任务,比如同时从多个不同的服务器获取数据(如多图加载)或者同时进行多个独立的计算任务。通过创建多个Web Workers,可以并发地处理这些任务,加快整体任务的完成速度。
  3. 实例

    • 计算斐波那契数列(复杂计算任务)
      • 主线程代码(index.html):
<!DOCTYPE html>
<html>
<head><title>Web Workers Fibonacci Example</title>
</head>
<body><p>计算斐波那契数列</p><input type="number" id="inputNumber" placeholder="输入斐波那契数列的项数"><button id="calculateButton">计算</button><div id="result"></div><script>const calculateButton = document.getElementById('calculateButton');const resultDiv = document.getElementById('result');const inputNumber = document.getElementById('inputNumber');calculateButton.addEventListener('click', function () {const worker = new Worker('worker.js');const n = parseInt(inputNumber.value);worker.addEventListener('message', function (event) {resultDiv.textContent = '斐波那契数列的第' + n +'项是:' + event.data;});worker.postMessage(n);});</script>
</body>
</html>
  • Web Worker代码(worker.js):
self.addEventListener('message', function (e) {const n = e.data;function fibonacci(n) {if (n === 0 || n === 1) {return n;}let a = 0, b = 1, temp;for (let i = 2; i <= n; i++) {temp = a + b;a = b;b = temp;}return b;}self.postMessage(fibonacci(n));
});
  • 在这个例子中,当用户点击“计算”按钮时,主线程创建一个Web Worker,并将用户输入的斐波那契数列的项数发送给Web Worker。Web Worker在后台线程中计算斐波那契数列的指定项,计算完成后将结果发送回主线程。主线程收到结果后,将其显示在页面上。整个计算过程不会阻塞主线程,用户可以在计算过程中继续与页面进行交互。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com