0

0

如何实现JS栈结构?栈的应用场景有哪些

煙雲

煙雲

发布时间:2025-08-19 14:34:01

|

654人浏览过

|

来源于php中文网

原创

答案:JS栈在程序执行中管理函数调用顺序,通过调用栈实现执行上下文的压入与弹出,确保函数调用正确性,并应用于撤销/重做、浏览器前进后退、表达式求值和深度优先搜索等场景。

如何实现js栈结构?栈的应用场景有哪些

在JavaScript中实现一个栈结构,最直接也最常用的方式就是基于数组。栈本质上是一种“后进先出”(LIFO)的数据结构,就像一叠盘子,你最后放上去的盘子,永远是第一个被拿走的。它的应用场景远比你想象的要广泛,从我们日常编写的代码执行,到各种复杂算法的实现,栈的身影无处不在。

解决方案

要实现一个JS栈,我们可以封装一个类,内部用一个数组来存储元素,并提供

push
(入栈)、
pop
(出栈)、
peek
(查看栈顶元素)和
isEmpty
(判断栈是否为空)等核心方法。

class Stack {
    constructor() {
        this.items = []; // 内部使用数组存储元素
    }

    // 入栈:将元素添加到栈顶
    push(element) {
        this.items.push(element);
    }

    // 出栈:移除并返回栈顶元素
    pop() {
        if (this.isEmpty()) {
            // 栈为空时,尝试出栈应该有所提示或返回特定值
            // 个人觉得返回undefined比抛出错误更“JS”一些,但具体看场景
            console.warn("栈已空,无法执行pop操作。");
            return undefined;
        }
        return this.items.pop();
    }

    // 查看栈顶元素,但不移除
    peek() {
        if (this.isEmpty()) {
            return undefined; // 空栈没有栈顶元素
        }
        return this.items[this.items.length - 1];
    }

    // 判断栈是否为空
    isEmpty() {
        return this.items.length === 0;
    }

    // 获取栈的当前大小
    size() {
        return this.items.length;
    }

    // 清空栈
    clear() {
        this.items = [];
    }

    // 打印栈内容(辅助方法)
    printStack() {
        console.log(this.items.toString());
    }
}

// 实际使用示例:
// const myStack = new Stack();
// myStack.push(10);
// myStack.push(20);
// console.log(myStack.peek()); // 输出 20
// myStack.pop();
// console.log(myStack.size()); // 输出 1

JS栈在程序执行中扮演了什么角色?

当我们谈论栈在程序执行中的角色,最典型的就是“调用栈”(Call Stack)。这东西,你每天都在用,但可能没意识到它就是个栈。每当JavaScript引擎执行到一个函数调用时,它就会把当前函数的执行上下文(包括局部变量、参数、函数返回地址等)压入调用栈。当函数执行完毕并返回时,对应的执行上下文就会从栈顶弹出。

这真是个精妙的设计。你想想,如果一个函数A调用了函数B,函数B又调用了函数C,那么调用栈的顺序就是A -> B -> C。当C执行完,它就从栈里“消失”了,控制权回到B;B执行完,再回到A。这种机制确保了函数调用的顺序性和上下文的正确性。

这也就是为什么递归函数如果写得不好,会遇到“Maximum call stack size exceeded”错误。因为每次递归调用都会往栈里压入一个新的执行上下文,如果递归没有合适的终止条件,栈就会无限增长,直到超出JS引擎分配的内存限制,然后就爆了。这就像你往盘子里不停地加盘子,总有个极限。

除了函数调用,栈在日常开发中还有哪些意想不到的应用场景?

栈的应用远不止函数调用那么简单,它在很多地方都能发挥奇效。

比如,撤销/重做(Undo/Redo)功能。你用Photoshop或者Word,每次操作都能撤销,这背后通常就是两个栈在工作:一个“撤销栈”和一个“重做栈”。当你执行一个操作,比如画了一条线,这个操作就被压入撤销栈。当你点击撤销,这个操作就从撤销栈弹出,然后压入重做栈。如果你又想“重做”,就从重做栈弹出,再压回撤销栈。是不是很巧妙?

再比如,浏览器的前进/后退功能。这和撤销/重做的逻辑非常相似。你每访问一个新页面,当前页面的URL就被压入一个“历史栈”。当你点击“后退”,当前页面从历史栈弹出,并压入一个“前进栈”,然后显示历史栈的下一个(实际上是上一个)页面。点击“前进”则反之。

还有,表达式求值。你可能在算法课上听过中缀表达式转后缀表达式(逆波兰表示法)的算法,或者直接求后缀表达式的值。这都离不开栈。比如,处理

3 + 4 * 2
这样的表达式,栈可以帮助我们正确处理运算符的优先级。当遇到数字时入栈,遇到运算符时,则根据优先级与栈顶运算符进行比较,决定是压入栈还是弹出栈进行计算。这玩意儿,说实话,一开始学的时候有点绕,但理解了原理后,你会发现栈在这里的作用简直是核心。

星火作家大神
星火作家大神

星火作家大神是一款面向作家的AI写作工具

下载

另外,深度优先搜索(DFS),无论是树还是图的遍历,虽然常常用递归实现(那自然就是利用了调用栈),但也可以用迭代的方式,这时候就需要我们自己维护一个显式的栈来存储待访问的节点。这在处理大型图结构或者避免递归深度限制时非常有用。

实现一个健壮的JS栈结构时,我们需要考虑哪些性能和边界问题?

虽然JS数组的

push
pop
方法在大多数情况下效率很高,平均时间复杂度是O(1),但它毕竟是动态数组,在内部可能涉及到内存重新分配和元素拷贝,特别是在数组容量不足时。不过,对于绝大多数日常应用来说,JS引擎对数组的优化已经做得非常好了,性能通常不是瓶颈。如果你真的在处理海量数据,或者对性能有极致要求,可以考虑链表实现的栈,但那会增加代码复杂性,而且JS引擎对原生数组的优化可能比你手写的链表更高效。所以,一般情况下,基于数组的实现是最佳实践。

至于边界问题,最常见的有:

  1. 空栈操作:当你尝试从一个空栈中

    pop
    peek
    时,应该如何处理?是返回
    undefined
    ,还是抛出一个错误?我个人倾向于返回
    undefined
    ,因为这在JavaScript中是“不存在”的常见表示,对调用者来说更友好,可以避免不必要的
    try-catch
    。但如果你希望严格控制程序流程,抛出错误也是一种选择。在上面的代码里,我选择了返回
    undefined
    并打印警告,兼顾了提示性和代码的健壮性。

  2. 类型问题:你的栈是用来存储特定类型的数据(比如只存数字,或只存字符串),还是可以存储任何类型的数据?JS是弱类型语言,默认可以存任何类型。如果你有强类型需求,可能需要在

    push
    方法里加入类型检查。不过,这通常不是栈结构本身的问题,而是业务逻辑的约束。

  3. 栈溢出:这个主要是指递归调用导致的“Maximum call stack size exceeded”。我们自己实现的

    Stack
    类并不会直接导致这个错误,因为它的底层是JS数组,理论上只受限于系统内存。但如果你用递归算法大量使用了这个栈,或者你的程序逻辑本身导致了无限递归,那么JS引擎的调用栈还是会溢出。这是一个重要的概念,值得在讨论栈的时候提一嘴。

总的来说,JS数组作为栈的底层实现,已经足够高效和健壮。我们在实现时更多关注的是如何优雅地处理空栈情况,以及提供清晰、符合预期的API接口。

相关专题

更多
js获取数组长度的方法
js获取数组长度的方法

在js中,可以利用array对象的length属性来获取数组长度,该属性可设置或返回数组中元素的数目,只需要使用“array.length”语句即可返回表示数组对象的元素个数的数值,也就是长度值。php中文网还提供JavaScript数组的相关下载、相关课程等内容,供大家免费下载使用。

557

2023.06.20

js刷新当前页面
js刷新当前页面

js刷新当前页面的方法:1、reload方法,该方法强迫浏览器刷新当前页面,语法为“location.reload([bForceGet]) ”;2、replace方法,该方法通过指定URL替换当前缓存在历史里(客户端)的项目,因此当使用replace方法之后,不能通过“前进”和“后退”来访问已经被替换的URL,语法为“location.replace(URL) ”。php中文网为大家带来了js刷新当前页面的相关知识、以及相关文章等内容

394

2023.07.04

js四舍五入
js四舍五入

js四舍五入的方法:1、tofixed方法,可把 Number 四舍五入为指定小数位数的数字;2、round() 方法,可把一个数字舍入为最接近的整数。php中文网为大家带来了js四舍五入的相关知识、以及相关文章等内容

754

2023.07.04

js删除节点的方法
js删除节点的方法

js删除节点的方法有:1、removeChild()方法,用于从父节点中移除指定的子节点,它需要两个参数,第一个参数是要删除的子节点,第二个参数是父节点;2、parentNode.removeChild()方法,可以直接通过父节点调用来删除子节点;3、remove()方法,可以直接删除节点,而无需指定父节点;4、innerHTML属性,用于删除节点的内容。

478

2023.09.01

JavaScript转义字符
JavaScript转义字符

JavaScript中的转义字符是反斜杠和引号,可以在字符串中表示特殊字符或改变字符的含义。本专题为大家提供转义字符相关的文章、下载、课程内容,供大家免费下载体验。

454

2023.09.04

js生成随机数的方法
js生成随机数的方法

js生成随机数的方法有:1、使用random函数生成0-1之间的随机数;2、使用random函数和特定范围来生成随机整数;3、使用random函数和round函数生成0-99之间的随机整数;4、使用random函数和其他函数生成更复杂的随机数;5、使用random函数和其他函数生成范围内的随机小数;6、使用random函数和其他函数生成范围内的随机整数或小数。

1031

2023.09.04

如何启用JavaScript
如何启用JavaScript

JavaScript启用方法有内联脚本、内部脚本、外部脚本和异步加载。详细介绍:1、内联脚本是将JavaScript代码直接嵌入到HTML标签中;2、内部脚本是将JavaScript代码放置在HTML文件的`<script>`标签中;3、外部脚本是将JavaScript代码放置在一个独立的文件;4、外部脚本是将JavaScript代码放置在一个独立的文件。

658

2023.09.12

Js中Symbol类详解
Js中Symbol类详解

javascript中的Symbol数据类型是一种基本数据类型,用于表示独一无二的值。Symbol的特点:1、独一无二,每个Symbol值都是唯一的,不会与其他任何值相等;2、不可变性,Symbol值一旦创建,就不能修改或者重新赋值;3、隐藏性,Symbol值不会被隐式转换为其他类型;4、无法枚举,Symbol值作为对象的属性名时,默认是不可枚举的。

554

2023.09.20

AO3中文版入口地址大全
AO3中文版入口地址大全

本专题整合了AO3中文版入口地址大全,阅读专题下面的的文章了解更多详细内容。

1

2026.01.21

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
如何进行WebSocket调试
如何进行WebSocket调试

共1课时 | 0.1万人学习

TypeScript全面解读课程
TypeScript全面解读课程

共26课时 | 5万人学习

前端工程化(ES6模块化和webpack打包)
前端工程化(ES6模块化和webpack打包)

共24课时 | 5.1万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号