JavaScript面试题手册

梳理高频技术问题,帮助你按主题复习和查漏补缺。

前端阅读 482026年5月27日 01:17

== 和 === 的区别是什么?什么情况下用 == 相等?

=== 是严格相等:类型不同直接 false,类型相同才比较值。== 是宽松相等:类型不同时做类型转换(强制类型转换),然后再比较。大多数场景用 ===。但 == 也有实际用途:if (x == null) —— 等价于 x === null || x === undefined,很简洁你明确知道两端类型相同时(和 === 没区别)处理字符串和数字比较时('5' == 5 是 true),比如从 input 里读出来的值// == 的经典坑'' == 0; // true[] == 0; // true[] == ''; // true[] == ![]; // true (?!)null == undefined; // trueNaN == NaN; // false (即使 === 也是 false)追问Object.is 和 === 有什么区别?两个不同:Object.is(NaN, NaN) 是 true(=== 是 false),Object.is(0, -0) 是 false(=== 是 true)。其他行为和 === 完全一致。if (x == null) 比 if (x === null || x === undefined) 有什么风险吗?几乎没有。== null 只在值为 null 或 undefined 时为 true,对 0、''、false 都是 false。这是 == 唯一一个业界认可的"干净"用法。
前端阅读 312026年5月27日 01:13

前端模块规范有哪些?模块如何异步加载?

前端模块规范演进:IIFE(立即执行函数):闭包隔离作用域,通过全局变量通信。最原始的方式CommonJS:require/module.exports,同步加载,Node.js 原生。不适合浏览器(同步加载会阻塞)AMD:define/require,异步加载,RequireJS 实现,为浏览器设计CMD:SeaJS 规范,和 AMD 类似但依赖就近声明(用到时才 require)UMD:兼容 AMD + CommonJS + 全局变量的缝合方案,库开发者常用ESModule:import/export,静态声明,原生支持,统一标准异步加载:ESModule 用 import() 动态导入,返回 Promise。AMD 用 require([deps], callback) 本身就是异步。CommonJS 支持 import() 但 require 本身是同步的。追问import() 和顶层 import 有什么区别?顶层 import 是静态声明,必须在模块顶层,编译时确定依赖关系。import() 是动态函数调用,返回 Promise,可以在任何地方调用,运行时才加载模块。后者用于代码分割和懒加载。为什么浏览器不支持 CommonJS 的 require?require 是同步调用——读取文件、编译、执行,然后返回。在浏览器里,模块要从网络下载,同步阻塞意味着页面卡死直到下载完成。AMD 和 ESModule 都支持异步加载,不影响页面渲染。库作者应该发布什么格式?ESM + CJS 双格式——package.json 里 main 指向 CJS(兼容老工具),module 或 exports 指向 ESM(支持 Tree-Shaking)。现在的主流方案是 exports 字段同时声明两种格式的入口。
前端阅读 392026年5月27日 01:13

Webpack 有哪些优化手段?

分两类:构建速度优化和生产产物优化。构建提速:cache: { type: 'filesystem' } 开启文件系统缓存缩小 loader 范围(include/exclude)thread-loader 或 Webpack 5 的 parallel 并行压缩resolve.extensions 只保留必要的(如 ['.ts', '.tsx', '.js'])module.noParse 跳过已打包库的解析(如 lodash)用 esbuild/swc 替代 Babel(如 swc-loader)产物优化:代码分割:splitChunks(提取公共依赖)、动态 import()(路由懒加载)Tree-Shaking:生产模式自动开启,但避免使用有副作用的写法压缩:terser-webpack-plugin(JS)、css-minimizer-webpack-plugin(CSS)图片内联限制(小于 8KB 的转 base64)externals 排除不需要打包的库(CDN 引入)追问splitChunks 和动态 import 有什么区别?splitChunks 是 webpack 自动提取公共依赖(如 node_modules 中的库),是配置层面的优化。动态 import() 是代码层面的代码分割,由开发者决定哪些模块懒加载。Tree-Shaking 为什么需要 ESM?ESModule 的 import/export 是静态声明,编译时能分析依赖关系。CommonJS 的 require 是运行时调用,你可以在 if 里 require,打包工具无法判断哪些代码会被用到。webpack 的 cache 缓存了什么?为什么能加快构建?缓存了 loader 结果和模块编译产物。第一次构建后,改一个文件只重新编译那个文件和依赖它的文件,其他模块直接用缓存。filesystem 缓存把结果写到 node_modules/.cache/webpack 目录,二次构建可减少 60-80% 时间。
服务端阅读 982026年5月27日 01:12

Node.js 如何开启多进程?进程之间如何通讯?

Node.js 用 child_process 模块创建子进程,用 cluster 模块做多核利用:child_process:spawn(command, args):启动一个新进程,返回流(适合长时间运行、大量输出的进程)exec(command, callback):启动 shell 执行命令,缓存输出后回调(适合短命令)fork(modulePath):特殊 spawn,创建 Node.js 子进程,自带 IPC 通道cluster:基于 fork 封装,能创建多个共享同一端口的 worker 进程(常见于 HTTP 服务利用多核)。进程通讯:fork 创建的父子进程间有 IPC 通道,用 process.send(msg) 和 process.on('message') 通信。底层实现:libuv 管道(pipe)。追问cluster 怎么实现多进程共享端口?主进程监听端口,将接收到的连接通过 Round-Robin 分发给 worker 进程。worker 不直接监听端口,而是接收主进程分配的连接句柄。Linux 上也可用 SO_REUSEPORT 内核级别的分发。fork 和 spawn 的区别?fork 是 spawn 的特殊版——专门 fork Node.js 进程,自动建立 IPC 通道。spawn 启动任何命令,流式处理输出,适合与外部程序交互。PM2 的 cluster 模式和 fork 模式有什么区别?cluster:PM2 用 Node.js cluster 模块,多实例共享端口,自动负载均衡fork:PM2 只是用 child_process.fork 启动多个实例,需要不同端口或用 Nginx 做反向代理
前端阅读 422026年5月27日 01:09

JavaScript 如何使用 setTimeout 模拟实现 setInterval?

直接用 setInterval 有个问题:如果回调执行时间超过了间隔时间,回调会堆积。用 setTimeout 递归模拟能解决这个——每次回调执行完再设置下一次定时器。function mySetInterval(fn, delay) { let timer = null; function loop() { fn(); timer = setTimeout(loop, delay); } timer = setTimeout(loop, delay); return () => clearTimeout(timer); // 返回清除函数}const cancel = mySetInterval(() => console.log('tick'), 1000);// cancel(); 需要停止时调用相比原生 setInterval:不会出现回调堆积(回调执行完才开始计时下一次),但如果回调本身执行时间很长,间隔会不准。追问setInterval 回调堆积是什么情况?设 setInterval(fn, 100),但 fn 执行需要 150ms。第一次回调执行期间,100ms 到了,第二个回调被放进任务队列;150ms 到了第一个回调还没结束,第三个又被放进队列。结果就是回调不断排队。这种模拟方式的间隔精确吗?仍然不精确——setTimeout 只保证"至少延迟 delay",收到事件循环和主线程影响。但回调不会堆积,每次都是执行完再计时,比 setInterval 更可控。如果需要真正精确的定时循环怎么办?requestAnimationFrame + performance.now() 做时间判断(适合动画循环)Web Worker 里跑定时器(独立线程,不受主线程影响)如果记录已执行次数,用实际时间戳判断是否需要补执行
前端阅读 362026年5月27日 01:08

如何实现 JavaScript 的 bind 方法?

bind 做了两件事:1) 绑定 this;2) 预设部分参数。手写版本:Function.prototype.myBind = function(context, ...args) { const fn = this; return function bound(...newArgs) { // 如果通过 new 调用,this 指向实例而非 context return fn.apply( this instanceof bound ? this : context, [...args, ...newArgs] ); };};关键点:this instanceof bound 判断是为了兼容 new 情况。当 new bound() 时,this 指向新创建的实例,此时不应替换 this。追问bind 返回的函数再用 new 调用会发生什么?new 的优先级高于 bind。new 创建的新对象会作为 this,bind 指定的 this 被忽略。但 bind 预设的参数仍然生效。call、apply、bind 三者区别?call(thisArg, arg1, arg2, ...):逐个传参,立即执行apply(thisArg, [arg1, arg2]):数组传参,立即执行bind(thisArg, arg1, arg2):预设 this 和参数,返回新函数,不立即执行多次 bind 会叠加吗?不会。bind 返回的函数内部 this 已经固定了,再次 bind 不会改变内部函数的 this。但预设参数会叠加。
前端阅读 522026年5月27日 01:08

什么是柯里化函数?JavaScript 中有哪些使用场景?

柯里化就是把一个接受多个参数的函数,转换成一系列每次只接受一个参数的函数调用。// 普通函数function add(a, b, c) { return a + b + c; }add(1, 2, 3); // 6// 柯里化function curryAdd(a) { return function(b) { return function(c) { return a + b + c; }; };}curryAdd(1)(2)(3); // 6核心价值是参数复用和延迟执行。比如你有一个 log(level, message) 函数,柯里化后可以创建 errorLog = curry(log)('error'),之后只需要 errorLog('消息')。实际场景:bind 方法本质就是柯里化(预设 this + 部分参数)Redux middleware 的 store => next => action => {} 链函数式编程的工具库(Ramda 等)追问柯里化和偏函数有什么区别?柯里化把多参数函数变成一元函数链(每次只接受一个参数)。偏函数是预先填充部分参数,返回的函数仍可接受多个参数。bind 就是偏函数应用,不是严格柯里化。能手写一个通用的 curry 函数吗?function curry(fn) { return function curried(...args) { if (args.length >= fn.length) return fn(...args); return (...next) => curried(...args, ...next); };}柯里化在 React 中有什么应用?最常见的是事件处理器参数绑定:onClick={handleClick.bind(null, id)} 或者用高阶函数 onClick={() => handleClick(id)}。后者本质也是一种柯里化——延迟了函数的执行。
前端阅读 352026年5月27日 01:08

JavaScript 如何实现自定义事件?

浏览器原生支持自定义事件,通过 CustomEvent 构造函数:// 创建事件const event = new CustomEvent('my-event', { detail: { name: 'hello', time: Date.now() }, // 传递数据 bubbles: true, // 是否冒泡 cancelable: true});// 监听el.addEventListener('my-event', (e) => { console.log(e.detail); // { name: 'hello', time: ... }});// 触发el.dispatchEvent(event);detail 是传数据的位置,任何 JS 值都能放。和 Event 构造函数的区别就是多了这个 detail 字段。追问自定义事件和发布订阅有什么区别?自定义事件绑定在 DOM 元素上,有冒泡/捕获机制、事件委托、stopPropagation 等 DOM 事件系统的完整能力。发布订阅(EventEmitter)是纯 JS 模式的,没有 DOM 树概念。需要跨层级通信、事件委托时用自定义事件;需要纯数据流、完全与 DOM 无关时用发布订阅。不用框架(Vue/React)怎么用自定义事件做组件通信?父组件给子组件传一个 DOM 元素引用,子组件在这个元素上 dispatchEvent(new CustomEvent('change', { detail: value })),父组件监听这个元素的 change 事件。这就是 Web Components 的通信基础。自定义事件可以跨 Shadow DOM 吗?可以。composed: true 的 CustomEvent 会穿透 Shadow DOM 边界,但不设置这个属性事件就被封闭在 Shadow DOM 内部。
前端阅读 332026年5月27日 01:08

JavaScript 的继承方式有哪些?

JS 的继承方式经历了几代演变,面试中能说出这几种就够了:原型链继承:Child.prototype = new Parent()。问题是所有子实例共享父实例的引用类型属性。构造函数继承:在 Child 里 Parent.call(this)。解决了属性共享问题,但无法继承父类原型上的方法。组合继承:上面两种结合——属性用 Parent.call(this),方法用原型链。经典方案,但 Parent 被调用了两次。寄生组合继承:组合继承的优化版,通过 Object.create(Parent.prototype) 创建中间对象,只调用一次 Parent。ES6 class extends 的 Babel 编译结果就是这种。ES6 class extends:语法糖,底层还是原型链 + 寄生组合继承,但加了 super 和静态方法的继承。追问ES6 class 和寄生组合继承有什么本质区别?class 除了语法更清晰外,还做了几点:1) 内置了 new.target 检查(不用 new 调用会报错);2) 类方法不可枚举;3) extends 同时继承了静态属性和原型方法;4) super 作为关键字比 Parent.call(this) 更安全。寄生组合继承为什么是最优的?只调用一次父类构造函数,不产生冗余属性。原型链保持干净(子类原型上只有通过 Object.create 创建的对象,不包含父类实例属性)。Object.create 和 new 有什么区别?Object.create(proto) 创建一个新对象,把 proto 设为新对象的原型,不调用构造函数。new 会调用构造函数并执行其中的逻辑。寄生组合继承用 Object.create 就是为了避免多调用一次 Parent。
前端阅读 672026年5月27日 01:05

localStorage 对象有哪些 API?

localStorage 是浏览器提供的持久化键值存储,数据不随页面关闭而清除。API 一共就 5 个方法和 1 个属性:setItem(key, value):存数据。key 和 value 都是字符串。存对象需要用 JSON.stringifygetItem(key):取数据。key 不存在返回 nullremoveItem(key):删单个 keyclear():清空所有数据(注意:清的是当前域名下的所有 localStorage)key(index):按索引获取 key 名,用于遍历length:只读属性,返回存储的条目数存储对象的正确姿势:localStorage.setItem('user', JSON.stringify({ name: 'John', age: 30 }));const user = JSON.parse(localStorage.getItem('user'));追问localStorage 和 sessionStorage 有什么区别?生命周期:localStorage 永久存储,sessionStorage 关闭标签页就清除作用域:两者都是按域名隔离,但 sessionStorage 额外按标签页隔离API 完全一致localStorage 有什么限制?容量 5-10MB(不同浏览器不同)只能存字符串同步 API,大容量读写会阻塞主线程不能存敏感信息(明文存储,XSS 可直接读取)不支持过期时间,需要自己实现Cookie 和 localStorage 怎么选?需要随 HTTP 请求自动发送(如身份认证)→ Cookie纯前端存储、不随请求发送 → localStorage需要服务端可读 → Cookie(设置 HttpOnly 防止 XSS 读取)容量超过 4KB → localStorage(Cookie 限制 4KB)