1. 大厂JavaScript面试题深度解析(2026精选版)
作为前端开发者,JavaScript的掌握程度直接决定了你的技术深度和职业发展空间。本文精选了2026年大厂面试中最具代表性的JavaScript题目,从原理到实践,从基础到高级,为你全面解析这些面试题的考察点和应对策略。
1.1 Proxy深度监听实现原理
Proxy是ES6引入的强大特性,但很多开发者对其深层监听机制理解不够深入。我们先看一个基础示例:
const obj = { a: 1, b: { c: 2 } } const proxy = new Proxy(obj, { get(target, key) { console.log(`读取 ${key}`) return target[key] }, set(target, key, value) { console.log(`设置 ${key} = ${value}`) target[key] = value return true } }) proxy.a = 3 // 触发set proxy.b.c = 4 // 不会触发任何监听这个例子揭示了Proxy的一个重要限制:它只能代理第一层属性的访问。要实现深层监听,我们需要递归代理:
function deepProxy(obj, handler) { if (typeof obj !== 'object' || obj === null) return obj // 递归处理所有对象属性 for (let key in obj) { if (typeof obj[key] === 'object' && obj[key] !== null) { obj[key] = deepProxy(obj[key], handler) } } return new Proxy(obj, handler) } const obj = { a: 1, b: { c: 2, d: { e: 3 } } } const proxy = deepProxy(obj, { get(target, key) { console.log(`读取 ${key}`) return target[key] }, set(target, key, value) { console.log(`设置 ${key} = ${JSON.stringify(value)}`) target[key] = value return true } }) proxy.b.c = 4 // 触发set proxy.b.d.e = 5 // 触发setVue3响应式原理正是基于这种递归Proxy实现的。Vue3的reactive函数内部处理了更多边界情况:
import { reactive } from 'vue' const state = reactive({ user: { name: '张三', address: { city: '北京' } } }) state.user.address.city = '上海' // 触发响应式更新注意事项:
- 性能考虑:递归代理会带来额外性能开销,特别是对于大型对象
- 循环引用:需要特殊处理对象循环引用的情况
- 避免重复代理:同一对象不应被多次代理,否则会导致行为不一致
1.2 解构赋值的正确用法
解构赋值是ES6的常用特性,但面试中经常考察其细节理解。先看题目中的错误示例:
var[a,b]={a:1,b:2} // 错误写法错误原因:
- 数组解构语法用于对象解构
- 正确写法应该是
var {a, b} = {a:1, b:2}
对象解构的几种形式:
// 基础用法 const {a, b} = {a: 1, b: 2} // 别名 const {a: x, b: y} = {a: 1, b: 2} console.log(x) // 1 // 默认值 const {a = 0, b = 0, c = 0} = {a: 1, b: 2} console.log(c) // 0 // 嵌套解构 const {a: {x, y}, b} = {a: {x: 1, y: 2}, b: 3}实际应用场景:
- 函数参数解构:
function getUserInfo({name, age, city = '未知'}) { console.log(`${name}, ${age}岁, 来自${city}`) }- 交换变量值:
let a = 1, b = 2; [a, b] = [b, a] // 注意:数组解构才能这样交换- 处理API响应:
const {data: {user}, status} = await fetchUser()1.3 作用域链的深入理解
作用域链是JavaScript的核心概念,决定了变量的查找规则。我们先看一个简单示例:
var globalVar = 'global' function outer() { var outerVar = 'outer' function inner() { var innerVar = 'inner' console.log(innerVar) // 当前作用域 console.log(outerVar) // 父级作用域 console.log(globalVar) // 全局作用域 } inner() } outer()作用域链的创建过程:
- 全局作用域链:包含所有全局变量
- 函数调用时:
- 创建函数的活动对象(AO)
- 设置作用域链:当前AO -> 外层函数AO -> ... -> 全局VO
- 变量查找时沿作用域链向上查找
闭包与作用域链:
function createCounter() { let count = 0 return { increment() { count++ return count }, getCount() { return count } } } const counter = createCounter() console.log(counter.increment()) // 1在这个例子中,内部函数保持了对外部函数变量count的引用,形成了闭包。
ES6的作用域变化:
// let/const 的块级作用域 { var a = 1 let b = 2 } console.log(a) // 1 console.log(b) // ReferenceError // 循环中的表现差异 for (var i = 0; i < 3; i++) { setTimeout(() => console.log(i), 100) // 3,3,3 } for (let j = 0; j < 3; j++) { setTimeout(() => console.log(j), 100) // 0,1,2 }1.4 事件冒泡机制详解
事件冒泡是DOM事件模型的核心机制,但有些事件不会冒泡。我们先看不会冒泡的事件列表:
// 不会冒泡的事件 element.addEventListener('focus', handler) // 不会冒泡 element.addEventListener('mouseenter', handler) // 不会冒泡 img.addEventListener('load', handler) // 不会冒泡 window.addEventListener('resize', handler) // 不会冒泡 // 会冒泡的替代事件 element.addEventListener('focusin', handler) // 会冒泡 element.addEventListener('mouseover', handler) // 会冒泡事件冒泡对比实验:
<div id="parent"> <button id="child">点击我</button> </div> <script> parent.addEventListener('click', () => { console.log('父元素捕获点击事件') // 会触发 }) child.addEventListener('click', () => { console.log('子元素点击事件') }) parent.addEventListener('focus', () => { console.log('父元素focus事件') // 不会触发 }, true) // 使用捕获阶段可以监听到 </script>实际应用技巧:
- 对于不会冒泡的事件,可以使用事件捕获:
document.addEventListener('focus', handler, true)- 或者直接在目标元素上监听:
const inputs = document.querySelectorAll('input') inputs.forEach(input => input.addEventListener('focus', handler))- 使用会冒泡的替代事件:
// 用focusin代替focus element.addEventListener('focusin', handler)2. 异步编程与模块加载
2.1 async/await实现原理
async/await是建立在Promise和Generator之上的语法糖。我们先看一个简单示例:
async function fetchData() { const res = await fetch('/api') const data = await res.json() return data }Generator实现原理:
function* fetchGenerator() { const res = yield fetch('/api') const data = yield res.json() return data } function runGenerator(gen) { const g = gen() function handle(result) { if (result.done) return result.value return result.value.then(data => { return handle(g.next(data)) }) } return handle(g.next()) } runGenerator(fetchGenerator).then(data => { console.log(data) })Babel编译结果:
function _asyncToGenerator(fn) { return function() { const gen = fn.apply(this, arguments) return new Promise((resolve, reject) => { function step(key, arg) { try { const info = gen[key](arg) const value = info.value if (info.done) { resolve(value) } else { return Promise.resolve(value).then( val => step('next', val), err => step('throw', err) ) } } catch (error) { reject(error) } } step('next') }) } } const fetchData = _asyncToGenerator(function* () { const res = yield fetch('/api') const data = yield res.json() return data })错误处理最佳实践:
async function fetchWithRetry(url, retries = 3) { try { const res = await fetch(url) if (!res.ok) throw new Error(res.statusText) return await res.json() } catch (error) { if (retries <= 0) throw error await new Promise(resolve => setTimeout(resolve, 1000)) return fetchWithRetry(url, retries - 1) } }2.2 script标签加载策略
script标签的位置和属性对页面性能有重大影响。我们通过实验来分析:
位置对比实验:
<!-- 案例1:head中的阻塞脚本 --> <head> <script src="blocking.js"></script> </head> <!-- 案例2:body底部的非阻塞脚本 --> <body> <!-- 页面内容 --> <script src="non-blocking.js"></script> </body>性能指标对比:
| 指标 | head脚本 | body底部脚本 |
|---|---|---|
| DOMContentLoaded | 延迟 | 提前 |
| 首屏渲染 | 延迟 | 提前 |
| 页面完全加载 | 基本相同 | 基本相同 |
现代最佳实践:
<head> <!-- 预加载关键资源 --> <link rel="preload" href="critical.js" as="script"> <!-- 异步加载非关键脚本 --> <script src="analytics.js" async></script> <!-- defer脚本保持执行顺序 --> <script src="vendor.js" defer></script> <script src="app.js" defer></script> </head> <body> <!-- 内联关键CSS --> <style> .critical { color: red; } </style> <!-- 页面内容 --> <!-- 延迟加载非关键脚本 --> <script> window.addEventListener('load', () => { const script = document.createElement('script') script.src = 'lazy.js' document.body.appendChild(script) }) </script> </body>script属性详解:
async:异步加载,不保证执行顺序defer:异步加载,保持执行顺序module:ES6模块,默认defer行为nomodule:不支持模块时的回退方案
3. 浏览器原理与性能优化
3.1 页面渲染全流程
从输入URL到页面显示是一个复杂的过程,我们可以将其分解为以下几个阶段:
关键阶段时间线:
导航阶段:
- DNS查询(50-200ms)
- TCP连接(100-300ms)
- TLS握手(HTTPS,100-400ms)
- HTTP请求/响应(取决于内容大小)
渲染阶段:
- HTML解析(构建DOM树)
- CSS解析(构建CSSOM树)
- JavaScript编译与执行
- 构建渲染树(结合DOM和CSSOM)
- 布局计算(重排)
- 绘制(重绘)
- 合成(GPU加速层)
性能优化关键点:
// 使用Performance API测量关键指标 const timing = performance.timing const metrics = { dns: timing.domainLookupEnd - timing.domainLookupStart, tcp: timing.connectEnd - timing.connectStart, ttfb: timing.responseStart - timing.requestStart, domReady: timing.domContentLoadedEventEnd - timing.navigationStart, load: timing.loadEventEnd - timing.navigationStart }优化建议:
网络层面:
- 使用HTTP/2或多路复用
- 启用资源压缩(Brotli/Gzip)
- 使用CDN分发静态资源
- 预加载关键资源
渲染层面:
- 减少关键CSS大小
- 延迟非关键JavaScript
- 使用CSS动画代替JavaScript动画
- 避免强制同步布局
缓存策略:
- 设置合适的Cache-Control头
- 使用Service Worker缓存
- 实现资源版本控制
3.2 DOM与BOM的区别
DOM(文档对象模型):
// DOM核心API示例 document.getElementById('app') element.querySelectorAll('.item') node.appendChild(newNode) element.classList.add('active') element.addEventListener('click', handler)BOM(浏览器对象模型):
// BOM主要对象 window.location.href = '/new-page' window.history.pushState({}, '', '/new-url') window.localStorage.setItem('key', 'value') window.navigator.userAgent window.screen.widthDOM与BOM对比:
| 特性 | DOM | BOM |
|---|---|---|
| 标准 | W3C标准 | 无统一标准 |
| 用途 | 操作文档内容 | 操作浏览器窗口 |
| 核心对象 | document | window |
| 事件类型 | click, change等 | load, resize等 |
| 典型API | querySelector, addEventListener | location, history, navigator |
实际应用中的结合使用:
// 根据设备类型设置不同的DOM处理 if (/Mobi|Android/i.test(navigator.userAgent)) { document.body.classList.add('mobile') // 移动端特定处理 } else { // 桌面端处理 } // 使用BOM信息优化DOM操作 const isSlowNetwork = navigator.connection?.effectiveType === '2g' if (isSlowNetwork) { // 减少DOM复杂度 document.querySelectorAll('.lazy').forEach(el => { el.remove() }) }4. 高级编程技巧与设计模式
4.1 使用Promise实现红绿灯
红绿灯问题是考察异步编程能力的经典题目。我们来看几种实现方式:
基础Promise链实现:
function light(color, duration) { return new Promise(resolve => { console.log(`${color}灯亮`) setTimeout(() => { console.log(`${color}灯灭`) resolve() }, duration * 1000) }) } function runCycle() { return light('红', 3) .then(() => light('绿', 2)) .then(() => light('黄', 1)) } function start() { return runCycle().then(start) // 循环执行 } start()async/await实现:
async function trafficLight() { while (true) { await light('红', 3) await light('绿', 2) await light('黄', 1) } }带状态控制的实现:
class TrafficLight { constructor() { this.states = [ { color: '红', duration: 3000 }, { color: '绿', duration: 2000 }, { color: '黄', duration: 1000 } ] this.currentIndex = 0 this.timer = null } start() { this.next() } next() { const state = this.states[this.currentIndex] console.log(`${state.color}灯亮`) this.timer = setTimeout(() => { console.log(`${state.color}灯灭`) this.currentIndex = (this.currentIndex + 1) % this.states.length this.next() }, state.duration) } stop() { clearTimeout(this.timer) } } const light = new TrafficLight() light.start() // 10秒后停止 setTimeout(() => light.stop(), 10000)可视化红绿灯实现:
function createTrafficLight(element) { const colors = ['red', 'yellow', 'green'] let current = 0 function update() { element.querySelectorAll('.light').forEach((light, i) => { light.style.opacity = i === current ? 1 : 0.3 }) } function next() { current = (current + 1) % colors.length update() const durations = [3000, 1000, 2000] setTimeout(next, durations[current]) } // 初始化DOM colors.forEach(color => { const light = document.createElement('div') light.className = `light ${color}` element.appendChild(light) }) update() next() } createTrafficLight(document.getElementById('traffic-light'))4.2 防抖与节流实现
防抖(debounce)和节流(throttle)是解决高频事件处理的经典方案:
防抖实现:
function debounce(fn, delay) { let timer = null return function(...args) { clearTimeout(timer) timer = setTimeout(() => { fn.apply(this, args) }, delay) } } // 使用示例 window.addEventListener('resize', debounce(() => { console.log('窗口大小改变') }, 300))节流实现:
function throttle(fn, interval) { let lastTime = 0 return function(...args) { const now = Date.now() if (now - lastTime >= interval) { fn.apply(this, args) lastTime = now } } } // 使用示例 window.addEventListener('scroll', throttle(() => { console.log('滚动事件') }, 200))增强版实现:
function enhancedThrottle(fn, delay, options = {}) { let lastTime = 0 let timer = null const { leading = true, trailing = true } = options return function(...args) { const now = Date.now() const remaining = delay - (now - lastTime) if (remaining <= 0 && leading) { if (timer) { clearTimeout(timer) timer = null } fn.apply(this, args) lastTime = now } else if (!timer && trailing) { timer = setTimeout(() => { fn.apply(this, args) lastTime = Date.now() timer = null }, remaining) } } } // 使用示例 window.addEventListener('scroll', enhancedThrottle(() => { console.log('优化的滚动处理') }, 200, { leading: true, trailing: true }))实际应用场景:
- 搜索框输入建议(防抖)
- 窗口resize事件处理(防抖)
- 滚动加载更多(节流)
- 按钮连续点击防止重复提交(节流)
- 鼠标移动事件(节流)
5. JavaScript核心概念深入
5.1 new操作符的完整过程
new操作符在JavaScript中用于创建对象实例,其内部执行过程可以分为以下几个步骤:
完整执行流程:
- 创建一个空对象
- 将该对象的原型指向构造函数的prototype属性
- 执行构造函数,并将this绑定到新创建的对象
- 判断构造函数的返回值类型:
- 如果返回对象,则作为new表达式的结果
- 否则返回新创建的对象
手动实现new:
function myNew(constructor, ...args) { // 步骤1:创建新对象并设置原型 const obj = Object.create(constructor.prototype) // 步骤2:执行构造函数 const result = constructor.apply(obj, args) // 步骤3:处理返回值 return result instanceof Object ? result : obj } // 使用示例 function Person(name) { this.name = name } const p = myNew(Person, '张三') console.log(p.name) // 张三特殊情况处理:
// 情况1:构造函数返回基本类型 function Foo() { this.name = 'Foo' return 123 // 被忽略 } console.log(new Foo()) // Foo { name: 'Foo' } // 情况2:构造函数返回对象 function Bar() { this.name = 'Bar' return { custom: 'object' } // 替换 } console.log(new Bar()) // { custom: 'object' } // 情况3:箭头函数不能作为构造函数 const Arrow = () => {} console.log(new Arrow()) // TypeError实际应用技巧:
- 实现单例模式:
function Singleton() { if (Singleton.instance) { return Singleton.instance } this.name = 'Singleton' Singleton.instance = this } const s1 = new Singleton() const s2 = new Singleton() console.log(s1 === s2) // true- 实现对象池:
function createPool(createFn) { const pool = [] return { get() { if (pool.length) { return pool.pop() } return createFn() }, put(obj) { pool.push(obj) } } } const pool = createPool(() => new SomeClass()) const obj = pool.get() // 使用后 pool.put(obj)5.2 ==与===的隐式转换规则
JavaScript的宽松相等(==)会进行类型转换,了解其规则对避免bug至关重要:
转换规则表:
| 类型X | 类型Y | 转换规则 |
|---|---|---|
| null | undefined | 总是true |
| 数字 | 字符串 | 字符串转数字 |
| 布尔值 | 任何 | 布尔值转数字(true=1,false=0) |
| 对象 | 数字/字符串 | 对象通过valueOf/toString转换 |
| 其他组合 | - | 严格相等比较 |
典型示例分析:
null == undefined // true '5' == 5 // true false == 0 // true '' == false // true [] == false // true ([] -> '' -> 0, false -> 0) [] == [] // false (引用比较) {} == {} // false (引用比较)最佳实践建议:
- 绝大多数情况下使用===,避免隐式转换带来的意外行为
- 在明确需要类型转换时使用==,如判断null/undefined:
if (value == null) { // 同时匹配null和undefined }- 对可能为0/false的值进行严格判断:
// 不推荐 if (count == false) { /*...*/ } // 推荐 if (count === 0 || count === false) { /*...*/ }安全比较函数实现:
function safeEqual(a, b) { // 处理null/undefined if (a == null && b == null) return true if (a == null || b == null) return false // 处理数字/字符串 if (typeof a === 'number' || typeof b === 'number') { return +a === +b } // 处理布尔值 if (typeof a === 'boolean' || typeof b === 'boolean') { return !!a === !!b } // 其他情况严格相等 return a === b }6. 现代JavaScript特性与实践
6.1 Map与Set的深入应用
ES6引入的Map和Set提供了更强大的集合操作能力:
Map核心用法:
const map = new Map() // 基本操作 map.set('name', '张三') map.set({ id: 1 }, '对象作为键') console.log(map.get('name')) // 张三 console.log(map.size) // 2 // 迭代方法 for (const [key, value] of map) { console.log(key, value) } // 与Object对比优势 const objKey = { id: 1 } map.set(objKey, 'value1') objKey.id = 2 console.log(map.get(objKey)) // 'value1' (仍能获取)Set核心用法:
const set = new Set() // 基本操作 set.add(1) set.add(2) set.add(2) // 重复值被忽略 console.log(set.size) // 2 // 集合运算 const setA = new Set([1, 2, 3]) const setB = new Set([2, 3, 4]) // 并集 const union = new Set([...setA, ...setB]) // {1, 2, 3, 4} // 交集 const intersection = new Set( [...setA].filter(x => setB.has(x)) // {2, 3} ) // 差集 const difference = new Set( [...setA].filter(x => !setB.has(x)) // {1} )实际应用场景:
- 数据去重:
const arr = [1, 2, 2, 3, 4, 4] const unique = [...new Set(arr)] // [1, 2, 3, 4]- 对象集合管理:
const userMap = new Map() function addUser(user) { userMap.set(user.id, user) } function getUser(id) { return userMap.get(id) }- 最近访问记录:
const recentItems = new Set() function addRecent(item) { if (recentItems.has(item)) { recentItems.delete(item) } recentItems.add(item) // 限制大小 if (recentItems.size > 10) { const oldest = recentItems.values().next().value recentItems.delete(oldest) } }6.2 模块化开发实践
现代JavaScript开发离不开模块化,我们来看几种模块化方案:
ES Modules (ESM):
// math.js export function add(a, b) { return a + b } // app.js import { add } from './math.js' console.log(add(1, 2))CommonJS (Node.js):
// math.js exports.add = function(a, b) { return a + b } // app.js const { add } = require('./math') console.log(add(1, 2))UMD (通用模块定义):
(function(root, factory) { if (typeof define === 'function' && define.amd) { // AMD define(['exports'], factory) } else if (typeof exports === 'object') { // CommonJS factory(exports) } else { // 浏览器全局 factory(root.myModule = {}) } }(this, function(exports) { exports.add = function(a, b) { return a + b } }))模块化最佳实践:
- 使用ES Modules作为首选方案
- 对于Node.js环境,可以使用条件导出:
// package.json { "exports": { "import": "./esm/index.js", "require": "./cjs/index.js" } }- 对于浏览器兼容性考虑,使用构建工具打包
- 合理拆分模块,保持单一职责原则
动态导入实践:
// 静态导入 import { add } from './math' // 动态导入 async function calculate() { const { add } = await import('./math') console.log(add(1, 2)) } // 基于条件的动态导入 if (featureFlag) { import('./analytics').then(analytics => { analytics.init() }) }7. 算法与数据结构实战
7.1 最大子序和问题
最大子序和(Kadane算法)是经典的动态规划问题:
问题描述: 给定一个整数数组nums,找到一个具有最大和的连续子数组(至少包含一个元素),返回其最大和。
动态规划解法:
function maxSubArray(nums) { let maxSum = nums[0] let currentSum = nums[0] for (let i = 1; i < nums.length; i++) { currentSum = Math.max(nums[i], currentSum + nums[i]) maxSum = Math.max(maxSum, currentSum) } return maxSum } // 示例 console.log(maxSubArray([-2,1,-3,4,-1,2,1,-5,4])) // 6 ([4,-1,2,1])返回子数组本身的实现:
function maxSubArrayWithIndices(nums) { let maxSum = nums[0] let currentSum = nums[0] let start = 0, end = 0 let tempStart = 0 for (let i = 1; i < nums.length; i++) { if (nums[i] > currentSum + nums[i]) { currentSum = nums[i] tempStart = i } else { currentSum += nums[i] } if (currentSum > maxSum) { maxSum = currentSum start = tempStart end = i } } return { sum: maxSum, subarray: nums.slice(start, end + 1) } }分治法解法:
function maxSubArrayDivide(nums, left = 0, right = nums.length - 1) { if (left === right) return nums[left] const mid = Math.floor((left + right) / 2) const leftMax = maxSubArrayDivide(nums, left, mid) const rightMax = maxSubArrayDivide(nums, mid + 1, right) const crossMax = maxCrossingSum(nums, left, mid, right) return Math.max(leftMax, rightMax, crossMax) } function maxCrossingSum(nums, left, mid, right) { let leftSum = -Infinity let sum = 0 for (let i = mid; i >= left; i--) { sum += nums[i] leftSum = Math.max(leftSum, sum) } let rightSum = -Infinity sum = 0 for (let i = mid + 1; i <= right; i++) { sum += nums[i] rightSum = Math.max(rightSum, sum) } return leftSum + rightSum }性能对比:
| 方法 | 时间复杂度 | 空间复杂度 | 适用场景 |
|---|---|---|---|
| 动态规划 | O(n) | O(1) | 最佳通用解法 |
| 分治法 | O(nlogn) | O(logn) | 学术研究 |
| 暴力法 | O(n^2) | O(1) | 不推荐 |
7.2 二叉树实现与遍历
JavaScript中实现二叉树及其常见操作:
基础实现:
class TreeNode { constructor(value) { this.value = value this.left = null this.right = null } } class BinaryTree { constructor() { this.root = null } // 插入节点 insert(value) { const newNode = new TreeNode(value) if (!this.root) { this.root = newNode return } let current = this.root while (true) { if (value < current.value) { if (!current.left) { current.left = newNode break } current = current.left } else { if (!current.right) { current.right = newNode break } current = current.right } } } // 查找节点 contains(value) { let current = this.root while (current) { if (value === current.value) return true current = value < current.value ? current.left : current.right } return false } }遍历算法:
// 前序遍历 (根-左-右) function preOrder(node, result = []) { if (node) { result.push(node.value) preOrder(node.left, result) preOrder(node.right, result) } return result } // 中序遍历 (左-根-右) function inOrder(node, result = []) { if (node) { inOrder(node.left, result) result.push(node.value) inOrder(node.right, result) } return result } // 后序遍历 (左-右-根) function postOrder(node, result = []) { if (node) { postOrder(node.left, result) postOrder(node.right, result) result.push(node.value) } return result } // 层序遍历 function levelOrder(root) { const result = [] if (!root) return result const queue = [root] while (queue.length) { const levelSize = queue.length const currentLevel = [] for (let i = 0; i < levelSize; i++) { const node = queue.shift() currentLevel.push(node.value) if (node.left) queue.push(node.left) if (node.right) queue.push(node.right) } result.push(currentLevel) } return result }实际应用:
- 二叉搜索树验证:
function isValidBST(root, min = -Infinity, max = Infinity) { if (!root) return true if (root.value <= min || root.value >= max) return false return isValidBST(root.left, min, root.value) && isValidBST(root.right, root.value, max) }- 查找第k小的元素:
function kthSmallest(root, k) { const stack = [] let current = root let count = 0 while (current || stack.length) { while (current) { stack.push(current) current = current.left } current = stack.pop() count++ if (count === k) return current.value current = current.right } return null }- 序列化与反序列化:
function serialize(root) { if (!root) return 'null' const left = serialize(root.left) const right = serialize(root.right) return `${root.value},${left},${right}` } function deserialize(data