公司动态

2026大厂JavaScript面试题深度解析与实战

📅 2026/8/23 1:37:46
2026大厂JavaScript面试题深度解析与实战
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 idparent button idchild点击我/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标签的位置和属性对页面性能有重大影响。我们通过实验来分析位置对比实验!-- 案例1head中的阻塞脚本 -- head script srcblocking.js/script /head !-- 案例2body底部的非阻塞脚本 -- body !-- 页面内容 -- script srcnon-blocking.js/script /body性能指标对比指标head脚本body底部脚本DOMContentLoaded延迟提前首屏渲染延迟提前页面完全加载基本相同基本相同现代最佳实践head !-- 预加载关键资源 -- link relpreload hrefcritical.js asscript !-- 异步加载非关键脚本 -- script srcanalytics.js async/script !-- defer脚本保持执行顺序 -- script srcvendor.js defer/script script srcapp.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 /bodyscript属性详解async异步加载不保证执行顺序defer异步加载保持执行顺序moduleES6模块默认defer行为nomodule不支持模块时的回退方案3. 浏览器原理与性能优化3.1 页面渲染全流程从输入URL到页面显示是一个复杂的过程我们可以将其分解为以下几个阶段关键阶段时间线导航阶段DNS查询50-200msTCP连接100-300msTLS握手HTTPS100-400msHTTP请求/响应取决于内容大小渲染阶段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对比特性DOMBOM标准W3C标准无统一标准用途操作文档内容操作浏览器窗口核心对象documentwindow事件类型click, change等load, resize等典型APIquerySelector, addEventListenerlocation, 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表达式的结果否则返回新创建的对象手动实现newfunction 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转换规则nullundefined总是true数字字符串字符串转数字布尔值任何布尔值转数字(true1,false0)对象数字/字符串对象通过valueOf/toString转换其他组合-严格相等比较典型示例分析null undefined // true 5 5 // true false 0 // true false // true [] false // true ([] - - 0, false - 0) [] [] // false (引用比较) {} {} // false (引用比较)最佳实践建议绝大多数情况下使用避免隐式转换带来的意外行为在明确需要类型转换时使用如判断null/undefinedif (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