食堂阿姨看了都要点赞:这个“栈“究竟是个什么鬼?
先别急我们来看看以下场景想象一下你在一个特别小的食堂里打饭。食堂阿姨贼凶只从一头上菜。你端着餐盘走过去阿姨依次给你放了米饭 → 红烧肉 → 青菜 → 煎蛋好家伙堆了四层。但你端到座位上准备吃的时候你第一口吃的是什么煎蛋。因为它就在最上面最好拿。而最后一口吃的是米饭因为它在最底下被压得最深。恭喜你刚才你无意识地用了一次栈。到底什么是栈栈Stack就是一种只能从一端操作数据的数据结构。你只能往里放东西叫入栈push往外拿东西叫出栈pop而且有个铁律后来居上先走一步。专业点说叫LIFOLast In First Out。说人话最后进去的最先出来。就像叠盘子、子弹上膛、IDE的撤销操作——都是栈的经典应用。栈的几个重要概念栈顶Top就是那个最上面、最容易被拿走的元素(煎蛋就是栈顶)。栈底Bottom就是那个被压在最底下、最难被拿到的元素(米饭就是栈底)。入栈Push把新元素放到最上面。Push 五花肉 ← 五花肉新来的站在最上面 ← 煎蛋 ← 青菜 ← 红烧肉 ← 米饭最老的被压在最底下出栈Pop把最上面的元素拿走。Pop 一下 ← 青菜 ← 煎蛋被拿走了走了走了 ← 红烧肉 ← 米饭前端最熟悉的栈函数调用栈每天写代码其实无时无刻不在跟栈打交道。**当 JavaScript 执行一段代码时它用调用栈Call Stack来记住现在执行到哪了。看这个例子这段代码执行时调用栈是这么变化的第1步调用 daily → Stack: [daily] 第2步调用 lunch → Stack: [lunch, daily] 第3步调用 eat → Stack: [eat, lunch, daily] 第4步eat 执行完出栈 → Stack: [lunch, daily] 第5步lunch 执行完 → Stack: [daily] 第6步daily 执行完 → Stack: []看到了吗eat 是最后进去的但它最先出来。这就是栈。栈有什么特点必须记住这4条1. 后进先出LIFO这是栈的灵魂。不接受反驳没有例外。2. 只有栈顶能操作栈底的东西想出来等上面所有人都走了再说。3. 没有随机访问普通数组你可以随便拿第5个元素但栈不行。你想拿到栈底的米饭必须先把煎蛋、青菜、红烧肉全部 Pop 出去。4. 操作只有 O(1)入栈和出栈都是常量时间不需要遍历不需要找来找去就是快。前端场景中常用的栈案例一JS 引擎的调用栈上面刚讲过。每次你调一个函数就入栈函数执行完就出栈。为什么你写递归写多了会报Maximum call stack size exceeded因为栈太深了内存装不下了。// 这种无限递归调用栈会爆炸 functionrecursion() { recursion(); } recursion(); // Uncaught RangeError: Maximum call stack size exceeded案例二浏览器历史记录前进/后退浏览器的前进和后退功能背后就是一个栈。案例三撤销操作CtrlZ你在 VSCode 里疯狂 CtrlZ为什么能把你的操作一步步撤销回来因为编辑器内部维护了一个操作栈。你做了打字 → 删掉 → 粘贴 → 撤销 ↓ ↓ ↓ ↓ Stack: [打字] [打字,删掉] [打字,删掉,粘贴] [打字,删掉]每次撤销就是一次 Pop栈顶的操作被取消掉。案例四括号匹配写 JS 的时候有没有想过IDE 怎么知道你写的{[(什么)]}有没有少一个括号用栈来判断functionisValid(str) { conststack []; constmap { ): (, ]: [, }: { }; for (constcharofstr) { if (([{.includes(char)) { // 开括号入栈 stack.push(char); } else { // 闭括号检查栈顶是不是对应的开括号 if (stack.pop() !map[char]) { returnfalse; // 不匹配凉了 } } } // 栈空 全部匹配成功 returnstack.length0; } isValid({ [ ( ) ] }); // true isValid({ [ ( ] ) }); // false ← 括号乱套了案例五Vue/React 中的组件渲染栈简化理解React 的协调Reconciliation过程虽然内部用的是链表但从概念上也可以用栈来理解——子组件的渲染和卸载本质上遵循后进先出的顺序。用 JavaScript 五行代码实现一个栈别被数据结构四个字吓到栈的实现简单到离谱classStack { constructor() { this.items []; // 用数组存数据不会丢 } push(item) { this.items.push(item); // 入栈 } pop() { returnthis.items.pop(); // 出栈 } peek() { returnthis.items[this.items.length-1]; // 看一眼栈顶是谁 } isEmpty() { returnthis.items.length0; // 空不空 } } // 用起来 conststacknewStack(); stack.push(米饭); stack.push(红烧肉); stack.push(青菜); stack.push(煎蛋); stack.pop(); // 煎蛋 ← 最后一个进来的第一个出来 stack.pop(); // 青菜栈这个数据结构看起来简单但到处都在用。你写的每个函数调用、每次撤销操作、每个括号校验、浏览器的前进后退——背后都是栈在默默工作。搞懂了栈你就算是真正踏进了程序员的思维世界。