Z
ZHANK
🟨

JavaScript 面试题

20 道题 · 4 个模块

简单 8中等 9困难 3

基础概念

7
Q1.var、let、const 的区别?
简单
参考答案
| | var | let | const |
|---|-----|-----|-------|
| 作用域 | 函数作用域 | 块级作用域 | 块级作用域 |
| 变量提升 | ✅ (undefined) | ✅ (TDZ) | ✅ (TDZ) |
| 重复声明 | ✅ | ❌ | ❌ |
| 重新赋值 | ✅ | ✅ | ❌ |
| 必须初始化 | ❌ | ❌ | ✅ |

**最佳实践**:默认用 `const`,需要重新赋值时用 `let`,禁用 `var`。
Q2.== 和 === 的区别?
简单
参考答案
`==` 进行比较时做**类型转换**(如 `'5' == 5` 为 true)。`===` **严格比较**,类型不同直接返回 false。
javascript
0 == false   // true  (都转为 0)
0 === false  // false (类型不同)
null == undefined  // true
null === undefined // false

**永远优先用 ===**,避免隐式转换带来的 bug。
Q3.闭包是什么?举个例子
简单
参考答案
闭包是**函数 + 其外部变量的引用**——即使外部函数已执行完毕,内部函数仍能访问外部变量。
javascript
function createCounter() {
    let count = 0;
    return function() {
        return ++count;  // 访问外部 count
    };
}
const counter = createCounter();
counter(); // 1
counter(); // 2

常用于:数据封装、回调、柯里化、模块模式。
Q4.null 和 undefined 的区别?
简单
参考答案
`undefined` 表示**变量已声明但未赋值**——JavaScript 引擎自动赋值。`null` 表示**故意赋值为空**——开发者显式设置。
javascript
let a;
console.log(a);  // undefined(自动)

let b = null;    // null(手动)

typeof undefined  // 'undefined'
typeof null       // 'object' (历史遗留 bug)
Q5.JavaScript 中的 this 指向规则?
简单
参考答案
`this` 的值取决于**调用方式**:
1. 普通函数调用 → `this` = `undefined`(严格模式)/ `window`
2. 方法调用 → `this` = 调用该方法的对象
3. 构造函数(new)→ `this` = 新创建的对象
4. call/apply/bind → `this` = 指定的对象
5. 箭头函数 → `this` = 定义时外层作用域的 `this`

javascript
const obj = { name: '小明', greet() { console.log(this.name); } };
obj.greet();        // '小明'(方法调用)
const f = obj.greet;
f();                // undefined(普通函数调用)
Q6.JavaScript 中的类型转换(隐式转换)规则?
简单
参考答案
javascript
// 字符串拼接
1 + '2'     // '12'(数字→字符串)
'1' + 2     // '12'

// 算术运算
'5' - 2     // 3(字符串→数字)
'5' * '2'   // 10

// 布尔转换
!!''        // false(假值:0, '', null, undefined, NaN, false)
!!'hello'   // true

// == 比较
0 == false  // true
null == undefined  // true

避免隐式转换的坑——用 `===` 和显式转换(`Number()`, `String()`, `Boolean()`)。
Q7.JavaScript 的模块化发展历程?
中等
参考答案
1. **全局函数/IIFE**(最早)——命名冲突
2. **CommonJS**(Node.js, 2009)——`require`/`module.exports`,同步加载
3. **AMD**(RequireJS, 2011)——异步加载,浏览器优先
4. **UMD**——兼容 CommonJS + AMD
5. **ES Modules**(ES6, 2015)——`import`/`export`,静态分析,现代标准

现在:Node.js 和浏览器都原生支持 ESM。打包工具(Webpack/Vite)处理模块转换。

ES6+

6
Q1.箭头函数和普通函数的区别?
中等
参考答案
1. **this 绑定**:箭头函数没有自己的 `this`——继承外层作用域的 `this`。普通函数的 `this` 取决于调用方式。
2. **不能用作构造函数**:不能用 `new` 调用箭头函数
3. **没有 arguments 对象**:需用 rest 参数 `(...args)` 替代
4. **不能用作 generator**:不能使用 `yield`
5. **语法更简洁**:单表达式可省略 `{}` 和 `return`
Q2.解构赋值和展开运算符
中等
参考答案
javascript
// 数组解构
const [a, b, ...rest] = [1, 2, 3, 4, 5];
// a=1, b=2, rest=[3,4,5]

// 对象解构
const { name, age: userAge = 18 } = { name: '小明' };
// name='小明', userAge=18

// 展开——合并
const arr = [...arr1, ...arr2];
const obj = { ...obj1, ...obj2 };

// 函数参数
function sum(...numbers) { return numbers.reduce((a,b) => a+b); }
Q3.JavaScript 的原型链和继承
困难
参考答案
JavaScript 使用**原型继承**——每个对象有一个内部 `[[Prototype]]` 链接(通过 `__proto__` 或 `Object.getPrototypeOf()`)。查找属性时沿原型链向上查找。
javascript
function Animal(name) { this.name = name; }
Animal.prototype.speak = function() { return `${this.name} 叫了一声`; };

function Dog(name) { Animal.call(this, name); }
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;

// ES6 class 是语法糖
class Cat extends Animal {
    speak() { return `${this.name} 喵喵叫`; }
}
Q4.Map 和 Object 的区别?什么时候用 Map?
中等
参考答案
| | Map | Object |
|---|-----|--------|
| 键类型 | 任意类型 | 字符串/Symbol |
| 顺序 | 插入顺序 | 不一定 |
| 大小 | map.size | Object.keys(obj).length |
| 迭代 | 直接 for...of | 需要 Object.entries() |
| 性能 | 频繁增删更快 | 少量固定键更快 |

**用 Map**:键不是字符串、需要保持顺序、频繁增删。**用 Object**:JSON 序列化、少量固定键。
Q5.ES Module 和 CommonJS 的区别?
中等
参考答案
| | ES Module | CommonJS |
|---|-----------|----------|
| 语法 | import/export | require/module.exports |
| 加载 | 异步(编译时) | 同步(运行时) |
| 导出 | 静态(编译时确定) | 动态 |
| this | undefined | module.exports |
| Tree Shaking | ✅ | ❌ |
| 浏览器 | ✅ | ❌ 需打包 |

Node.js 中 .mjs 或 package.json type:module 启用 ESM。
Q6.Set 和 WeakSet 的区别?Map 和 WeakMap 的区别?
中等
参考答案
**WeakSet/WeakMap** 的 key 必须是对象,且是**弱引用**——不影响垃圾回收。

javascript
// WeakMap 典型用途:私有数据
const privates = new WeakMap();
class MyClass {
    constructor() { privates.set(this, { secret: 'data' }); }
}
// 当 MyClass 实例被回收,privates 中的数据自动释放

WeakMap 不可迭代,没有 size。适合 DOM 节点关联数据、缓存。

异步编程

4
Q1.Promise 的三种状态?async/await 怎么用?
中等
参考答案
Promise 有三种状态:**pending**(进行中)、**fulfilled**(成功)、**rejected**(失败)。状态不可逆。
javascript
async function fetchUser(id) {
    try {
        const res = await fetch(`/api/users/${id}`);
        const data = await res.json();
        return data;
    } catch (error) {
        console.error('获取失败', error);
    }
}

`async` 函数始终返回 Promise,`await` 等待 Promise 结果。
Q2.事件循环(Event Loop)是什么?宏任务和微任务?
中等
参考答案
JavaScript 是单线程的,事件循环是其异步机制:
1. 执行同步代码
2. 清空**微任务队列**(Promise.then、queueMicrotask、MutationObserver)
3. 取一个**宏任务**(setTimeout、setInterval、I/O、事件回调)
4. 重复

javascript
console.log(1);
setTimeout(() => console.log(2), 0);
Promise.resolve().then(() => console.log(3));
console.log(4);
// 输出: 1, 4, 3, 2

微任务优先级高于宏任务。
Q3.Promise.all、Promise.race、Promise.allSettled、Promise.any 的区别?
困难
参考答案
javascript
Promise.all([p1,p2])       // 全成功→结果数组;一个失败→立即 reject
Promise.allSettled([p1,p2])// 全结束→每个的结果(含 status)
Promise.race([p1,p2])      // 第一个结束(不管成功失败)
Promise.any([p1,p2])       // 第一个成功;全失败→AggregateError

| | 失败行为 | 返回时机 |
|---|---------|--------|
| all | 一个失败全失败 | 全成功 |
| allSettled | 不失败 | 全结束 |
| race | 跟第一个结果 | 第一个结束 |
| any | 全失败才失败 | 第一个成功 |
Q4.如何实现一个带并发限制的 Promise 调度器?
困难
参考答案
javascript
class PromisePool {
    constructor(limit) {
        this.limit = limit;
        this.running = 0;
        this.queue = [];
    }
    add(fn) {
        return new Promise((resolve, reject) => {
            this.queue.push({ fn, resolve, reject });
            this.run();
        });
    }
    run() {
        while (this.running < this.limit && this.queue.length) {
            const { fn, resolve, reject } = this.queue.shift();
            this.running++;
            fn().then(resolve, reject).finally(() => {
                this.running--;
                this.run();
            });
        }
    }
}

使用场景:批量 API 请求限流、文件并发处理。

DOM/BOM

3
Q1.事件冒泡和事件捕获的区别?如何阻止?
简单
参考答案
DOM 事件传播三个阶段:捕获→目标→冒泡。`stopPropagation()` 阻止传播,`preventDefault()` 阻止默认行为。`addEventListener` 第三个参数 true 表示捕获阶段。
Q2.防抖(debounce)和节流(throttle)的区别?实现一个?
中等
参考答案
**防抖**:连续触发只执行最后一次(如搜索框输入后 300ms 无输入才搜索)。**节流**:固定时间间隔最多执行一次(如滚动事件每 100ms 最多触发一次)。

javascript
function debounce(fn, delay) {
    let timer;
    return function(...args) {
        clearTimeout(timer);
        timer = setTimeout(() => fn.apply(this, args), delay);
    };
}

function throttle(fn, limit) {
    let inThrottle;
    return function(...args) {
        if (!inThrottle) {
            fn.apply(this, args);
            inThrottle = true;
            setTimeout(() => inThrottle = false, limit);
        }
    };
}
Q3.什么是事件委托(Event Delegation)?有什么好处?
简单
参考答案
事件委托利用**事件冒泡**——把事件监听器绑在父元素上,通过 `event.target` 判断实际点击的元素。