前端 JavaScript 面试题大全(含答案及解析)
前端 JavaScript 面试题大全(含答案及解析)
一、JavaScript 基础与核心概念
1.1 变量与作用域
题目1:var、let、const 的区别
答案:
// 1. 变量提升
console.log(a); // undefined
var a = 1;
console.log(b); // ReferenceError
let b = 2;// 2. 重复声明
var c = 1;
var c = 2; // 允许let d = 1;
let d = 2; // SyntaxError// 3. 块级作用域
{var e = 1;let f = 2;
}
console.log(e); // 1
console.log(f); // ReferenceError// 4. 暂时性死区
let g = 1;
{console.log(g); // ReferenceErrorlet g = 2;
}// 5. const 必须初始化且不能重新赋值
const h = 1;
h = 2; // TypeError
解析:
- var:函数作用域,存在变量提升,可重复声明
- let:块级作用域,不存在变量提升,不可重复声明,存在暂时性死区
- const:块级作用域,必须初始化,不可重新赋值(但可修改对象属性)
题目2:闭包及其应用场景
答案:
// 闭包示例:函数能够记住并访问所在的词法作用域
function createCounter() {let count = 0;return function() {count++;return count;};
}const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2// 应用场景1:模块模式
const calculator = (function() {let result = 0;return {add: function(x) { result += x; },getResult: function() { return result; }};
})();// 应用场景2:私有变量
function Person(name) {const privateName = name;this.getName = function() {return privateName;};
}
解析:
- 闭包定义:函数能够访问其声明时的词法作用域,即使函数在其它地方执行
- 内存泄漏风险:不当使用闭包可能导致内存无法释放
- 实际应用:模块化、私有变量、柯里化、防抖节流等
1.2 数据类型与类型转换
题目3:== 与 === 的区别
答案:
// == 宽松相等(类型转换)
console.log(1 == '1'); // true
console.log(true == 1); // true
console.log(null == undefined); // true
console.log('' == 0); // true// === 严格相等(无类型转换)
console.log(1 === '1'); // false
console.log(true === 1); // false
console.log(null === undefined); // false
console.log('' === 0); // false// 特殊情况
console.log(NaN === NaN); // false
console.log(Object.is(NaN, NaN)); // true
解析:
- == 会进行类型转换,遵循复杂的转换规则
- === 不会进行类型转换,类型不同直接返回 false
- 最佳实践:大多数情况下使用 ===,避免隐式类型转换带来的问题
题目4:数据类型检测方法
答案:
// typeof - 基本类型检测
console.log(typeof 123); // "number"
console.log(typeof 'hello'); // "string"
console.log(typeof true); // "boolean"
console.log(typeof undefined); // "undefined"
console.log(typeof null); // "object" (历史遗留问题)
console.log(typeof []); // "object"
console.log(typeof {}); // "object"
console.log(typeof function(){});// "function"// instanceof - 对象类型检测
console.log([] instanceof Array); // true
console.log({} instanceof Object); // true
console.log(function(){} instanceof Function); // true// Array.isArray - 数组检测
console.log(Array.isArray([])); // true
console.log(Array.isArray({})); // false// Object.prototype.toString - 精确类型检测
console.log(Object.prototype.toString.call(123)); // "[object Number]"
console.log(Object.prototype.toString.call('hello')); // "[object String]"
console.log(Object.prototype.toString.call(null)); // "[object Null]"
console.log(Object.prototype.toString.call(undefined)); // "[object Undefined]"
console.log(Object.prototype.toString.call([])); // "[object Array]"
二、异步编程
2.1 Promise 相关
题目5:Promise 的基本使用
答案:
// Promise 创建
const promise = new Promise((resolve, reject) => {setTimeout(() => {Math.random() > 0.5 ? resolve('成功') : reject('失败');}, 1000);
});// Promise 链式调用
promise.then(result => {console.log('then:', result);return result + '!';}).catch(error => {console.log('catch:', error);throw new Error('新的错误');}).then(result => {console.log('第二个then:', result);}).finally(() => {console.log('无论如何都会执行');});// Promise 静态方法
Promise.resolve('立即成功').then(console.log);
Promise.reject('立即失败').catch(console.log);// Promise.all - 全部成功
Promise.all([Promise.resolve(1),Promise.resolve(2)
]).then(results => console.log('all成功:', results)); // [1, 2]// Promise.race - 第一个完成
Promise.race([new Promise(resolve => setTimeout(() => resolve('第一个'), 100)),new Promise(resolve => setTimeout(() => resolve('第二个'), 200))
]).then(result => console.log('race:', result)); // "第一个"
题目6:async/await 错误处理
答案:
// 错误处理方式
async function fetchData() {try {const response = await fetch('/api/data');const data = await response.json();return data;} catch (error) {console.error('请求失败:', error);throw error; // 重新抛出错误}
}// 多个异步操作
async function processMultiple() {try {const [user, posts] = await Promise.all([fetchUser(),fetchPosts()]);return { user, posts };} catch (error) {// 任何一个请求失败都会进入catchconsole.error('多个请求失败:', error);}
}// 替代 try-catch 的方式
async function alternativeErrorHandling() {const result = await fetchData().catch(error => {console.error('替代方案:', error);return null;});if (!result) return;// 处理结果
}
2.2 事件循环
题目7:说出以下代码的输出顺序
答案:
console.log('1');setTimeout(() => {console.log('2');
}, 0);Promise.resolve().then(() => {console.log('3');
});console.log('4');// 输出顺序: 1 -> 4 -> 3 -> 2
解析:
- 同步代码:
1
、4
- 微任务(Promise):
3
- 宏任务(setTimeout):
2
事件循环机制:
- 调用栈:同步代码执行
- 微任务队列:Promise、MutationObserver、process.nextTick
- 宏任务队列:setTimeout、setInterval、I/O、UI渲染
- 执行顺序:调用栈 → 微任务 → 宏任务 → 微任务…
三、面向对象与原型
3.1 原型与继承
题目8:原型链的理解
答案:
function Person(name) {this.name = name;
}Person.prototype.sayHello = function() {console.log(`Hello, I'm ${this.name}`);
};function Student(name, grade) {Person.call(this, name);this.grade = grade;
}// 设置原型链
Student.prototype = Object.create(Person.prototype);
Student.prototype.constructor = Student;Student.prototype.study = function() {console.log(`${this.name} is studying`);
};const student = new Student('Alice', 'A');
student.sayHello(); // "Hello, I'm Alice"
student.study(); // "Alice is studying"// 原型链验证
console.log(student instanceof Student); // true
console.log(student instanceof Person); // true
console.log(student instanceof Object); // true
解析:
- prototype:函数的原型对象
- proto:对象的原型指针(实际使用 Object.getPrototypeOf)
- 原型链:对象通过 proto 连接形成的链式结构
- 继承本质:通过原型链实现属性和方法的共享
题目9:Class 与构造函数的区别
答案:
// 构造函数方式
function OldPerson(name) {this.name = name;
}OldPerson.prototype.sayHello = function() {console.log(`Hello, ${this.name}`);
};// Class 方式
class NewPerson {constructor(name) {this.name = name;}// 实例方法(在原型上)sayHello() {console.log(`Hello, ${this.name}`);}// 静态方法static createAnonymous() {return new NewPerson('Anonymous');}// Getter/Setterget displayName() {return `Person: ${this.name}`;}
}// 继承对比
class Student extends NewPerson {constructor(name, grade) {super(name); // 必须调用this.grade = grade;}
}
解析:
- Class 是语法糖,本质还是基于原型的继承
- Class 特点:
- 更清晰的语法
- 必须使用 new 调用
- 方法不可枚举
- 内置的 extends 和 super
- 支持静态方法和私有字段
四、ES6+ 新特性
4.1 解构与扩展运算符
题目10:解构赋值的应用
答案:
// 数组解构
const [first, second, ...rest] = [1, 2, 3, 4, 5];
console.log(first, second, rest); // 1 2 [3,4,5]// 对象解构
const person = { name: 'Alice', age: 25, city: 'Beijing' };
const { name, age, country = 'China' } = person;
console.log(name, age, country); // Alice 25 China// 函数参数解构
function printUser({ name, age = 18 }) {console.log(`${name} is ${age} years old`);
}// 嵌套解构
const company = {name: 'Tech Corp',employees: [{ name: 'Alice', department: 'Engineering' }]
};
const { employees: [{ department }] } = company;// 扩展运算符
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const merged = [...arr1, ...arr2];const obj1 = { a: 1, b: 2 };
const obj2 = { ...obj1, c: 3 };
4.2 箭头函数
题目11:箭头函数与普通函数的区别
答案:
const obj = {name: 'Alice',// 普通函数regularFunc: function() {console.log('Regular:', this.name);},// 箭头函数arrowFunc: () => {console.log('Arrow:', this.name); // undefined},// 嵌套函数中的thisnestedFunc: function() {const innerArrow = () => {console.log('Nested Arrow:', this.name); // Alice};innerArrow();}
};// 其它区别
function Regular() {}
const Arrow = () => {};console.log(new Regular()); // Regular实例
console.log(new Arrow()); // TypeError// arguments 对象
function regular() { console.log(arguments); }
const arrow = () => { console.log(arguments); }; // ReferenceError
解析:
- this 绑定:箭头函数没有自己的 this,继承外层作用域
- 构造函数:箭头函数不能用作构造函数
- arguments:箭头函数没有 arguments 对象
- 原型:箭头函数没有 prototype 属性
五、函数式编程
5.1 高阶函数
题目12:实现常用的高阶函数
答案:
// map 实现
Array.prototype.myMap = function(callback) {const result = [];for (let i = 0; i < this.length; i++) {result.push(callback(this[i], i, this));}return result;
};// filter 实现
Array.prototype.myFilter = function(callback) {const result = [];for (let i = 0; i < this.length; i++) {if (callback(this[i], i, this)) {result.push(this[i]);}}return result;
};// reduce 实现
Array.prototype.myReduce = function(callback, initialValue) {let accumulator = initialValue !== undefined ? initialValue : this[0];let startIndex = initialValue !== undefined ? 0 : 1;for (let i = startIndex; i < this.length; i++) {accumulator = callback(accumulator, this[i], i, this);}return accumulator;
};// 使用示例
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.myMap(x => x * 2);
const evens = numbers.myFilter(x => x % 2 === 0);
const sum = numbers.myReduce((acc, curr) => acc + curr, 0);
5.2 柯里化与组合
题目13:实现函数柯里化
答案:
// 柯里化函数
function curry(fn) {return function curried(...args) {if (args.length >= fn.length) {return fn.apply(this, args);} else {return function(...args2) {return curried.apply(this, args.concat(args2));};}};
}// 使用示例
function add(a, b, c) {return a + b + c;
}const curriedAdd = curry(add);
console.log(curriedAdd(1)(2)(3)); // 6
console.log(curriedAdd(1, 2)(3)); // 6
console.log(curriedAdd(1)(2, 3)); // 6// 实际应用:创建特定功能的函数
const add5 = curriedAdd(5);
console.log(add5(10)(15)); // 30
六、性能与优化
6.1 防抖与节流
题目14:实现防抖和节流函数
答案:
// 防抖:在事件触发 n 秒后执行,如果 n 秒内重复触发则重新计时
function debounce(fn, delay) {let timer = null;return function(...args) {clearTimeout(timer);timer = setTimeout(() => {fn.apply(this, args);}, delay);};
}// 节流:在 n 秒内只执行一次
function throttle(fn, delay) {let lastTime = 0;return function(...args) {const now = Date.now();if (now - lastTime >= delay) {fn.apply(this, args);lastTime = now;}};
}// 使用示例
const debouncedSearch = debounce((query) => {console.log('搜索:', query);
}, 300);const throttledScroll = throttle(() => {console.log('滚动处理');
}, 100);// 输入框搜索防抖
searchInput.addEventListener('input', (e) => {debouncedSearch(e.target.value);
});// 滚动事件节流
window.addEventListener('scroll', throttledScroll);
6.2 内存管理
题目15:常见的内存泄漏场景及避免
答案:
// 1. 意外的全局变量
function leak1() {leakedVar = '这是一个全局变量'; // 没有 var/let/const
}// 2. 闭包使用不当
function leak2() {const bigData = new Array(1000000).fill('*');return function() {console.log(bigData.length); // bigData 无法被回收};
}// 3. 定时器未清理
function leak3() {const data = { /* 大数据 */ };setInterval(() => {console.log(data);}, 1000);// 应该保存 timer 并在适当时机清除
}// 4. DOM 引用未清理
function leak4() {const elements = {button: document.getElementById('button'),// 即使从 DOM 移除,elements 仍持有引用};// 解决方案:手动解除引用// elements.button = null;
}// 5. 事件监听器未移除
function leak5() {const button = document.getElementById('button');button.addEventListener('click', handleClick);// 应该在组件卸载时移除// button.removeEventListener('click', handleClick);
}
七、实用编程题
7.1 数组去重
答案:
// 多种数组去重方法
const array = [1, 2, 2, 3, 4, 4, 5];// 1. Set (最简洁)
const unique1 = [...new Set(array)];// 2. filter + indexOf
const unique2 = array.filter((item, index) => array.indexOf(item) === index
);// 3. reduce
const unique3 = array.reduce((acc, curr) => {return acc.includes(curr) ? acc : [...acc, curr];
}, []);// 4. 对象属性 (适用于基本类型)
const unique4 = Object.keys(array.reduce((acc, curr) => {acc[curr] = true;return acc;
}, {}));console.log(unique1); // [1, 2, 3, 4, 5]
7.2 深拷贝实现
答案:
function deepClone(obj, hash = new WeakMap()) {// 基本类型或 nullif (obj === null || typeof obj !== 'object') {return obj;}// 循环引用检测if (hash.has(obj)) {return hash.get(obj);}// 日期对象if (obj instanceof Date) {return new Date(obj);}// 正则表达式if (obj instanceof RegExp) {return new RegExp(obj);}// 数组if (Array.isArray(obj)) {const cloneArr = [];hash.set(obj, cloneArr);obj.forEach((item, index) => {cloneArr[index] = deepClone(item, hash);});return cloneArr;}// 普通对象const cloneObj = Object.create(Object.getPrototypeOf(obj));hash.set(obj, cloneObj);for (let key in obj) {if (obj.hasOwnProperty(key)) {cloneObj[key] = deepClone(obj[key], hash);}}return cloneObj;
}// 使用示例
const original = {name: 'Alice',hobbies: ['reading', 'coding'],address: {city: 'Beijing',coordinates: { lat: 39.9, lng: 116.4 }}
};const cloned = deepClone(original);
面试技巧与准备建议
💡 面试准备建议
- 理解原理而非死记硬背:理解 JavaScript 的运行机制比记住 API 更重要
- 手写代码练习:经常练习手写常见函数和方法
- 关注性能:了解常见性能问题和优化方案
- 掌握调试技巧:熟练使用浏览器开发者工具
- 跟进新特性:关注 ES6+ 的新特性和最佳实践
🎯 答题技巧
- 先思考再回答:理解问题本质,组织好语言再回答
- 结合实际场景:用实际项目经验来支撑你的答案
- 承认不知道:对于不了解的问题诚实承认,展示学习能力
- 代码规范:手写代码时注意命名规范和代码风格
这份面试题涵盖了 JavaScript 的核心概念和常见考点,建议结合实际编码练习来加深理解。祝你面试顺利!