- Published on
- · 59 min read
2025JavaScript面试题
- Authors
- Name
- felixDu
Table of Contents
2025 JavaScript 面试题
目录
1. 基础数据类型与类型判断
1.1 JavaScript的数据类型有哪些?如何准确判断?
问题描述: JavaScript中包含哪些数据类型?如何准确判断一个变量的类型?
详细解析:
JavaScript的数据类型分为两大类:
基本数据类型(原始类型):
Number
- 数值类型String
- 字符串类型Boolean
- 布尔类型Undefined
- 未定义类型Null
- 空值类型Symbol
- 符号类型(ES6新增)BigInt
- 大整数类型(ES2020新增)
引用数据类型:
Object
- 对象类型(包括普通对象、数组、函数等)
示例代码:
// 基本数据类型
let num = 42; // Number
let str = "Hello World"; // String
let bool = true; // Boolean
let undef; // Undefined
let nul = null; // Null
let sym = Symbol('id'); // Symbol
let bigInt = 123456789012345678901234567890n; // BigInt
// 引用数据类型
let obj = { name: '张三' }; // Object
let arr = [1, 2, 3]; // Array(Object的子类型)
let func = function() {}; // Function(Object的子类型)
let date = new Date(); // Date(Object的子类型)
类型判断方法:
// 1. typeof 操作符(适用于基本类型,但有局限性)
console.log(typeof 42); // "number"
console.log(typeof "hello"); // "string"
console.log(typeof true); // "boolean"
console.log(typeof undefined); // "undefined"
console.log(typeof Symbol()); // "symbol"
console.log(typeof 123n); // "bigint"
// typeof的局限性
console.log(typeof null); // "object" (历史遗留问题)
console.log(typeof []); // "object" (无法区分数组)
console.log(typeof {}); // "object" (无法区分具体对象类型)
// 2. instanceof 操作符(用于引用类型)
console.log([] instanceof Array); // true
console.log({} instanceof Object); // true
console.log(function(){} instanceof Function); // true
// 3. Object.prototype.toString.call()(最准确的方法)
function getType(value) {
return Object.prototype.toString.call(value).slice(8, -1);
}
console.log(getType(42)); // "Number"
console.log(getType("hello")); // "String"
console.log(getType(true)); // "Boolean"
console.log(getType(undefined)); // "Undefined"
console.log(getType(null)); // "Null"
console.log(getType(Symbol())); // "Symbol"
console.log(getType([])); // "Array"
console.log(getType({})); // "Object"
console.log(getType(function(){})); // "Function"
console.log(getType(new Date())); // "Date"
面试情景对话:
面试官: "请说说JavaScript中有哪些数据类型?"
面试者: "JavaScript中有7种基本数据类型:Number、String、Boolean、Undefined、Null、Symbol和BigInt,以及1种引用数据类型Object。Object包括普通对象、数组、函数等。"
面试官: "如何准确判断一个变量的类型?typeof有什么局限性吗?"
面试者: "有三种主要方法:
- typeof操作符适用于基本类型,但对null返回'object',对数组也返回'object'
- instanceof用于判断引用类型的具体构造函数
- Object.prototype.toString.call()是最准确的方法,能精确识别所有类型"
面试官: "为什么typeof null返回'object'?"
面试者: "这是JavaScript的历史遗留问题。在JavaScript的早期实现中,值是通过类型标签来表示的,null的类型标签和对象相同,导致typeof null返回'object'。虽然这是个bug,但为了保持向后兼容,一直保留至今。"
拓展问题:
- 如何判断一个变量是否为数组?
- Symbol类型的应用场景有哪些?
- BigInt类型解决了什么问题?
1.2 深拷贝与浅拷贝的区别及实现
问题描述: 请解释深拷贝与浅拷贝的区别,并实现一个完整的深拷贝函数。
详细解析:
浅拷贝: 只复制对象的第一层属性,如果属性值是引用类型,复制的是引用地址。 深拷贝: 递归复制对象的所有层级,完全独立的新对象。
示例代码:
// 浅拷贝示例
const original = {
name: '张三',
age: 25,
hobbies: ['读书', '游泳'],
address: {
city: '北京',
district: '朝阳区'
}
};
// 浅拷贝方法
const shallowCopy1 = Object.assign({}, original);
const shallowCopy2 = { ...original };
// 修改嵌套对象
shallowCopy1.hobbies.push('跑步');
shallowCopy1.address.city = '上海';
console.log(original.hobbies); // ['读书', '游泳', '跑步'] - 被影响了
console.log(original.address.city); // '上海' - 被影响了
// 深拷贝实现
function deepClone(obj, hash = new WeakMap()) {
// 处理null和非对象类型
if (obj === null || typeof obj !== 'object') return obj;
// 处理循环引用
if (hash.has(obj)) return hash.get(obj);
// 处理Date对象
if (obj instanceof Date) return new Date(obj);
// 处理RegExp对象
if (obj instanceof RegExp) return new RegExp(obj);
// 处理函数(函数一般不需要深拷贝,直接返回)
if (typeof obj === 'function') return obj;
// 处理数组和对象
const cloneObj = Array.isArray(obj) ? [] : {};
hash.set(obj, cloneObj);
// 递归克隆
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
cloneObj[key] = deepClone(obj[key], hash);
}
}
return cloneObj;
}
// 测试深拷贝
const deepCopy = deepClone(original);
deepCopy.hobbies.push('画画');
deepCopy.address.city = '广州';
console.log(original.hobbies); // ['读书', '游泳'] - 不受影响
console.log(original.address.city); // '北京' - 不受影响
console.log(deepCopy.hobbies); // ['读书', '游泳', '画画']
console.log(deepCopy.address.city); // '广州'
// 处理循环引用的测试
const circularObj = { name: '测试' };
circularObj.self = circularObj;
const clonedCircular = deepClone(circularObj);
console.log(clonedCircular.self === clonedCircular); // true
其他深拷贝方法:
// 1. JSON方法(有局限性)
const jsonClone = JSON.parse(JSON.stringify(original));
// 局限性:无法处理函数、undefined、Symbol、Date等
// 2. 使用第三方库
// lodash的cloneDeep方法
// const _ = require('lodash');
// const lodashClone = _.cloneDeep(original);
// 3. structuredClone(现代浏览器支持)
if (typeof structuredClone !== 'undefined') {
const structuredClone = structuredClone(original);
}
面试情景对话:
面试官: "请解释深拷贝与浅拷贝的区别,并说说各自的应用场景。"
面试者: "浅拷贝只复制对象的第一层属性,对于引用类型的属性,复制的是引用地址,因此修改拷贝对象会影响原对象。深拷贝则会递归复制所有层级,创建完全独立的新对象。浅拷贝适用于只需复制简单对象的场景,深拷贝适用于需要完全独立副本的场景。"
面试官: "JSON.parse(JSON.stringify())这种深拷贝方法有什么问题?"
面试者: "这种方法有几个局限性:
- 无法处理函数、undefined、Symbol等类型
- 无法处理Date、RegExp等特殊对象
- 无法处理循环引用,会报错
- 会忽略对象的原型链 因此只适合处理简单的、纯数据对象。"
面试官: "如何处理循环引用的问题?"
面试者: "可以使用WeakMap来记录已经拷贝过的对象,在递归过程中,如果发现对象已经被拷贝过,直接返回之前拷贝的结果,避免无限递归。"
拓展问题:
- 如何实现一个支持所有数据类型的深拷贝?
- 在什么情况下应该选择浅拷贝而不是深拷贝?
- structuredClone API的优势和兼容性如何?
2. 闭包与作用域
2.1 什么是闭包?闭包的应用场景
问题描述: 请详细解释JavaScript中的闭包概念,并举例说明其主要应用场景。
详细解析:
闭包定义: 闭包是指有权访问另一个函数作用域中变量的函数。简单来说,闭包就是函数和其周围状态(词法环境)的引用捆绑在一起形成的组合。
基础示例:
function outerFunction(x) {
// 外部函数的变量
let outerVariable = x;
// 内部函数(闭包)
function innerFunction(y) {
// 访问外部函数的变量
console.log(outerVariable + y);
}
return innerFunction;
}
const myClosure = outerFunction(10);
myClosure(5); // 输出: 15
// 即使outerFunction已经执行完毕,innerFunction仍然能访问outerVariable
应用场景详解:
1. 数据私有化和封装
function createCounter() {
let count = 0; // 私有变量
return {
increment: function() {
count++;
return count;
},
decrement: function() {
count--;
return count;
},
getCount: function() {
return count;
}
};
}
const counter = createCounter();
console.log(counter.increment()); // 1
console.log(counter.increment()); // 2
console.log(counter.getCount()); // 2
// console.log(counter.count); // undefined - 无法直接访问私有变量
2. 函数柯里化(Currying)
function multiply(a) {
return function(b) {
return function(c) {
return a * b * c;
};
};
}
const multiplyBy2 = multiply(2);
const multiplyBy2And3 = multiplyBy2(3);
const result = multiplyBy2And3(4); // 2 * 3 * 4 = 24
console.log(result); // 24
// 或者链式调用
console.log(multiply(2)(3)(4)); // 24
3. 模块化模式
const Calculator = (function() {
// 私有变量和方法
let history = [];
function addToHistory(operation, result) {
history.push({ operation, result, timestamp: Date.now() });
}
// 公共API
return {
add: function(a, b) {
const result = a + b;
addToHistory(`${a} + ${b}`, result);
return result;
},
subtract: function(a, b) {
const result = a - b;
addToHistory(`${a} - ${b}`, result);
return result;
},
getHistory: function() {
return [...history]; // 返回副本,保护原数据
},
clearHistory: function() {
history = [];
}
};
})();
console.log(Calculator.add(5, 3)); // 8
console.log(Calculator.subtract(10, 4)); // 6
console.log(Calculator.getHistory()); // 显示操作历史
4. 事件处理和回调函数
function setupEventHandlers() {
let clickCount = 0;
document.getElementById('button').addEventListener('click', function() {
clickCount++;
console.log(`按钮被点击了 ${clickCount} 次`);
});
// 返回一个方法来获取点击次数
return function() {
return clickCount;
};
}
const getClickCount = setupEventHandlers();
// 每次点击按钮后,可以通过getClickCount()获取点击次数
5. 防抖和节流
// 防抖函数
function debounce(func, delay) {
let timeoutId;
return function(...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => func.apply(this, args), delay);
};
}
// 节流函数
function throttle(func, delay) {
let isThrottled = false;
return function(...args) {
if (!isThrottled) {
func.apply(this, args);
isThrottled = true;
setTimeout(() => isThrottled = false, delay);
}
};
}
// 使用示例
const debouncedSearch = debounce(function(query) {
console.log('搜索:', query);
}, 300);
const throttledScroll = throttle(function() {
console.log('滚动事件处理');
}, 100);
闭包的注意事项:
// 常见陷阱:循环中的闭包
console.log('=== 错误示例 ===');
for (var i = 0; i < 3; i++) {
setTimeout(function() {
console.log(i); // 输出: 3, 3, 3
}, 100);
}
console.log('=== 正确示例1: 使用IIFE ===');
for (var i = 0; i < 3; i++) {
(function(index) {
setTimeout(function() {
console.log(index); // 输出: 0, 1, 2
}, 100);
})(i);
}
console.log('=== 正确示例2: 使用let ===');
for (let i = 0; i < 3; i++) {
setTimeout(function() {
console.log(i); // 输出: 0, 1, 2
}, 100);
}
面试情景对话:
面试官: "请解释一下什么是闭包?"
面试者: "闭包是指有权访问另一个函数作用域中变量的函数。当一个内部函数引用了外部函数的变量时,即使外部函数执行完毕,这些变量仍然会被保存在内存中,内部函数仍然可以访问它们。"
面试官: "闭包有什么实际应用?"
面试者: "闭包有很多应用场景:
- 数据私有化,创建私有变量和方法
- 函数柯里化,实现参数复用
- 模块化模式,创建命名空间
- 事件处理中保存状态
- 实现防抖和节流函数"
面试官: "闭包可能带来什么问题?如何避免?"
面试者: "闭包可能导致内存泄漏,因为被引用的变量无法被垃圾回收。避免方法包括:
- 及时解除不必要的引用
- 避免在闭包中引用DOM元素
- 在不需要时将变量设置为null
- 使用WeakMap等弱引用数据结构"
拓展问题:
- 如何用闭包实现单例模式?
- 闭包和普通函数在性能上有什么区别?
- 在React Hooks中,闭包是如何应用的?
6. ES6+新特性
6.1 解构赋值与扩展运算符
问题描述: 请详细解释ES6的解构赋值和扩展运算符,包括它们的高级用法和应用场景。
详细解析:
数组解构赋值:
// 基础数组解构
const arr = [1, 2, 3, 4, 5];
const [first, second, ...rest] = arr;
console.log(first); // 1
console.log(second); // 2
console.log(rest); // [3, 4, 5]
// 跳过某些元素
const [a, , c] = arr;
console.log(a); // 1
console.log(c); // 3
// 默认值
const [x = 10, y = 20] = [1];
console.log(x); // 1
console.log(y); // 20
// 交换变量
let num1 = 10, num2 = 20;
[num1, num2] = [num2, num1];
console.log(num1, num2); // 20, 10
// 函数返回多个值
function getCoordinates() {
return [100, 200];
}
const [x, y] = getCoordinates();
对象解构赋值:
// 基础对象解构
const person = {
name: '张三',
age: 25,
city: '北京',
hobbies: ['读书', '游泳']
};
const { name, age, city } = person;
console.log(name, age, city); // 张三 25 北京
// 重命名和默认值
const { name: personName, age: personAge, salary = 0 } = person;
console.log(personName, personAge, salary); // 张三 25 0
// 嵌套解构
const student = {
info: {
name: '李四',
grade: {
math: 90,
english: 85
}
}
};
const { info: { name: studentName, grade: { math } } } = student;
console.log(studentName, math); // 李四 90
// 函数参数解构
function greet({ name, age = 18 }) {
console.log(`你好,我是${name},今年${age}岁`);
}
greet({ name: '王五', age: 22 }); // 你好,我是王五,今年22岁
greet({ name: '赵六' }); // 你好,我是赵六,今年18岁
// 动态属性解构
const key = 'name';
const { [key]: value } = person;
console.log(value); // 张三
扩展运算符应用:
// 数组扩展
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
// 数组合并
const merged = [...arr1, ...arr2]; // [1, 2, 3, 4, 5, 6]
// 数组克隆
const cloned = [...arr1]; // [1, 2, 3]
// 函数参数传递
function sum(a, b, c) {
return a + b + c;
}
console.log(sum(...arr1)); // 6
// Math函数应用
const numbers = [1, 5, 3, 9, 2];
console.log(Math.max(...numbers)); // 9
console.log(Math.min(...numbers)); // 1
// 对象扩展
const obj1 = { a: 1, b: 2 };
const obj2 = { c: 3, d: 4 };
// 对象合并
const mergedObj = { ...obj1, ...obj2 }; // { a: 1, b: 2, c: 3, d: 4 }
// 对象克隆(浅拷贝)
const clonedObj = { ...obj1 }; // { a: 1, b: 2 }
// 属性覆盖
const updated = { ...person, age: 26, city: '上海' };
// 条件属性添加
const isAdmin = true;
const user = {
name: '管理员',
...(isAdmin && { role: 'admin', permissions: ['read', 'write'] })
};
高级应用场景:
// 1. React组件props传递
function MyComponent(props) {
const { title, ...restProps } = props;
return <div {...restProps}>{title}</div>;
}
// 2. 函数重载模拟
function createUser(name, ...options) {
const [age, email, ...extraInfo] = options;
return {
name,
age: age || 18,
email: email || '',
extraInfo
};
}
// 3. 数组去重
const duplicateArray = [1, 2, 2, 3, 3, 4];
const uniqueArray = [...new Set(duplicateArray)]; // [1, 2, 3, 4]
// 4. 字符串转数组
const str = 'hello';
const charArray = [...str]; // ['h', 'e', 'l', 'l', 'o']
// 5. NodeList转数组
const nodeList = document.querySelectorAll('div');
const nodeArray = [...nodeList];
// 6. 函数参数收集
function logger(level, ...messages) {
console.log(`[${level}]`, ...messages);
}
logger('INFO', '用户登录', '用户ID: 123', '时间:', new Date());
面试情景对话:
面试官: "请解释ES6解构赋值的作用和优势。"
面试者: "解构赋值允许我们从数组或对象中提取值,赋值给变量,使代码更简洁易读。优势包括:
- 减少代码量,提高可读性
- 支持默认值,增强代码健壮性
- 可以轻松交换变量值
- 函数参数解构,让API更清晰"
面试官: "扩展运算符有哪些实际应用场景?"
面试者: "扩展运算符有很多应用:
- 数组和对象的浅拷贝与合并
- 函数参数的展开传递
- 数组去重(配合Set)
- React中的props传递
- 将类数组对象转为真正的数组"
拓展问题:
- 解构赋值的性能如何?
- 如何深度解构嵌套对象?
- 扩展运算符和Object.assign有什么区别?
6.2 Symbol与WeakMap/WeakSet
问题描述: 请解释Symbol类型的特点和应用场景,以及WeakMap和WeakSet与普通Map、Set的区别。
详细解析:
Symbol基础概念:
// Symbol的基本特性
const sym1 = Symbol();
const sym2 = Symbol();
console.log(sym1 === sym2); // false - 每个Symbol都是唯一的
const sym3 = Symbol('description');
const sym4 = Symbol('description');
console.log(sym3 === sym4); // false - 即使描述相同也不相等
// Symbol.for() - 全局Symbol注册表
const globalSym1 = Symbol.for('global');
const globalSym2 = Symbol.for('global');
console.log(globalSym1 === globalSym2); // true
console.log(Symbol.keyFor(globalSym1)); // 'global'
Symbol应用场景:
// 1. 创建对象的私有属性
const _privateMethod = Symbol('privateMethod');
const _privateProperty = Symbol('privateProperty');
class MyClass {
constructor() {
this[_privateProperty] = '私有属性';
}
[_privateMethod]() {
return '私有方法';
}
getPrivateValue() {
return this[_privateProperty];
}
callPrivateMethod() {
return this[_privateMethod]();
}
}
const instance = new MyClass();
console.log(instance.getPrivateValue()); // 私有属性
console.log(instance.callPrivateMethod()); // 私有方法
console.log(Object.keys(instance)); // [] - Symbol属性不可枚举
// 2. 避免属性名冲突
const SIZE = Symbol('size');
const obj = {
[SIZE]: 100,
size: 'large' // 不会冲突
};
console.log(obj[SIZE]); // 100
console.log(obj.size); // 'large'
// 3. 实现可迭代对象
class Range {
constructor(start, end) {
this.start = start;
this.end = end;
}
[Symbol.iterator]() {
let current = this.start;
const end = this.end;
return {
next() {
if (current <= end) {
return { value: current++, done: false };
} else {
return { done: true };
}
}
};
}
}
const range = new Range(1, 5);
for (const num of range) {
console.log(num); // 1, 2, 3, 4, 5
}
// 4. 实现对象的类型检查
const TYPE_SYMBOL = Symbol('type');
class Animal {
constructor() {
this[TYPE_SYMBOL] = 'Animal';
}
static isAnimal(obj) {
return obj && obj[TYPE_SYMBOL] === 'Animal';
}
}
const animal = new Animal();
console.log(Animal.isAnimal(animal)); // true
WeakMap详解:
// WeakMap vs Map
const map = new Map();
const weakMap = new WeakMap();
let obj1 = { name: '对象1' };
let obj2 = { name: '对象2' };
// Map允许任何类型的key
map.set('string-key', 'value1');
map.set(1, 'value2');
map.set(obj1, 'value3');
// WeakMap只允许对象作为key
// weakMap.set('string', 'value'); // TypeError
weakMap.set(obj1, 'value1');
weakMap.set(obj2, 'value2');
console.log(map.size); // 3
// console.log(weakMap.size); // undefined - WeakMap没有size属性
// WeakMap的垃圾回收优势
obj1 = null; // 释放引用
// 在WeakMap中,当obj1被垃圾回收时,对应的键值对也会被自动删除
// WeakMap应用场景
// 1. 存储DOM元素的私有数据
const elementData = new WeakMap();
function attachData(element, data) {
elementData.set(element, data);
}
function getData(element) {
return elementData.get(element);
}
const div = document.createElement('div');
attachData(div, { clickCount: 0, lastClicked: null });
// 当DOM元素被删除时,相关数据也会被自动回收
// 2. 实现对象的私有属性
const _private = new WeakMap();
class Person {
constructor(name, age) {
_private.set(this, { name, age });
}
getName() {
return _private.get(this).name;
}
getAge() {
return _private.get(this).age;
}
setAge(age) {
const data = _private.get(this);
data.age = age;
}
}
const person = new Person('张三', 25);
console.log(person.getName()); // 张三
// 无法直接访问私有数据
// 3. 缓存计算结果
const cache = new WeakMap();
function fibonacci(n) {
if (n <= 1) return n;
if (cache.has(fibonacci)) {
const cached = cache.get(fibonacci);
if (cached[n] !== undefined) {
return cached[n];
}
} else {
cache.set(fibonacci, {});
}
const result = fibonacci(n - 1) + fibonacci(n - 2);
cache.get(fibonacci)[n] = result;
return result;
}
WeakSet详解:
// WeakSet vs Set
const set = new Set();
const weakSet = new WeakSet();
let objA = { id: 1 };
let objB = { id: 2 };
// Set可以存储任何类型的值
set.add(1);
set.add('string');
set.add(objA);
// WeakSet只能存储对象
weakSet.add(objA);
weakSet.add(objB);
// weakSet.add(1); // TypeError
console.log(set.size); // 3
// console.log(weakSet.size); // undefined
// WeakSet应用场景
// 1. 标记对象状态
const processedObjects = new WeakSet();
function processObject(obj) {
if (processedObjects.has(obj)) {
console.log('对象已处理过');
return;
}
// 处理对象...
console.log('处理对象:', obj);
processedObjects.add(obj);
}
const data = { value: 42 };
processObject(data); // 处理对象: { value: 42 }
processObject(data); // 对象已处理过
// 2. 防止循环引用
const visited = new WeakSet();
function traverse(obj) {
if (visited.has(obj)) {
return; // 避免循环引用
}
visited.add(obj);
// 遍历对象属性...
for (const key in obj) {
if (typeof obj[key] === 'object' && obj[key] !== null) {
traverse(obj[key]);
}
}
}
// 3. 权限验证
const authorizedUsers = new WeakSet();
class SecureResource {
constructor() {
this.data = '敏感数据';
}
authorize(user) {
authorizedUsers.add(user);
}
access(user) {
if (!authorizedUsers.has(user)) {
throw new Error('无权限访问');
}
return this.data;
}
}
面试情景对话:
面试官: "Symbol类型有什么特点?主要用于解决什么问题?"
面试者: "Symbol是ES6新增的基本数据类型,每个Symbol都是唯一的。主要特点包括:
- 不可变性和唯一性
- 不能被隐式转换
- 不会出现在for...in循环中 主要用于创建对象的唯一属性键,避免属性名冲突,实现对象的私有属性等。"
面试官: "WeakMap和Map有什么区别?各自的应用场景是什么?"
面试者: "主要区别:
- WeakMap只能使用对象作为键,Map可以使用任何类型
- WeakMap的键是弱引用,有利于垃圾回收
- WeakMap不可迭代,没有size属性 应用场景:WeakMap适合存储对象的私有数据、DOM元素相关数据等;Map适合需要迭代或获取大小的键值对存储。"
拓展问题:
- 如何使用Symbol实现真正的私有属性?
- WeakMap在内存管理方面有什么优势?
- 如何选择使用Map还是WeakMap?
7. 性能优化与内存管理
7.1 JavaScript内存管理与垃圾回收
问题描述: 请详细解释JavaScript的内存管理机制,包括垃圾回收算法和常见的内存泄漏场景。
详细解析:
JavaScript内存管理基础:
// 内存分配的三个阶段:
// 1. 分配内存
let obj = { name: '张三' }; // 为对象分配内存
// 2. 使用内存
console.log(obj.name); // 读取内存中的数据
// 3. 释放内存
obj = null; // 取消引用,等待垃圾回收
垃圾回收算法:
1. 引用计数(Reference Counting)
// 引用计数的问题:循环引用
function createCircularReference() {
const objA = {};
const objB = {};
objA.ref = objB;
objB.ref = objA;
// 循环引用导致引用计数永远不为0
return [objA, objB];
}
// 现代浏览器已经解决了这个问题,但了解原理很重要
2. 标记清除(Mark-and-Sweep)
// 标记清除算法的工作原理示例
function demonstrateMarkAndSweep() {
let root = {
child1: {
data: '可达对象1'
},
child2: {
data: '可达对象2'
}
};
let unreachable = {
data: '不可达对象'
};
// 从根对象开始标记所有可达对象
// unreachable对象没有被引用,会被标记为不可达
// 清除阶段会回收不可达对象
return root;
}
常见内存泄漏场景及解决方案:
1. 全局变量滥用
// 问题代码
function leakyFunction() {
// 意外创建全局变量
leakyVariable = '这是内存泄漏';
// 未声明的变量自动成为全局变量
this.anotherLeak = '另一个泄漏';
}
// 解决方案
function fixedFunction() {
'use strict'; // 严格模式防止意外全局变量
let localVariable = '正确的局部变量';
const anotherLocal = '另一个局部变量';
}
2. 事件监听器未清理
// 问题代码
class ComponentWithLeak {
constructor() {
this.data = new Array(1000000).fill('大量数据');
// 添加事件监听器但从不移除
document.addEventListener('click', this.handleClick.bind(this));
}
handleClick() {
console.log('处理点击事件');
}
}
// 解决方案
class ComponentFixed {
constructor() {
this.data = new Array(1000000).fill('大量数据');
this.boundHandleClick = this.handleClick.bind(this);
document.addEventListener('click', this.boundHandleClick);
}
handleClick() {
console.log('处理点击事件');
}
destroy() {
// 组件销毁时移除事件监听器
document.removeEventListener('click', this.boundHandleClick);
this.data = null;
}
}
3. 定时器未清理
// 问题代码
class TimerLeak {
constructor() {
this.data = new Array(1000000).fill('大量数据');
// 设置定时器但从不清理
this.timer = setInterval(() => {
console.log('定时器运行中...');
}, 1000);
}
}
// 解决方案
class TimerFixed {
constructor() {
this.data = new Array(1000000).fill('大量数据');
this.timer = setInterval(() => {
console.log('定时器运行中...');
}, 1000);
}
destroy() {
// 清理定时器
if (this.timer) {
clearInterval(this.timer);
this.timer = null;
}
this.data = null;
}
}
4. 闭包引用大对象
// 问题代码
function createClosure() {
const largeObject = new Array(1000000).fill('大量数据');
return function() {
// 闭包引用了largeObject,即使不使用也不会被回收
console.log('闭包函数');
};
}
// 解决方案
function createOptimizedClosure() {
const largeObject = new Array(1000000).fill('大量数据');
// 只提取需要的数据
const neededData = largeObject.length;
return function() {
console.log('数据长度:', neededData);
// largeObject可以被垃圾回收
};
}
5. DOM引用未清理
// 问题代码
class DOMReferenceLeak {
constructor() {
this.elements = [];
// 保存大量DOM引用
document.querySelectorAll('div').forEach(div => {
this.elements.push(div);
});
}
}
// 解决方案
class DOMReferenceFixed {
constructor() {
this.elements = [];
this.weakElements = new WeakSet();
document.querySelectorAll('div').forEach(div => {
// 使用WeakSet避免强引用
this.weakElements.add(div);
});
}
destroy() {
// 清理引用
this.elements = [];
// WeakSet会自动清理
}
}
内存监控和分析工具:
// 1. Performance API监控
function monitorMemory() {
if (performance.memory) {
const memory = performance.memory;
console.log('已使用内存:', memory.usedJSHeapSize / 1024 / 1024, 'MB');
console.log('总分配内存:', memory.totalJSHeapSize / 1024 / 1024, 'MB');
console.log('内存限制:', memory.jsHeapSizeLimit / 1024 / 1024, 'MB');
}
}
// 定期监控内存使用
setInterval(monitorMemory, 5000);
// 2. 手动触发垃圾回收(仅在开发环境)
function forceGC() {
if (window.gc) {
window.gc(); // 需要在Chrome中使用--enable-precise-memory-info标志
}
}
// 3. 内存使用分析工具
class MemoryAnalyzer {
constructor() {
this.measurements = [];
}
measureMemory(label) {
const measurement = {
label,
timestamp: Date.now(),
memory: performance.memory ? {
used: performance.memory.usedJSHeapSize,
total: performance.memory.totalJSHeapSize
} : null
};
this.measurements.push(measurement);
console.log(`[${label}] 内存使用:`, measurement.memory);
}
getMemoryDiff(startLabel, endLabel) {
const start = this.measurements.find(m => m.label === startLabel);
const end = this.measurements.find(m => m.label === endLabel);
if (start && end && start.memory && end.memory) {
const diff = end.memory.used - start.memory.used;
console.log(`${startLabel} -> ${endLabel}: ${diff} bytes`);
return diff;
}
}
}
// 使用示例
const analyzer = new MemoryAnalyzer();
analyzer.measureMemory('开始');
// 执行一些操作...
const largeArray = new Array(1000000).fill('test');
analyzer.measureMemory('创建大数组后');
largeArray.length = 0;
analyzer.measureMemory('清空数组后');
analyzer.getMemoryDiff('开始', '创建大数组后');
最佳实践和优化建议:
// 1. 对象池(Object Pool)减少内存分配
class ObjectPool {
constructor(createFn, maxSize = 100) {
this.createFn = createFn;
this.pool = [];
this.maxSize = maxSize;
}
get() {
return this.pool.pop() || this.createFn();
}
release(obj) {
if (this.pool.length < this.maxSize) {
// 重置对象状态
this.resetObject(obj);
this.pool.push(obj);
}
}
resetObject(obj) {
// 重置对象到初始状态
Object.keys(obj).forEach(key => {
delete obj[key];
});
}
}
// 使用对象池
const pointPool = new ObjectPool(() => ({ x: 0, y: 0 }));
function usePoint() {
const point = pointPool.get();
point.x = 10;
point.y = 20;
// 使用完后归还
pointPool.release(point);
}
// 2. 及时清理引用
class ResourceManager {
constructor() {
this.resources = new Map();
this.timers = new Set();
this.eventListeners = new Map();
}
addResource(id, resource) {
this.resources.set(id, resource);
}
addTimer(timer) {
this.timers.add(timer);
}
addEventListener(element, event, handler) {
if (!this.eventListeners.has(element)) {
this.eventListeners.set(element, new Map());
}
this.eventListeners.get(element).set(event, handler);
element.addEventListener(event, handler);
}
cleanup() {
// 清理所有资源
this.resources.clear();
// 清理定时器
this.timers.forEach(timer => clearInterval(timer));
this.timers.clear();
// 清理事件监听器
this.eventListeners.forEach((events, element) => {
events.forEach((handler, event) => {
element.removeEventListener(event, handler);
});
});
this.eventListeners.clear();
}
}
面试情景对话:
面试官: "请解释JavaScript的垃圾回收机制。"
面试者: "JavaScript使用自动垃圾回收,主要算法是标记清除。从根对象开始标记所有可达对象,然后清除未标记的对象。现代引擎还使用了分代回收、增量回收等优化技术。"
面试官: "常见的内存泄漏场景有哪些?如何避免?"
面试者: "常见场景包括:
- 全局变量滥用 - 使用严格模式,及时清理
- 事件监听器未移除 - 组件销毁时removeEventListener
- 定时器未清理 - 使用clearInterval/clearTimeout
- 闭包引用大对象 - 只保留必要数据
- DOM引用未释放 - 使用WeakMap存储DOM相关数据"
拓展问题:
- 如何监控和分析内存使用情况?
- V8引擎的垃圾回收策略有哪些?
- 如何优化大型应用的内存使用?
8. 模块化与工程化
8.1 JavaScript模块化发展历程与实现
问题描述: 请详细解释JavaScript模块化的发展历程,包括CommonJS、AMD、UMD、ES6 Module等,并分析它们的区别和应用场景。
详细解析:
1. 立即执行函数表达式(IIFE)
// 早期的模块化模式
const MyModule = (function() {
// 私有变量
let privateVariable = 0;
// 私有方法
function privateMethod() {
console.log('这是私有方法');
}
// 返回公共接口
return {
publicMethod: function() {
privateVariable++;
console.log('公共方法被调用,计数:', privateVariable);
},
getCount: function() {
return privateVariable;
}
};
})();
MyModule.publicMethod(); // 公共方法被调用,计数: 1
console.log(MyModule.getCount()); // 1
// console.log(MyModule.privateVariable); // undefined - 无法访问私有变量
2. CommonJS (Node.js)
// math.js - CommonJS模块导出
const PI = 3.14159;
function add(a, b) {
return a + b;
}
function multiply(a, b) {
return a * b;
}
// 导出方式1:exports
exports.add = add;
exports.multiply = multiply;
exports.PI = PI;
// 导出方式2:module.exports
module.exports = {
add,
multiply,
PI,
// 还可以导出类、函数等
Calculator: class {
constructor() {
this.result = 0;
}
add(num) {
this.result += num;
return this;
}
getResult() {
return this.result;
}
}
};
// main.js - CommonJS模块导入
const math = require('./math');
const { add, multiply, PI } = require('./math');
console.log(add(2, 3)); // 5
console.log(multiply(4, 5)); // 20
console.log(PI); // 3.14159
const calc = new math.Calculator();
console.log(calc.add(10).add(5).getResult()); // 15
// CommonJS的特点:
// 1. 同步加载
// 2. 运行时加载
// 3. 输出的是值的拷贝
// 4. 可以动态加载
3. AMD (Asynchronous Module Definition)
// 使用RequireJS实现AMD
// math.js - AMD模块定义
define(function() {
const PI = 3.14159;
function add(a, b) {
return a + b;
}
function multiply(a, b) {
return a * b;
}
return {
add,
multiply,
PI
};
});
// 带依赖的模块
define(['jquery', './math'], function($, math) {
function showResult(result) {
$('#result').text(result);
}
return {
calculate: function() {
const result = math.add(2, 3);
showResult(result);
}
};
});
// main.js - AMD模块使用
require(['./math', './calculator'], function(math, calculator) {
console.log(math.add(2, 3)); // 5
calculator.calculate();
});
// AMD的特点:
// 1. 异步加载
// 2. 依赖前置
// 3. 适合浏览器环境
4. UMD (Universal Module Definition)
// umd-module.js - 兼容多种模块系统
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD
define(['dependency'], factory);
} else if (typeof module === 'object' && module.exports) {
// CommonJS
module.exports = factory(require('dependency'));
} else {
// 浏览器全局变量
root.MyModule = factory(root.Dependency);
}
}(typeof self !== 'undefined' ? self : this, function (dependency) {
// 模块代码
function MyModule() {
this.name = 'UMD Module';
}
MyModule.prototype.getName = function() {
return this.name;
};
return MyModule;
}));
5. ES6 Module (ESM)
// math.js - ES6模块导出
export const PI = 3.14159;
export function add(a, b) {
return a + b;
}
export function multiply(a, b) {
return a * b;
}
// 默认导出
export default class Calculator {
constructor() {
this.result = 0;
}
add(num) {
this.result += num;
return this;
}
multiply(num) {
this.result *= num;
return this;
}
getResult() {
return this.result;
}
}
// 重新导出
export { add as sum } from './basic-math';
export * from './advanced-math';
// main.js - ES6模块导入
import Calculator, { add, multiply, PI } from './math.js';
import { add as sum } from './math.js';
import * as math from './math.js';
console.log(add(2, 3)); // 5
console.log(multiply(4, 5)); // 20
console.log(PI); // 3.14159
const calc = new Calculator();
console.log(calc.add(10).multiply(2).getResult()); // 20
// 动态导入
async function loadModule() {
const module = await import('./math.js');
console.log(module.add(1, 2));
}
// ES6 Module的特点:
// 1. 编译时加载(静态分析)
// 2. 异步加载
// 3. 输出的是值的引用
// 4. 支持树摇(Tree Shaking)
模块化最佳实践:
// 1. 模块结构组织
// utils/
// ├── index.js // 统一导出入口
// ├── string.js // 字符串工具
// ├── array.js // 数组工具
// ├── object.js // 对象工具
// └── date.js // 日期工具
// utils/string.js
export function capitalize(str) {
return str.charAt(0).toUpperCase() + str.slice(1);
}
export function camelCase(str) {
return str.replace(/-([a-z])/g, (match, letter) => letter.toUpperCase());
}
export function kebabCase(str) {
return str.replace(/[A-Z]/g, match => `-${match.toLowerCase()}`);
}
// utils/array.js
export function unique(arr) {
return [...new Set(arr)];
}
export function chunk(arr, size) {
const chunks = [];
for (let i = 0; i < arr.length; i += size) {
chunks.push(arr.slice(i, i + size));
}
return chunks;
}
export function flatten(arr) {
return arr.reduce((flat, item) => {
return flat.concat(Array.isArray(item) ? flatten(item) : item);
}, []);
}
// utils/index.js - 统一导出
export * as string from './string.js';
export * as array from './array.js';
export * as object from './object.js';
export * as date from './date.js';
// 使用方式
import { string, array } from './utils/index.js';
console.log(string.capitalize('hello')); // Hello
console.log(array.unique([1, 1, 2, 3, 3])); // [1, 2, 3]
// 2. 条件导入和代码分割
// 懒加载模块
async function loadFeature() {
if (needsAdvancedFeature) {
const { AdvancedFeature } = await import('./advanced-feature.js');
return new AdvancedFeature();
}
return null;
}
// 3. 模块的热更新支持
if (module.hot) {
module.hot.accept('./math.js', function() {
console.log('math模块已更新');
// 重新初始化使用该模块的部分
});
}
// 4. 环境特定的模块
// config/index.js
let config;
if (process.env.NODE_ENV === 'development') {
config = await import('./config.dev.js');
} else if (process.env.NODE_ENV === 'production') {
config = await import('./config.prod.js');
} else {
config = await import('./config.test.js');
}
export default config.default;
模块打包和构建工具:
// webpack.config.js
module.exports = {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js',
library: 'MyLibrary',
libraryTarget: 'umd'
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}
}
]
},
resolve: {
alias: {
'@': path.resolve(__dirname, 'src'),
'utils': path.resolve(__dirname, 'src/utils')
}
},
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all'
},
common: {
name: 'common',
minChunks: 2,
chunks: 'all'
}
}
}
}
};
// 使用Tree Shaking优化
// package.json
{
"name": "my-library",
"version": "1.0.0",
"main": "dist/index.cjs.js",
"module": "dist/index.esm.js",
"sideEffects": false,
"exports": {
".": {
"import": "./dist/index.esm.js",
"require": "./dist/index.cjs.js"
},
"./utils": {
"import": "./dist/utils.esm.js",
"require": "./dist/utils.cjs.js"
}
}
}
面试情景对话:
面试官: "请解释JavaScript模块化的发展历程以及各种模块系统的区别。"
面试者: "JavaScript模块化经历了几个阶段:
- IIFE - 早期通过立即执行函数创建模块作用域
- CommonJS - Node.js使用,同步加载,运行时加载
- AMD - RequireJS实现,异步加载,依赖前置
- UMD - 兼容多种模块系统的通用解决方案
- ES6 Module - 原生支持,编译时加载,支持静态分析"
面试官: "CommonJS和ES6 Module有什么主要区别?"
面试者: "主要区别:
- 加载时机:CommonJS运行时加载,ES6 Module编译时加载
- 加载方式:CommonJS同步,ES6 Module异步
- 输出机制:CommonJS输出值的拷贝,ES6 Module输出值的引用
- 静态分析:ES6 Module支持Tree Shaking,CommonJS不支持
- 循环依赖:处理方式不同,ES6 Module更优雅"
拓展问题:
- 如何解决循环依赖问题?
- Tree Shaking的原理是什么?
- 如何设计一个通用的模块系统?
9. 错误处理与调试
9.1 JavaScript错误类型与处理机制
问题描述: 请详细解释JavaScript中的错误类型,以及如何进行有效的错误处理和调试。
详细解析:
JavaScript错误类型:
// 1. SyntaxError - 语法错误
try {
eval('var a = ;'); // 语法错误
} catch (error) {
console.log(error.name); // SyntaxError
console.log(error.message); // Unexpected token ';'
}
// 2. ReferenceError - 引用错误
try {
console.log(undefinedVariable); // 引用了未定义的变量
} catch (error) {
console.log(error.name); // ReferenceError
console.log(error.message); // undefinedVariable is not defined
}
// 3. TypeError - 类型错误
try {
const obj = null;
obj.property; // 尝试访问null的属性
} catch (error) {
console.log(error.name); // TypeError
console.log(error.message); // Cannot read property 'property' of null
}
// 4. RangeError - 范围错误
try {
const arr = new Array(-1); // 数组长度不能为负数
} catch (error) {
console.log(error.name); // RangeError
console.log(error.message); // Invalid array length
}
// 5. URIError - URI错误
try {
decodeURIComponent('%'); // 无效的URI组件
} catch (error) {
console.log(error.name); // URIError
console.log(error.message); // URI malformed
}
// 6. EvalError - eval错误(现已很少使用)
// 7. 自定义错误
class CustomError extends Error {
constructor(message, code) {
super(message);
this.name = 'CustomError';
this.code = code;
}
}
try {
throw new CustomError('这是自定义错误', 'CUSTOM_001');
} catch (error) {
console.log(error.name); // CustomError
console.log(error.code); // CUSTOM_001
}
错误处理最佳实践:
// 1. 基础的try-catch-finally
function safeDivision(a, b) {
try {
if (typeof a !== 'number' || typeof b !== 'number') {
throw new TypeError('参数必须是数字');
}
if (b === 0) {
throw new Error('除数不能为零');
}
return a / b;
} catch (error) {
console.error('计算错误:', error.message);
return null;
} finally {
console.log('计算完成');
}
}
console.log(safeDivision(10, 2)); // 5
console.log(safeDivision(10, 0)); // null
console.log(safeDivision('10', 2)); // null
// 2. 异步错误处理
// Promise错误处理
async function fetchUserData(userId) {
try {
const response = await fetch(`/api/users/${userId}`);
if (!response.ok) {
throw new Error(`HTTP错误: ${response.status}`);
}
const userData = await response.json();
return userData;
} catch (error) {
if (error instanceof TypeError) {
// 网络错误
console.error('网络连接失败:', error.message);
throw new Error('网络连接失败,请检查网络设置');
} else if (error.message.includes('HTTP错误')) {
// HTTP状态错误
console.error('服务器错误:', error.message);
throw new Error('服务器暂时不可用,请稍后重试');
} else {
// 其他错误
console.error('未知错误:', error.message);
throw new Error('获取用户数据失败');
}
}
}
// Promise链式错误处理
fetchUserData(123)
.then(userData => {
console.log('用户数据:', userData);
})
.catch(error => {
console.error('最终错误处理:', error.message);
// 显示用户友好的错误信息
showErrorMessage(error.message);
});
// 3. 全局错误处理
// 捕获未处理的Promise rejection
window.addEventListener('unhandledrejection', event => {
console.error('未处理的Promise rejection:', event.reason);
// 阻止默认的控制台错误日志
event.preventDefault();
// 发送错误报告
sendErrorReport({
type: 'unhandledrejection',
message: event.reason.message || 'Unknown error',
stack: event.reason.stack,
timestamp: Date.now()
});
});
// 捕获未处理的错误
window.addEventListener('error', event => {
console.error('全局错误:', {
message: event.message,
filename: event.filename,
lineno: event.lineno,
colno: event.colno,
error: event.error
});
sendErrorReport({
type: 'javascript-error',
message: event.message,
filename: event.filename,
lineno: event.lineno,
colno: event.colno,
stack: event.error?.stack,
timestamp: Date.now()
});
});
// 4. 错误边界模式(React风格的错误处理)
class ErrorBoundary {
constructor() {
this.hasError = false;
this.error = null;
}
static wrap(fn) {
return function(...args) {
try {
return fn.apply(this, args);
} catch (error) {
console.error('Error boundary caught:', error);
return null;
}
};
}
catchError(fn) {
try {
return fn();
} catch (error) {
this.hasError = true;
this.error = error;
this.handleError(error);
return null;
}
}
handleError(error) {
// 错误处理逻辑
console.error('Error boundary:', error);
sendErrorReport(error);
}
}
// 使用错误边界
const errorBoundary = new ErrorBoundary();
const safeFunction = ErrorBoundary.wrap(function(data) {
// 可能出错的代码
if (!data || !data.items) {
throw new Error('Invalid data structure');
}
return data.items.map(item => item.value);
});
// 5. 重试机制
class RetryableOperation {
constructor(maxRetries = 3, retryDelay = 1000) {
this.maxRetries = maxRetries;
this.retryDelay = retryDelay;
}
async execute(operation, ...args) {
let lastError;
for (let attempt = 1; attempt <= this.maxRetries; attempt++) {
try {
return await operation(...args);
} catch (error) {
lastError = error;
console.warn(`操作失败 (尝试 ${attempt}/${this.maxRetries}):`, error.message);
if (attempt === this.maxRetries) {
break;
}
// 指数退避策略
const delay = this.retryDelay * Math.pow(2, attempt - 1);
await this.sleep(delay);
}
}
throw new Error(`操作失败,已重试 ${this.maxRetries} 次: ${lastError.message}`);
}
sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
// 使用重试机制
const retryable = new RetryableOperation(3, 1000);
async function unreliableApiCall() {
if (Math.random() < 0.7) {
throw new Error('API调用失败');
}
return { data: '成功响应' };
}
retryable.execute(unreliableApiCall)
.then(result => console.log('成功:', result))
.catch(error => console.error('最终失败:', error.message));
调试技巧和工具:
// 1. 控制台调试
// 基本日志
console.log('普通日志');
console.info('信息日志');
console.warn('警告日志');
console.error('错误日志');
// 表格显示
const users = [
{ name: '张三', age: 25, city: '北京' },
{ name: '李四', age: 30, city: '上海' },
{ name: '王五', age: 28, city: '广州' }
];
console.table(users);
// 分组日志
console.group('用户处理');
console.log('开始处理用户数据');
console.log('验证用户权限');
console.groupEnd();
// 计时
console.time('数据处理');
// ... 执行一些操作
console.timeEnd('数据处理');
// 调用栈跟踪
function funcA() {
funcB();
}
function funcB() {
funcC();
}
function funcC() {
console.trace('调用栈跟踪');
}
funcA();
// 2. 断点调试
function debugExample(data) {
debugger; // 程序会在此处暂停
const processed = data.map(item => {
// 可以在开发者工具中检查变量
return item * 2;
});
return processed;
}
// 3. 性能监控
class PerformanceMonitor {
constructor() {
this.marks = new Map();
this.measures = new Map();
}
start(name) {
performance.mark(`${name}-start`);
this.marks.set(name, Date.now());
}
end(name) {
const startTime = this.marks.get(name);
if (startTime) {
performance.mark(`${name}-end`);
performance.measure(name, `${name}-start`, `${name}-end`);
const endTime = Date.now();
const duration = endTime - startTime;
this.measures.set(name, duration);
console.log(`${name} 耗时: ${duration}ms`);
}
}
getReport() {
const entries = performance.getEntriesByType('measure');
return entries.map(entry => ({
name: entry.name,
duration: entry.duration,
startTime: entry.startTime
}));
}
}
const monitor = new PerformanceMonitor();
monitor.start('数据加载');
// ... 执行数据加载
monitor.end('数据加载');
// 4. 错误报告系统
class ErrorReporter {
constructor(config = {}) {
this.endpoint = config.endpoint || '/api/errors';
this.maxRetries = config.maxRetries || 3;
this.batchSize = config.batchSize || 10;
this.errorQueue = [];
this.isOnline = navigator.onLine;
this.setupEventListeners();
}
setupEventListeners() {
window.addEventListener('online', () => {
this.isOnline = true;
this.flushErrors();
});
window.addEventListener('offline', () => {
this.isOnline = false;
});
// 页面卸载前发送错误
window.addEventListener('beforeunload', () => {
this.flushErrors(true);
});
}
reportError(error, context = {}) {
const errorData = {
message: error.message,
stack: error.stack,
timestamp: Date.now(),
url: window.location.href,
userAgent: navigator.userAgent,
context
};
this.errorQueue.push(errorData);
if (this.isOnline && this.errorQueue.length >= this.batchSize) {
this.flushErrors();
}
}
async flushErrors(synchronous = false) {
if (this.errorQueue.length === 0) return;
const errors = [...this.errorQueue];
this.errorQueue = [];
try {
if (synchronous) {
// 使用sendBeacon确保数据发送
navigator.sendBeacon(this.endpoint, JSON.stringify(errors));
} else {
await fetch(this.endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(errors)
});
}
} catch (error) {
// 发送失败,重新放回队列
this.errorQueue.unshift(...errors);
console.error('错误报告发送失败:', error);
}
}
}
// 全局错误报告器
const errorReporter = new ErrorReporter({
endpoint: '/api/errors',
batchSize: 5
});
// 集成到全局错误处理
window.addEventListener('error', event => {
errorReporter.reportError(event.error, {
type: 'javascript-error',
filename: event.filename,
lineno: event.lineno,
colno: event.colno
});
});
window.addEventListener('unhandledrejection', event => {
errorReporter.reportError(event.reason, {
type: 'unhandled-promise-rejection'
});
});
面试情景对话:
面试官: "JavaScript中有哪些常见的错误类型?如何进行有效的错误处理?"
面试者: "常见错误类型包括:
- SyntaxError - 语法错误
- ReferenceError - 引用错误
- TypeError - 类型错误
- RangeError - 范围错误
- URIError - URI错误
有效的错误处理包括:使用try-catch-finally、Promise错误处理、全局错误监听、错误边界模式、重试机制等。"
面试官: "如何在生产环境中监控和调试JavaScript错误?"
面试者: "生产环境的错误监控包括:
- 全局错误监听器捕获未处理的错误
- 错误报告系统收集错误信息
- 性能监控工具分析性能问题
- 使用Source Map进行错误定位
- 集成第三方监控服务如Sentry、Bugsnag等"
拓展问题:
- 如何设计一个完整的错误监控系统?
- Source Map的原理和使用场景?
- 如何平衡错误处理的性能开销?
10. 高级编程技巧
10.1 函数式编程与设计模式
问题描述: 请详细解释函数式编程在JavaScript中的应用,以及常见的设计模式实现。
详细解析:
函数式编程核心概念:
// 1. 纯函数 - 相同输入总是产生相同输出,无副作用
// 非纯函数示例
let count = 0;
function impureIncrement() {
count++; // 修改外部状态
return count;
}
// 纯函数示例
function pureIncrement(num) {
return num + 1; // 不修改外部状态
}
// 2. 不可变性
const originalArray = [1, 2, 3];
// 错误方式 - 修改原数组
// originalArray.push(4);
// 正确方式 - 创建新数组
const newArray = [...originalArray, 4];
// 深度不可变性
function updateNestedObject(obj, path, value) {
const keys = path.split('.');
const result = JSON.parse(JSON.stringify(obj)); // 深拷贝
let current = result;
for (let i = 0; i < keys.length - 1; i++) {
current = current[keys[i]];
}
current[keys[keys.length - 1]] = value;
return result;
}
// 使用Immutable.js等库提供更好的不可变性支持
// 3. 高阶函数
function withLogging(fn) {
return function(...args) {
console.log(`调用函数 ${fn.name},参数:`, args);
const result = fn.apply(this, args);
console.log(`函数 ${fn.name} 返回:`, result);
return result;
};
}
const add = (a, b) => a + b;
const loggedAdd = withLogging(add);
console.log(loggedAdd(2, 3)); // 会打印调用日志
// 4. 柯里化(Currying)
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
// 5. 组合函数
function compose(...fns) {
return function(value) {
return fns.reduceRight((acc, fn) => fn(acc), value);
};
}
function pipe(...fns) {
return function(value) {
return fns.reduce((acc, fn) => fn(acc), value);
};
}
const addOne = x => x + 1;
const multiplyByTwo = x => x * 2;
const square = x => x * x;
// 从右到左执行
const composed = compose(square, multiplyByTwo, addOne);
console.log(composed(3)); // ((3 + 1) * 2)^2 = 64
// 从左到右执行
const piped = pipe(addOne, multiplyByTwo, square);
console.log(piped(3)); // ((3 + 1) * 2)^2 = 64
// 6. 函子(Functor)和单子(Monad)
class Maybe {
constructor(value) {
this.value = value;
}
static of(value) {
return new Maybe(value);
}
isNothing() {
return this.value === null || this.value === undefined;
}
map(fn) {
return this.isNothing() ? this : Maybe.of(fn(this.value));
}
flatMap(fn) {
return this.isNothing() ? this : fn(this.value);
}
filter(predicate) {
return this.isNothing() || predicate(this.value) ? this : Maybe.of(null);
}
getOrElse(defaultValue) {
return this.isNothing() ? defaultValue : this.value;
}
}
// 使用Maybe处理可能为空的值
const result = Maybe.of('Hello World')
.map(str => str.toUpperCase())
.map(str => str.split(' '))
.filter(arr => arr.length > 1)
.map(arr => arr.join('-'))
.getOrElse('默认值');
console.log(result); // HELLO-WORLD
设计模式实现:
// 1. 单例模式
class Singleton {
constructor() {
if (Singleton.instance) {
return Singleton.instance;
}
this.data = {};
Singleton.instance = this;
}
setData(key, value) {
this.data[key] = value;
}
getData(key) {
return this.data[key];
}
}
// 使用WeakMap实现更安全的单例
const singletonInstances = new WeakMap();
class SecureSingleton {
constructor() {
if (singletonInstances.has(SecureSingleton)) {
return singletonInstances.get(SecureSingleton);
}
this.data = new Map();
singletonInstances.set(SecureSingleton, this);
}
}
// 2. 工厂模式
class CarFactory {
static createCar(type) {
switch (type) {
case 'sedan':
return new Sedan();
case 'suv':
return new SUV();
case 'truck':
return new Truck();
default:
throw new Error(`未知的车型: ${type}`);
}
}
}
class Car {
constructor(type) {
this.type = type;
}
start() {
console.log(`${this.type} 启动了`);
}
}
class Sedan extends Car {
constructor() {
super('轿车');
}
}
class SUV extends Car {
constructor() {
super('SUV');
}
}
// 抽象工厂模式
class AbstractVehicleFactory {
createCar() {
throw new Error('必须实现createCar方法');
}
createMotorcycle() {
throw new Error('必须实现createMotorcycle方法');
}
}
class LuxuryVehicleFactory extends AbstractVehicleFactory {
createCar() {
return new LuxuryCar();
}
createMotorcycle() {
return new LuxuryMotorcycle();
}
}
// 3. 观察者模式
class EventEmitter {
constructor() {
this.events = new Map();
}
on(event, callback) {
if (!this.events.has(event)) {
this.events.set(event, []);
}
this.events.get(event).push(callback);
// 返回取消订阅函数
return () => this.off(event, callback);
}
off(event, callback) {
if (this.events.has(event)) {
const callbacks = this.events.get(event);
const index = callbacks.indexOf(callback);
if (index > -1) {
callbacks.splice(index, 1);
}
}
}
emit(event, ...args) {
if (this.events.has(event)) {
this.events.get(event).forEach(callback => {
try {
callback(...args);
} catch (error) {
console.error('事件处理错误:', error);
}
});
}
}
once(event, callback) {
const onceWrapper = (...args) => {
callback(...args);
this.off(event, onceWrapper);
};
this.on(event, onceWrapper);
}
}
// 使用观察者模式
const emitter = new EventEmitter();
const unsubscribe = emitter.on('userLogin', (user) => {
console.log(`用户 ${user.name} 登录了`);
});
emitter.emit('userLogin', { name: '张三' });
unsubscribe(); // 取消订阅
// 4. 策略模式
class PaymentContext {
constructor(strategy) {
this.strategy = strategy;
}
setStrategy(strategy) {
this.strategy = strategy;
}
executePayment(amount) {
return this.strategy.pay(amount);
}
}
class CreditCardPayment {
constructor(cardNumber) {
this.cardNumber = cardNumber;
}
pay(amount) {
console.log(`使用信用卡 ${this.cardNumber} 支付 ${amount} 元`);
return { success: true, method: 'credit-card' };
}
}
class AlipayPayment {
pay(amount) {
console.log(`使用支付宝支付 ${amount} 元`);
return { success: true, method: 'alipay' };
}
}
class WeChatPayment {
pay(amount) {
console.log(`使用微信支付 ${amount} 元`);
return { success: true, method: 'wechat' };
}
}
// 使用策略模式
const payment = new PaymentContext(new CreditCardPayment('1234-5678-9012-3456'));
payment.executePayment(100);
payment.setStrategy(new AlipayPayment());
payment.executePayment(200);
// 5. 装饰器模式
function measure(target, propertyName, descriptor) {
const originalMethod = descriptor.value;
descriptor.value = function(...args) {
const start = performance.now();
const result = originalMethod.apply(this, args);
const end = performance.now();
console.log(`${propertyName} 执行耗时: ${end - start}ms`);
return result;
};
return descriptor;
}
function cache(target, propertyName, descriptor) {
const originalMethod = descriptor.value;
const cacheMap = new Map();
descriptor.value = function(...args) {
const key = JSON.stringify(args);
if (cacheMap.has(key)) {
console.log('从缓存返回结果');
return cacheMap.get(key);
}
const result = originalMethod.apply(this, args);
cacheMap.set(key, result);
return result;
};
return descriptor;
}
class Calculator {
@measure
@cache
fibonacci(n) {
if (n <= 1) return n;
return this.fibonacci(n - 1) + this.fibonacci(n - 2);
}
}
// 6. 代理模式
class ImageProxy {
constructor(url) {
this.url = url;
this.image = null;
this.loaded = false;
}
async display() {
if (!this.loaded) {
console.log('加载图片中...');
await this.loadImage();
}
console.log(`显示图片: ${this.url}`);
return this.image;
}
async loadImage() {
return new Promise((resolve) => {
setTimeout(() => {
this.image = `图片数据: ${this.url}`;
this.loaded = true;
console.log('图片加载完成');
resolve();
}, 1000);
});
}
}
// 使用Proxy实现属性访问代理
function createValidatedObject(target, validators) {
return new Proxy(target, {
set(obj, prop, value) {
if (validators[prop]) {
const isValid = validators[prop](value);
if (!isValid) {
throw new Error(`属性 ${prop} 的值 ${value} 不符合验证规则`);
}
}
obj[prop] = value;
return true;
},
get(obj, prop) {
if (prop in obj) {
return obj[prop];
} else {
throw new Error(`属性 ${prop} 不存在`);
}
}
});
}
const user = createValidatedObject({}, {
age: value => typeof value === 'number' && value > 0 && value < 150,
email: value => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)
});
user.age = 25; // 正常
user.email = 'test@example.com'; // 正常
// user.age = -5; // 抛出错误
面试情景对话:
面试官: "请解释函数式编程的核心概念以及在JavaScript中的应用。"
面试者: "函数式编程的核心概念包括:
- 纯函数 - 无副作用,相同输入产生相同输出
- 不可变性 - 不修改原始数据,总是返回新数据
- 高阶函数 - 函数作为参数或返回值
- 函数组合 - 将简单函数组合成复杂功能
- 柯里化 - 将多参数函数转换为单参数函数序列
在JavaScript中,可以通过Array的map、filter、reduce等方法,以及函数组合、柯里化等技术来实现函数式编程。"
面试官: "常见的设计模式有哪些?请举例说明其应用场景。"
面试者: "常见设计模式包括:
- 单例模式 - 确保类只有一个实例,如全局配置管理
- 工厂模式 - 创建对象而不暴露创建逻辑,如组件工厂
- 观察者模式 - 对象间一对多依赖关系,如事件系统
- 策略模式 - 算法族,可互换,如支付方式选择
- 装饰器模式 - 动态添加对象功能,如日志、缓存装饰器
- 代理模式 - 控制对象访问,如虚拟代理、缓存代理"
拓展问题:
- 如何在大型项目中应用函数式编程?
- 设计模式的选择原则是什么?
- 如何平衡代码的抽象程度和可读性?
总结
本文从专业前端工程师的角度,深入解析了2025年JavaScript面试中的核心技术点。每个章节都包含了详细的代码示例、实际应用场景以及面试官与面试者的对话模拟,帮助读者全面掌握JavaScript的核心概念和高级特性。
主要覆盖内容:
- 基础夯实 - 数据类型、闭包、原型链等核心概念
- 异步精通 - 事件循环、Promise、async/await深度理解
- 现代特性 - ES6+新语法的掌握和应用
- 性能优化 - 内存管理、垃圾回收机制
- 工程化思维 - 模块化、错误处理、调试技巧
- 高级技巧 - 函数式编程、设计模式实践
面试建议:
- 深度理解底层原理,不仅知道"是什么",更要知道"为什么"
- 结合实际项目经验,能够举出具体的应用场景
- 保持对新技术的敏感度,了解JavaScript生态的发展趋势
- 注重代码质量,能够写出优雅、可维护的代码
希望这份面试题集能够帮助你在JavaScript技术面试中脱颖而出!
本文档持续更新中,欢迎反馈和建议!