20道JavaScript相关前端面试题及答案
JavaScript 相关面试题及答案
-
JavaScript 的数据类型有哪些?如何区分基本数据类型和引用数据类型?
JavaScript 的数据类型分为两类:
-
基本数据类型(值类型):
Number
、String
、Boolean
、Null
、Undefined
、Symbol
(ES6)、BigInt
(ES11)。 -
引用数据类型:
Object
(包括Array
、Function
、Date
、RegExp
等)。区分方式:
① 存储方式:基本类型存储值本身,引用类型存储地址(指向堆内存中的对象);
② 赋值行为:基本类型赋值是值拷贝,引用类型赋值是地址拷贝(修改新变量会影响原变量)。
null
和undefined
的区别是什么?分别在什么场景下使用?
-
undefined
:表示 “未定义”,变量声明后未赋值、函数无返回值、访问不存在的属性时返回此值。 -
null
:表示 “空值”,主动赋值给变量,表示该变量有意为空(如释放对象引用)。区别:
① 类型不同:typeof undefined
返回"undefined"
,typeof null
返回"object"
;
② 转换为数字:Number(undefined)
为NaN
,Number(null)
为0
。
-
什么是变量提升?函数提升和变量提升有何区别?
变量提升指 JavaScript 引擎在执行代码前,将变量和函数声明 “提升” 到当前作用域顶部的行为。
区别:
① 函数提升:函数声明整体被提升(可在声明前调用);
② 变量提升:仅声明被提升,赋值留在原地(声明前访问返回undefined
)。示例:
console.log(a); // undefined(变量提升)var a = 1;fn(); // "hello"(函数提升)function fn() { console.log("hello"); }
this
关键字在不同场景下的指向是什么?
-
全局作用域 / 普通函数:指向全局对象(浏览器为
window
,Node.js 为global
),严格模式下为undefined
。 -
对象方法:指向调用方法的对象(如
obj.fn()
中this
指向obj
)。 -
构造函数(
new
调用):指向新创建的实例对象。 -
事件绑定:指向触发事件的元素(如
btn.onclick
中this
指向btn
)。 -
箭头函数:无自身
this
,继承外层作用域的this
(固定不变)。 -
call
/apply
/bind
:手动指定this
指向(第一个参数)。
-
什么是闭包?闭包有哪些应用场景和缺点?
闭包指嵌套函数中,内部函数引用外部函数的变量,且内部函数被外部访问,导致外部变量不被销毁的现象。
应用场景:
① 实现私有变量(如计数器:function createCounter() { let n = 0; return () => ++n; }
);
② 保存变量状态(如防抖节流的定时器);
③ 模块化(隔离作用域)。缺点:
① 变量长期驻留内存,可能导致内存泄漏;
② 过度使用会增加内存消耗。 -
原型和原型链的概念是什么?它们在 JavaScript 中的作用是什么?
-
原型:每个函数都有
prototype
属性(原型对象),实例对象通过__proto__
(隐式原型)指向该对象,用于共享方法和属性。 -
原型链:实例访问属性 / 方法时,若自身不存在,会沿
__proto__
向上查找,直到null
,形成的链条即原型链。作用:
① 实现继承(子实例共享父原型的属性方法);
② 节省内存(方法定义在原型上,而非每个实例)。
- 什么是事件冒泡和事件捕获?如何阻止事件冒泡?
-
事件冒泡:事件从触发元素(目标)向上传播到父元素、根元素(由内向外)。
-
事件捕获:事件从根元素向下传播到触发元素(由外向内)。
阻止冒泡:
① 标准浏览器:event.stopPropagation()
;
② IE 低版本:event.cancelBubble = true
。注意:
event.stopImmediatePropagation()
还会阻止当前元素后续事件监听器执行。
==
和===
的区别是什么?请举例说明。
-
==
:抽象相等,会自动转换类型后比较(如1 == "1"
返回true
)。 -
===
:严格相等,不转换类型,类型和值均相同才返回true
(如1 === "1"
返回false
)。特殊情况:
null == undefined
为true
,null === undefined
为false
;NaN == NaN
为false
(需用isNaN()
判断)。
- 什么是防抖和节流?它们的应用场景和实现方式是什么?
-
防抖:触发事件后延迟 n 秒执行,若 n 秒内再次触发则重新计时(如搜索框输入联想)。
实现:
function debounce(fn, delay) {let timer = null;return (...args) => {clearTimeout(timer);timer = setTimeout(() => fn.apply(this, args), delay);};}
-
节流:触发事件后,每隔 n 秒最多执行一次(如滚动加载)。
实现(时间戳版):
function throttle(fn, interval) {let lastTime = 0;return (...args) => {const now = Date.now();if (now - lastTime >= interval) {fn.apply(this, args);lastTime = now;}};}
- JavaScript 中如何实现继承?请列举常见方式。
-
原型链继承:
Child.prototype = new Parent()
,缺点是共享父类引用属性。 -
构造函数继承:
Parent.call(this)
,缺点是无法继承父类原型方法。 -
组合继承:原型链 + 构造函数(
Parent.call(this)
+Child.prototype = new Parent()
),较常用但父构造函数调用两次。 -
寄生组合继承:
Child.prototype = Object.create(Parent.prototype)
,优化组合继承,避免重复调用。 -
ES6
class
继承:class Child extends Parent { constructor() { super() } }
,语法糖,本质是原型链。
-
什么是 Promise?它解决了什么问题?常用方法有哪些?
Promise 是处理异步操作的对象,有
pending
(进行中)、fulfilled
(成功)、rejected
(失败)三种状态,状态一旦改变不可逆转。解决的问题:
① 回调地狱(嵌套回调导致代码混乱);
② 异步操作的同步化表达(链式调用)。常用方法:
①then()
:处理成功 / 失败;
②catch()
:捕获错误;
③finally()
:无论成功失败都执行;
④Promise.all()
:等待所有 Promise 成功;
⑤Promise.race()
:取第一个状态改变的 Promise。 -
箭头函数和普通函数的区别是什么?
-
this
指向:箭头函数无自身this
,继承外层this
;普通函数this
随调用方式变化。 -
构造函数:箭头函数不能用
new
调用(无prototype
);普通函数可以。 -
arguments
:箭头函数无arguments
对象(可用剩余参数...args
);普通函数有。 -
写法:箭头函数更简洁(单参数可省括号,单语句可省大括号和
return
)。 -
适用场景:箭头函数适合回调(如
setTimeout
、数组方法),普通函数适合需要动态this
的场景(如对象方法)。
- 什么是深拷贝和浅拷贝?如何实现深拷贝?
-
浅拷贝:仅复制对象的表层属性,若属性为引用类型,则复制地址(修改新对象会影响原对象)。
实现:
Object.assign()
、扩展运算符{...obj}
、数组slice()
/concat()
。 -
深拷贝:复制对象的所有层级,新对象与原对象完全独立(修改互不影响)。
实现:
①JSON.parse(JSON.stringify(obj))
(缺点:不支持函数、循环引用);
② 递归拷贝:
function deepClone(obj, map = new WeakMap()) {if (obj === null || typeof obj !== 'object') return obj;if (map.has(obj)) return map.get(obj); // 解决循环引用const clone = Array.isArray(obj) ? \[] : {};map.set(obj, clone);for (const key in obj) {if (obj.hasOwnProperty(key)) {clone\[key] = deepClone(obj\[key], map);}}return clone;}
- 数组有哪些常用方法?请分类说明(如增删、遍历、转换等)。
-
增删:
push()
(末尾增)、pop()
(末尾删)、unshift()
(开头增)、shift()
(开头删)、splice(index, deleteCount, ...add)
(增删改)。 -
遍历:
forEach()
、map()
(返回新数组)、filter()
(过滤)、find()
(找第一个符合条件元素)、some()
/every()
(判断)、reduce()
(累加)。 -
转换:
join()
(转字符串)、split()
(字符串转数组,数组无此方法)、toString()
。 -
其他:
slice()
(截取)、sort()
(排序)、reverse()
(反转)、concat()
(合并)。
-
什么是异步编程?JavaScript 中实现异步的方式有哪些?
异步编程指代码不按顺序执行,耗时操作(如网络请求)不阻塞后续代码,完成后通过回调等方式通知执行。
实现方式:
① 回调函数(setTimeout
、事件监听);
② Promise(链式调用);
③ Generator 函数(yield
暂停 / 恢复);
④ async/await(Promise 语法糖,更接近同步);
⑤ 发布 - 订阅模式(事件总线)。 -
async/await
的工作原理是什么?它和 Promise 有什么关系?async/await
是 ES2017 新增的异步语法,基于 Promise 实现,使异步代码更像同步代码。原理:
async
函数返回 Promise 对象,await
后接 Promise,会暂停函数执行,等待 Promise 状态改变后继续。关系:
①await
只能在async
函数中使用;
②await
会自动处理 Promise 的成功结果,失败需用try/catch
捕获;
③ 本质是 Generator 函数和 Promise 的封装,简化异步流程。 -
什么是模块化?ES6 模块和 CommonJS 模块的区别是什么?
模块化指将代码拆分为独立文件,通过导出 / 导入实现复用和隔离。
区别:
-
加载时机:ES6 模块是编译时加载(静态分析,支持 tree-shaking);CommonJS 是运行时加载(动态)。
-
输出:ES6 模块输出值的引用(只读);CommonJS 输出值的拷贝。
-
语法:ES6 用
import
/export
;CommonJS 用require()
/module.exports
。 -
环境:ES6 模块浏览器和 Node.js(需配置)支持;CommonJS 主要用于 Node.js。
Map
和Set
有什么特点?它们和普通对象、数组有何区别?
-
Map
:键值对集合,键可以是任意类型(对象、基本类型),键唯一,按插入顺序遍历。与对象区别:对象键只能是字符串 / Symbol,Map 键类型更灵活;Map 有
size
属性,对象需手动计算长度。 -
Set
:值的集合,值唯一,按插入顺序遍历,无索引。与数组区别:数组允许重复值,Set 值唯一;Set 查找值用
has()
,数组用indexOf
/includes
。
- 如何判断一个变量是否为数组?有哪些方法?
-
Array.isArray(arr)
:最可靠,返回布尔值(推荐)。 -
arr instanceof Array
:判断是否为 Array 构造函数的实例(可能受 iframe 影响)。 -
Object.prototype.toString.call(arr) === "[object Array]"
:准确,可判断多种类型。 -
arr.constructor === Array
:不可靠(constructor
可被修改)。
-
什么是事件循环(Event Loop)?简述其执行过程。
事件循环是 JavaScript 处理异步操作的机制,流程如下:
-
同步代码进入执行栈,立即执行。
-
异步任务(宏任务 / 微任务)完成后,分别放入宏任务队列和微任务队列。
-
执行栈为空时,先执行所有微任务(如
Promise.then
)。 -
微任务执行完,执行一个宏任务(如
setTimeout
回调),并触发 UI 渲染。 -
重复步骤 3-4,形成循环。
宏任务:
setTimeout
、setInterval
、I/O、UI 渲染等。微任务:
Promise.then/catch/finally
、queueMicrotask
、process.nextTick
(Node.js)。
-