Published on
· 59 min read

2025JavaScript面试题

Authors
  • avatar
    Name
    felixDu
    Twitter
Table of Contents

2025 JavaScript 面试题

目录

  1. 基础数据类型与类型判断
  2. 闭包与作用域
  3. 原型与原型链
  4. 事件循环与异步编程
  5. Promise与async/await
  6. ES6+新特性
  7. 性能优化与内存管理
  8. 模块化与工程化
  9. 错误处理与调试
  10. 高级编程技巧

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有什么局限性吗?"

面试者: "有三种主要方法:

  1. typeof操作符适用于基本类型,但对null返回'object',对数组也返回'object'
  2. instanceof用于判断引用类型的具体构造函数
  3. 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())这种深拷贝方法有什么问题?"

面试者: "这种方法有几个局限性:

  1. 无法处理函数、undefined、Symbol等类型
  2. 无法处理Date、RegExp等特殊对象
  3. 无法处理循环引用,会报错
  4. 会忽略对象的原型链 因此只适合处理简单的、纯数据对象。"

面试官: "如何处理循环引用的问题?"

面试者: "可以使用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);
}

面试情景对话:

面试官: "请解释一下什么是闭包?"

面试者: "闭包是指有权访问另一个函数作用域中变量的函数。当一个内部函数引用了外部函数的变量时,即使外部函数执行完毕,这些变量仍然会被保存在内存中,内部函数仍然可以访问它们。"

面试官: "闭包有什么实际应用?"

面试者: "闭包有很多应用场景:

  1. 数据私有化,创建私有变量和方法
  2. 函数柯里化,实现参数复用
  3. 模块化模式,创建命名空间
  4. 事件处理中保存状态
  5. 实现防抖和节流函数"

面试官: "闭包可能带来什么问题?如何避免?"

面试者: "闭包可能导致内存泄漏,因为被引用的变量无法被垃圾回收。避免方法包括:

  1. 及时解除不必要的引用
  2. 避免在闭包中引用DOM元素
  3. 在不需要时将变量设置为null
  4. 使用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解构赋值的作用和优势。"

面试者: "解构赋值允许我们从数组或对象中提取值,赋值给变量,使代码更简洁易读。优势包括:

  1. 减少代码量,提高可读性
  2. 支持默认值,增强代码健壮性
  3. 可以轻松交换变量值
  4. 函数参数解构,让API更清晰"

面试官: "扩展运算符有哪些实际应用场景?"

面试者: "扩展运算符有很多应用:

  1. 数组和对象的浅拷贝与合并
  2. 函数参数的展开传递
  3. 数组去重(配合Set)
  4. React中的props传递
  5. 将类数组对象转为真正的数组"

拓展问题:

  • 解构赋值的性能如何?
  • 如何深度解构嵌套对象?
  • 扩展运算符和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都是唯一的。主要特点包括:

  1. 不可变性和唯一性
  2. 不能被隐式转换
  3. 不会出现在for...in循环中 主要用于创建对象的唯一属性键,避免属性名冲突,实现对象的私有属性等。"

面试官: "WeakMap和Map有什么区别?各自的应用场景是什么?"

面试者: "主要区别:

  1. WeakMap只能使用对象作为键,Map可以使用任何类型
  2. WeakMap的键是弱引用,有利于垃圾回收
  3. 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使用自动垃圾回收,主要算法是标记清除。从根对象开始标记所有可达对象,然后清除未标记的对象。现代引擎还使用了分代回收、增量回收等优化技术。"

面试官: "常见的内存泄漏场景有哪些?如何避免?"

面试者: "常见场景包括:

  1. 全局变量滥用 - 使用严格模式,及时清理
  2. 事件监听器未移除 - 组件销毁时removeEventListener
  3. 定时器未清理 - 使用clearInterval/clearTimeout
  4. 闭包引用大对象 - 只保留必要数据
  5. 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模块化经历了几个阶段:

  1. IIFE - 早期通过立即执行函数创建模块作用域
  2. CommonJS - Node.js使用,同步加载,运行时加载
  3. AMD - RequireJS实现,异步加载,依赖前置
  4. UMD - 兼容多种模块系统的通用解决方案
  5. ES6 Module - 原生支持,编译时加载,支持静态分析"

面试官: "CommonJS和ES6 Module有什么主要区别?"

面试者: "主要区别:

  1. 加载时机:CommonJS运行时加载,ES6 Module编译时加载
  2. 加载方式:CommonJS同步,ES6 Module异步
  3. 输出机制:CommonJS输出值的拷贝,ES6 Module输出值的引用
  4. 静态分析:ES6 Module支持Tree Shaking,CommonJS不支持
  5. 循环依赖:处理方式不同,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中有哪些常见的错误类型?如何进行有效的错误处理?"

面试者: "常见错误类型包括:

  1. SyntaxError - 语法错误
  2. ReferenceError - 引用错误
  3. TypeError - 类型错误
  4. RangeError - 范围错误
  5. URIError - URI错误

有效的错误处理包括:使用try-catch-finally、Promise错误处理、全局错误监听、错误边界模式、重试机制等。"

面试官: "如何在生产环境中监控和调试JavaScript错误?"

面试者: "生产环境的错误监控包括:

  1. 全局错误监听器捕获未处理的错误
  2. 错误报告系统收集错误信息
  3. 性能监控工具分析性能问题
  4. 使用Source Map进行错误定位
  5. 集成第三方监控服务如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中的应用。"

面试者: "函数式编程的核心概念包括:

  1. 纯函数 - 无副作用,相同输入产生相同输出
  2. 不可变性 - 不修改原始数据,总是返回新数据
  3. 高阶函数 - 函数作为参数或返回值
  4. 函数组合 - 将简单函数组合成复杂功能
  5. 柯里化 - 将多参数函数转换为单参数函数序列

在JavaScript中,可以通过Array的map、filter、reduce等方法,以及函数组合、柯里化等技术来实现函数式编程。"

面试官: "常见的设计模式有哪些?请举例说明其应用场景。"

面试者: "常见设计模式包括:

  1. 单例模式 - 确保类只有一个实例,如全局配置管理
  2. 工厂模式 - 创建对象而不暴露创建逻辑,如组件工厂
  3. 观察者模式 - 对象间一对多依赖关系,如事件系统
  4. 策略模式 - 算法族,可互换,如支付方式选择
  5. 装饰器模式 - 动态添加对象功能,如日志、缓存装饰器
  6. 代理模式 - 控制对象访问,如虚拟代理、缓存代理"

拓展问题:

  • 如何在大型项目中应用函数式编程?
  • 设计模式的选择原则是什么?
  • 如何平衡代码的抽象程度和可读性?

总结

本文从专业前端工程师的角度,深入解析了2025年JavaScript面试中的核心技术点。每个章节都包含了详细的代码示例、实际应用场景以及面试官与面试者的对话模拟,帮助读者全面掌握JavaScript的核心概念和高级特性。

主要覆盖内容:

  1. 基础夯实 - 数据类型、闭包、原型链等核心概念
  2. 异步精通 - 事件循环、Promise、async/await深度理解
  3. 现代特性 - ES6+新语法的掌握和应用
  4. 性能优化 - 内存管理、垃圾回收机制
  5. 工程化思维 - 模块化、错误处理、调试技巧
  6. 高级技巧 - 函数式编程、设计模式实践

面试建议:

  • 深度理解底层原理,不仅知道"是什么",更要知道"为什么"
  • 结合实际项目经验,能够举出具体的应用场景
  • 保持对新技术的敏感度,了解JavaScript生态的发展趋势
  • 注重代码质量,能够写出优雅、可维护的代码

希望这份面试题集能够帮助你在JavaScript技术面试中脱颖而出!


本文档持续更新中,欢迎反馈和建议!