JavaScript面试题汇总
2026年最新版 - 涵盖数据类型、作用域闭包、原型链、异步编程、ES2024/2025新特性与手写代码
📋 目录
数据类型与类型判断
1. JavaScript有哪些数据类型?
8种数据类型,分为基本类型和引用类型:
| 分类 | 类型 | typeof 返回值 |
|---|---|---|
| 基本类型 | number, string, boolean, undefined, null, symbol, bigint | 对应字符串(null除外) |
| 引用类型 | object(包括Array、Function、Date等) | "object" 或 "function" |
javascript
// 基本类型存储在栈中,按值访问
let a = 10;
let b = a; // 复制值
b = 20;
console.log(a); // 10(不受影响)
// 引用类型存储在堆中,按引用访问
let obj1 = { name: '张三' };
let obj2 = obj1; // 复制引用
obj2.name = '李四';
console.log(obj1.name); // '李四'(被修改了)
// bigint - 处理大整数
const big = 9007199254740993n;
console.log(typeof big); // 'bigint'2. 如何准确判断数据类型?
javascript
// 1. typeof - 适合判断基本类型(null除外)
typeof 42; // 'number'
typeof 'hello'; // 'string'
typeof true; // 'boolean'
typeof undefined; // 'undefined'
typeof Symbol(); // 'symbol'
typeof 42n; // 'bigint'
typeof null; // 'object' ⚠️ 历史遗留bug
typeof {}; // 'object'
typeof []; // 'object' ⚠️ 无法区分数组
typeof function(){}; // 'function'
// 2. instanceof - 判断原型链
[] instanceof Array; // true
{} instanceof Object; // true
// ⚠️ 跨iframe失效,基本类型无法判断
// 3. Object.prototype.toString.call() - 最准确
Object.prototype.toString.call(42); // '[object Number]'
Object.prototype.toString.call('hi'); // '[object String]'
Object.prototype.toString.call(null); // '[object Null]'
Object.prototype.toString.call([]); // '[object Array]'
Object.prototype.toString.call({}); // '[object Object]'
Object.prototype.toString.call(/regex/); // '[object RegExp]'
// 4. 特定判断方法
Array.isArray([]); // true
Number.isNaN(NaN); // true
Number.isFinite(42); // true3. == 和 === 的区别?类型转换规则?
===严格相等:不进行类型转换,类型和值都相同才为true==宽松相等:会进行隐式类型转换
javascript
// === 严格相等
1 === 1; // true
1 === '1'; // false
null === undefined; // false
// == 隐式转换规则
null == undefined; // true(特殊规则)
null == 0; // false
'' == 0; // true(字符串转数字)
'1' == 1; // true
true == 1; // true(布尔转数字)
[] == ''; // true(数组→字符串→'')
[] == 0; // true
[1] == 1; // true
// ⚠️ 经典陷阱
NaN == NaN; // false(NaN不等于任何值)
NaN === NaN; // false
Object.is(NaN, NaN); // true ✅ 推荐
// Object.is() vs ===
Object.is(+0, -0); // false(=== 返回 true)
Object.is(NaN, NaN); // true (=== 返回 false)4. 深拷贝和浅拷贝的区别?
javascript
// 浅拷贝 - 只复制第一层
const original = { a: 1, b: { c: 2 } };
// 浅拷贝方法
const shallow1 = { ...original };
const shallow2 = Object.assign({}, original);
shallow1.b.c = 99;
console.log(original.b.c); // 99(嵌套对象被修改)
// 深拷贝方法
// ✅ 方法1: structuredClone()(2026推荐)
const deep1 = structuredClone(original);
// 方法2: JSON(有局限性)
const deep2 = JSON.parse(JSON.stringify(original));
// ⚠️ 无法处理: undefined、函数、Symbol、循环引用、Date、RegExp、Map、Set
// structuredClone vs JSON.parse
const complex = {
date: new Date(),
map: new Map([['key', 'value']]),
set: new Set([1, 2, 3]),
nested: { deep: { value: 42 } }
};
const cloned = structuredClone(complex);
// ✅ Date、Map、Set、循环引用都能正确复制
// ⚠️ 不能复制: 函数、DOM节点、Symbol属性作用域与闭包
5. var、let、const的区别?
| 特性 | var | let | const |
|---|---|---|---|
| 作用域 | 函数作用域 | 块级作用域 | 块级作用域 |
| 变量提升 | ✅ 提升并初始化为undefined | ✅ 提升但不初始化(暂时性死区) | ✅ 提升但不初始化 |
| 重复声明 | ✅ | ❌ | ❌ |
| 重新赋值 | ✅ | ✅ | ❌ |
| 全局属性 | ✅ window.x | ❌ | ❌ |
javascript
// 变量提升 vs 暂时性死区(TDZ)
console.log(a); // undefined(var提升)
var a = 1;
console.log(b); // ❌ ReferenceError(暂时性死区)
let b = 2;
// 块级作用域
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 0);
}
// 输出: 3, 3, 3(var是函数作用域,共享同一个i)
for (let j = 0; j < 3; j++) {
setTimeout(() => console.log(j), 0);
}
// 输出: 0, 1, 2(let是块级作用域,每次循环创建新的j)
// const 的"不可变"
const obj = { name: '张三' };
obj.name = '李四'; // ✅ 可以修改属性
obj = {}; // ❌ TypeError(不能重新赋值)6. 什么是闭包?有哪些应用场景?
闭包是指函数能够访问其词法作用域中的变量,即使该函数在作用域外执行。
javascript
// 闭包的本质
function outer() {
let count = 0; // 自由变量
return function inner() {
count++;
return count;
};
}
const counter = outer();
console.log(counter()); // 1
console.log(counter()); // 2(count被闭包保持在内存中)
// 应用1: 数据封装 / 模块模式
function createWallet(initial) {
let balance = initial; // 私有变量
return {
deposit(amount) { balance += amount; },
withdraw(amount) {
if (amount > balance) throw new Error('余额不足');
balance -= amount;
},
getBalance() { return balance; }
};
}
const wallet = createWallet(100);
wallet.deposit(50);
console.log(wallet.getBalance()); // 150
// wallet.balance → undefined(外部无法直接访问)
// 应用2: 函数柯里化
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn.apply(this, args);
}
return (...moreArgs) => curried(...args, ...moreArgs);
};
}
const add = curry((a, b, c) => a + b + c);
console.log(add(1)(2)(3)); // 6
console.log(add(1, 2)(3)); // 6
// 应用3: 防抖
function debounce(fn, delay) {
let timer = null; // 闭包保持timer引用
return function (...args) {
clearTimeout(timer);
timer = setTimeout(() => fn.apply(this, args), delay);
};
}
// ⚠️ 闭包陷阱 - 内存泄漏
function leakyFunction() {
const largeData = new Array(1000000).fill('x');
return function() {
// 即使不使用largeData,闭包仍持有引用
console.log('hello');
};
}
// 解决:不需要的大对象及时设为null7. 作用域链是什么?
作用域链是JavaScript查找变量时的链式查找机制,从当前作用域逐级向外查找直到全局作用域。
javascript
const global = 'global';
function outer() {
const outerVar = 'outer';
function middle() {
const middleVar = 'middle';
function inner() {
const innerVar = 'inner';
// 作用域链: inner → middle → outer → global
console.log(innerVar); // 'inner'(当前作用域)
console.log(middleVar); // 'middle'(上一级)
console.log(outerVar); // 'outer'(再上一级)
console.log(global); // 'global'(全局作用域)
}
inner();
}
middle();
}
// 词法作用域(静态作用域):作用域在函数定义时确定
let x = 10;
function foo() {
console.log(x); // 10(在定义时确定,不是调用时)
}
function bar() {
let x = 20;
foo(); // 仍然输出10
}
bar();原型与继承
8. 原型链是什么?如何工作?
每个对象都有一个内部属性 [[Prototype]](通过 __proto__ 或 Object.getPrototypeOf() 访问),指向它的原型对象。访问属性时,如果对象本身没有,就沿着原型链向上查找。
javascript
function Person(name) {
this.name = name;
}
Person.prototype.sayHi = function() {
return `Hi, I'm ${this.name}`;
};
const p = new Person('张三');
// 原型链
p.__proto__ === Person.prototype; // true
Person.prototype.__proto__ === Object.prototype; // true
Object.prototype.__proto__ === null; // true(链的终点)
// 属性查找顺序
p.name; // '张三'(自身属性)
p.sayHi(); // 'Hi, I'm 张三'(原型上的方法)
p.toString(); // '[object Object]'(Object.prototype上的方法)
// 判断属性位置
p.hasOwnProperty('name'); // true(自身属性)
p.hasOwnProperty('sayHi'); // false(原型上的)
'sayHi' in p; // true(包括原型链)
// 关键关系
// 实例.__proto__ === 构造函数.prototype
// 构造函数.prototype.constructor === 构造函数
// Function.__proto__ === Function.prototype(特殊)9. ES6 class 和原型继承的区别?
javascript
// ES6 class(语法糖,本质仍是原型继承)
class Animal {
// 私有字段(2026已广泛支持)
#name;
constructor(name) {
this.#name = name;
}
// 公有方法
speak() {
return `${this.#name} makes a sound`;
}
// 静态方法
static create(name) {
return new Animal(name);
}
// getter/setter
get name() { return this.#name; }
set name(val) { this.#name = val; }
}
// 继承
class Dog extends Animal {
#breed;
constructor(name, breed) {
super(name); // 必须先调用super
this.#breed = breed;
}
speak() {
return `${this.name} barks`; // 方法覆盖
}
}
const dog = new Dog('旺财', '柴犬');
console.log(dog.speak()); // '旺财 barks'
console.log(dog instanceof Dog); // true
console.log(dog instanceof Animal); // trueclass vs 构造函数的区别:
class声明不会提升(暂时性死区)class内部自动严格模式class方法不可枚举class必须用new调用- 支持
#私有字段和方法
10. 实现继承有哪些方式?
javascript
// 1. 原型链继承
function Parent() { this.colors = ['red']; }
function Child() {}
Child.prototype = new Parent();
// ⚠️ 所有实例共享引用类型属性
// 2. 构造函数继承
function Child() {
Parent.call(this); // 继承属性
}
// ⚠️ 无法继承原型上的方法
// 3. 组合继承(最常用的经典方式)
function Child(name) {
Parent.call(this, name); // 继承属性
}
Child.prototype = new Parent(); // 继承方法
Child.prototype.constructor = Child; // 修复constructor
// ⚠️ Parent被调用两次
// 4. 寄生组合继承(最佳方案)
function Child(name) {
Parent.call(this, name);
}
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;
// 5. ES6 class 继承(2026推荐 ✅)
class Child extends Parent {
constructor(name) {
super(name);
}
}this与执行上下文
11. this的指向规则?
javascript
// 规则1: 默认绑定 - 独立调用指向全局(严格模式为undefined)
function foo() { console.log(this); }
foo(); // window(非严格模式)/ undefined(严格模式)
// 规则2: 隐式绑定 - 谁调用指向谁
const obj = {
name: '张三',
greet() { console.log(this.name); }
};
obj.greet(); // '张三'
const fn = obj.greet;
fn(); // undefined(隐式绑定丢失 ⚠️)
// 规则3: 显式绑定 - call/apply/bind
function greet(greeting) {
console.log(`${greeting}, ${this.name}`);
}
greet.call({ name: '张三' }, 'Hello'); // 'Hello, 张三'
greet.apply({ name: '李四' }, ['Hi']); // 'Hi, 李四'
const bound = greet.bind({ name: '王五' });
bound('Hey'); // 'Hey, 王五'
// 规则4: new 绑定
function Person(name) { this.name = name; }
const p = new Person('张三'); // this → 新创建的对象
// 规则5: 箭头函数 - 没有自己的this,继承外层
const obj2 = {
name: '张三',
greet: () => {
console.log(this.name); // ⚠️ undefined(继承外层,即全局)
},
delayGreet() {
setTimeout(() => {
console.log(this.name); // ✅ '张三'(继承delayGreet的this)
}, 100);
}
};
// 优先级: new > 显式 > 隐式 > 默认12. call、apply、bind的区别?
| 方法 | 执行时机 | 参数形式 | 返回值 |
|---|---|---|---|
call | 立即执行 | 逐个传参 | 函数返回值 |
apply | 立即执行 | 数组传参 | 函数返回值 |
bind | 返回新函数 | 逐个传参 | 绑定后的函数 |
javascript
function sum(a, b) {
return this.base + a + b;
}
const ctx = { base: 10 };
sum.call(ctx, 1, 2); // 13
sum.apply(ctx, [1, 2]); // 13
const boundSum = sum.bind(ctx, 1);
boundSum(2); // 13(柯里化效果)
// 实际应用
// 借用数组方法处理类数组
function example() {
const args = Array.prototype.slice.call(arguments);
// 或者用ES6: const args = [...arguments];
// 或者: const args = Array.from(arguments);
}
// apply求最大值
const nums = [3, 1, 4, 1, 5];
Math.max.apply(null, nums); // 5
// ES6: Math.max(...nums)异步编程
13. Promise的核心概念和用法?
javascript
// Promise三种状态: pending → fulfilled / rejected(不可逆)
const promise = new Promise((resolve, reject) => {
// 异步操作
setTimeout(() => {
const success = true;
if (success) {
resolve('成功数据');
} else {
reject(new Error('失败原因'));
}
}, 1000);
});
// 链式调用
promise
.then(data => {
console.log(data); // '成功数据'
return data.toUpperCase();
})
.then(upper => {
console.log(upper); // '成功数据'(中文不变)
return fetch('/api/next');
})
.catch(err => {
console.error(err); // 捕获链中任何错误
})
.finally(() => {
console.log('完成'); // 无论成功失败都执行
});
// Promise 静态方法
// Promise.all - 全部成功才成功,一个失败即失败
const results = await Promise.all([
fetch('/api/users'),
fetch('/api/posts'),
fetch('/api/comments')
]);
// Promise.allSettled - 等待全部完成,不论成功失败
const outcomes = await Promise.allSettled([
fetch('/api/a'),
fetch('/api/b'), // 即使失败也不影响其他
]);
outcomes.forEach(result => {
if (result.status === 'fulfilled') {
console.log('成功:', result.value);
} else {
console.log('失败:', result.reason);
}
});
// Promise.race - 返回最先完成的
const fastest = await Promise.race([
fetch('/api/primary'),
new Promise((_, reject) =>
setTimeout(() => reject(new Error('超时')), 5000)
)
]);
// Promise.any - 返回最先成功的(全部失败才失败)
const firstSuccess = await Promise.any([
fetch('/api/mirror1'),
fetch('/api/mirror2'),
fetch('/api/mirror3')
]);14. async/await 的原理和最佳实践?
async/await 是 Generator + Promise 的语法糖,让异步代码以同步方式书写。
javascript
// 基础用法
async function fetchUser(id) {
try {
const response = await fetch(`/api/users/${id}`);
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const user = await response.json();
return user;
} catch (error) {
console.error('获取用户失败:', error);
throw error; // 重新抛出让调用者处理
}
}
// ⚠️ 常见错误: 串行等待(性能差)
async function bad() {
const a = await fetch('/api/a'); // 等待完成...
const b = await fetch('/api/b'); // 才开始请求b
return [a, b];
}
// ✅ 并行请求
async function good() {
const [a, b] = await Promise.all([
fetch('/api/a'),
fetch('/api/b')
]);
return [a, b];
}
// ✅ 循环中的异步
// 串行执行(有序)
async function sequential(urls) {
const results = [];
for (const url of urls) {
const res = await fetch(url);
results.push(await res.json());
}
return results;
}
// 并行执行(更快)
async function parallel(urls) {
const promises = urls.map(url => fetch(url).then(r => r.json()));
return Promise.all(promises);
}
// ⚠️ forEach + async 不会等待
urls.forEach(async (url) => {
await fetch(url); // 不会按序等待!
});
// ✅ 用 for...of 代替15. Promise.withResolvers() 是什么?(2026重点)
ES2024新增,简化Promise的创建模式。
javascript
// 旧写法 - 需要在executor中操作
let resolve, reject;
const promise = new Promise((res, rej) => {
resolve = res;
reject = rej;
});
// ✅ 新写法 - Promise.withResolvers()
const { promise, resolve, reject } = Promise.withResolvers();
// 实际应用: 可取消的请求
function cancellableFetch(url) {
const { promise, resolve, reject } = Promise.withResolvers();
const controller = new AbortController();
fetch(url, { signal: controller.signal })
.then(resolve)
.catch(reject);
return {
promise,
cancel() {
controller.abort();
reject(new Error('请求被取消'));
}
};
}
const { promise: req, cancel } = cancellableFetch('/api/data');
// 需要时取消
cancel();
// 实际应用: 事件转Promise
function waitForEvent(element, eventName) {
const { promise, resolve } = Promise.withResolvers();
element.addEventListener(eventName, resolve, { once: true });
return promise;
}
const clickEvent = await waitForEvent(button, 'click');事件循环
16. 事件循环(Event Loop)机制是什么?(高频必考)
JavaScript是单线程的,通过事件循环处理异步任务。
┌───────────────────────┐
│ 调用栈 (Call Stack) │
│ 执行同步代码 │
└──────────┬────────────┘
│ 同步代码执行完毕
▼
┌───────────────────────┐
│ 微任务队列 (Microtask) │ ← 优先清空
│ Promise.then/catch │
│ queueMicrotask() │
│ MutationObserver │
└──────────┬────────────┘
│ 微任务队列清空
▼
┌───────────────────────┐
│ 宏任务队列 (Macrotask) │ ← 取一个执行
│ setTimeout/setInterval │
│ I/O、UI渲染 │
│ requestAnimationFrame │
└───────────────────────┘执行顺序:同步代码 → 微任务(全部清空)→ 宏任务(取一个)→ 微任务 → 宏任务 → ...
javascript
// 经典面试题
console.log('1');
setTimeout(() => {
console.log('2');
Promise.resolve().then(() => console.log('3'));
}, 0);
Promise.resolve().then(() => {
console.log('4');
setTimeout(() => console.log('5'), 0);
});
console.log('6');
// 输出顺序: 1, 6, 4, 2, 3, 5
// 解析:
// 同步: 1, 6
// 微任务: 4(此时产生新的宏任务5)
// 宏任务: 2(执行后产生微任务3)
// 微任务: 3
// 宏任务: 517. 宏任务和微任务有哪些?
| 宏任务 (Macrotask) | 微任务 (Microtask) |
|---|---|
setTimeout / setInterval | Promise.then / catch / finally |
setImmediate(Node) | queueMicrotask() |
| I/O 操作 | MutationObserver |
| UI 渲染 | process.nextTick(Node) |
requestAnimationFrame | async/await 后续代码 |
MessageChannel |
javascript
// 进阶面试题: async/await的执行顺序
async function async1() {
console.log('async1 start');
await async2();
// await后面的代码相当于放入微任务队列
console.log('async1 end');
}
async function async2() {
console.log('async2');
}
console.log('script start');
setTimeout(() => console.log('setTimeout'), 0);
async1();
new Promise((resolve) => {
console.log('promise1');
resolve();
}).then(() => {
console.log('promise2');
});
console.log('script end');
// 输出:
// script start
// async1 start
// async2
// promise1
// script end
// async1 end
// promise2
// setTimeoutES6+核心特性
18. 解构赋值和展开运算符
javascript
// 数组解构
const [a, b, ...rest] = [1, 2, 3, 4, 5];
// a=1, b=2, rest=[3,4,5]
// 对象解构 + 重命名 + 默认值
const { name: userName, age = 18, address: { city } = {} } = user;
// 函数参数解构
function createUser({ name, role = 'user', permissions = [] } = {}) {
return { name, role, permissions };
}
// 展开运算符
// 数组
const merged = [...arr1, ...arr2];
const copy = [...original];
// 对象(浅拷贝)
const updated = { ...user, name: '新名字', updatedAt: Date.now() };
// 函数调用
Math.max(...numbers);19. Map、Set、WeakMap、WeakSet的区别?
javascript
// Map - 键值对集合(键可以是任意类型)
const map = new Map();
map.set({ id: 1 }, 'user1');
map.set('key', 'value');
map.get('key'); // 'value'
map.has('key'); // true
map.size; // 2
// Set - 唯一值集合
const set = new Set([1, 2, 2, 3, 3]);
console.log([...set]); // [1, 2, 3](自动去重)
// 数组去重
const unique = [...new Set(array)];
// WeakMap - 键必须是对象,不阻止垃圾回收
const cache = new WeakMap();
function process(obj) {
if (cache.has(obj)) return cache.get(obj);
const result = /* 耗时计算 */ obj;
cache.set(obj, result);
return result;
}
// obj被回收时,WeakMap中的条目自动清除
// WeakRef - 弱引用(2026已广泛支持)
const weakRef = new WeakRef(largeObject);
const obj = weakRef.deref(); // 可能返回undefined(已被GC)| 特性 | Map | WeakMap | Set | WeakSet |
|---|---|---|---|---|
| 键类型 | 任意 | 对象 | - | 对象 |
| 可迭代 | ✅ | ❌ | ✅ | ❌ |
| size属性 | ✅ | ❌ | ✅ | ❌ |
| GC友好 | ❌ | ✅ | ❌ | ✅ |
20. Proxy和Reflect的用途?
javascript
// Proxy - 拦截对象操作(Vue3响应式核心)
const handler = {
get(target, prop, receiver) {
console.log(`读取: ${String(prop)}`);
return Reflect.get(target, prop, receiver);
},
set(target, prop, value, receiver) {
console.log(`设置: ${String(prop)} = ${value}`);
// 数据验证
if (prop === 'age' && typeof value !== 'number') {
throw new TypeError('age必须是数字');
}
return Reflect.set(target, prop, value, receiver);
},
deleteProperty(target, prop) {
console.log(`删除: ${String(prop)}`);
return Reflect.deleteProperty(target, prop);
}
};
const user = new Proxy({ name: '张三', age: 25 }, handler);
user.name; // 读取: name → '张三'
user.age = 26; // 设置: age = 26
user.age = 'abc'; // ❌ TypeError
// 实际应用: 实现响应式
function reactive(obj) {
return new Proxy(obj, {
get(target, key, receiver) {
track(target, key); // 收集依赖
const result = Reflect.get(target, key, receiver);
return typeof result === 'object' ? reactive(result) : result;
},
set(target, key, value, receiver) {
const result = Reflect.set(target, key, value, receiver);
trigger(target, key); // 触发更新
return result;
}
});
}ES2024/2025新特性
21. Object.groupBy() 和 Map.groupBy()(2026重点)
javascript
const products = [
{ name: 'iPhone', category: '手机', price: 7999 },
{ name: 'MacBook', category: '电脑', price: 12999 },
{ name: 'iPad', category: '平板', price: 3999 },
{ name: 'Pixel', category: '手机', price: 5999 },
{ name: 'ThinkPad', category: '电脑', price: 8999 },
];
// Object.groupBy - 返回普通对象
const byCategory = Object.groupBy(products, p => p.category);
// {
// '手机': [{ name: 'iPhone', ... }, { name: 'Pixel', ... }],
// '电脑': [{ name: 'MacBook', ... }, { name: 'ThinkPad', ... }],
// '平板': [{ name: 'iPad', ... }]
// }
// Map.groupBy - 返回Map(键可以是非字符串)
const byPriceRange = Map.groupBy(products, p =>
p.price > 10000 ? 'premium' : p.price > 5000 ? 'mid' : 'budget'
);
// 对比旧写法
const oldWay = products.reduce((acc, p) => {
(acc[p.category] ||= []).push(p);
return acc;
}, {});22. 不可变数组方法(toSorted, toReversed, toSpliced, with)
javascript
const numbers = [3, 1, 4, 1, 5, 9];
// 旧方法(会修改原数组)
numbers.sort(); // 修改原数组 ⚠️
numbers.reverse(); // 修改原数组 ⚠️
numbers.splice(1, 2); // 修改原数组 ⚠️
// ✅ 新方法(返回新数组,原数组不变)
const sorted = numbers.toSorted((a, b) => a - b);
// sorted: [1, 1, 3, 4, 5, 9],numbers不变
const reversed = numbers.toReversed();
// reversed: [9, 5, 1, 4, 1, 3],numbers不变
const spliced = numbers.toSpliced(1, 2, 99, 88);
// spliced: [3, 99, 88, 1, 5, 9],numbers不变
const updated = numbers.with(0, 100);
// updated: [100, 1, 4, 1, 5, 9],numbers不变
// 链式使用
const result = numbers
.toSorted((a, b) => a - b)
.toReversed()
.with(0, 0);23. Temporal API(2026前瞻)
替代 Date 对象的现代日期时间API,解决 Date 的各种历史问题。
javascript
// ⚠️ Date 的问题
new Date(2026, 2, 25); // 3月25日(月份从0开始 🤦)
new Date('2026-03-25'); // 可能受时区影响
// ✅ Temporal API(需polyfill,Stage 3)
// 纯日期(无时区)
const date = Temporal.PlainDate.from('2026-03-25');
console.log(date.year); // 2026
console.log(date.month); // 3(符合直觉!)
console.log(date.day); // 25
// 纯时间
const time = Temporal.PlainTime.from('14:30:00');
// 日期时间
const dateTime = Temporal.PlainDateTime.from('2026-03-25T14:30:00');
// 带时区
const zoned = Temporal.ZonedDateTime.from({
timeZone: 'Asia/Shanghai',
year: 2026, month: 3, day: 25,
hour: 14, minute: 30
});
// 时间计算
const tomorrow = date.add({ days: 1 });
const duration = Temporal.Duration.from({ hours: 2, minutes: 30 });
const endTime = time.add(duration);
// 比较
Temporal.PlainDate.compare(date1, date2); // -1, 0, 124. 其他ES2024/2025重要特性
javascript
// 1. Error.cause - 错误链
async function fetchData(url) {
try {
const res = await fetch(url);
return await res.json();
} catch (err) {
throw new Error(`获取数据失败: ${url}`, { cause: err });
}
}
// 调试时可以追溯到原始错误
// error.cause → 原始的网络错误
// 2. Array.fromAsync - 从异步可迭代对象创建数组
async function* asyncRange(n) {
for (let i = 0; i < n; i++) {
await new Promise(r => setTimeout(r, 100));
yield i;
}
}
const arr = await Array.fromAsync(asyncRange(5));
// [0, 1, 2, 3, 4]
// 3. 正则表达式 v 标志(Unicode Sets)
const emoji = /[\p{Emoji}--\p{ASCII}]/v;
emoji.test('😀'); // true
emoji.test('A'); // false
// 4. String.isWellFormed() / String.toWellFormed()
const str = 'hello\uD800world';
str.isWellFormed(); // false(包含孤立代理项)
str.toWellFormed(); // 'hello�world'
// 5. Atomics.waitAsync() - 异步等待SharedArrayBuffer
// 用于Web Workers间的同步手写代码
25. 手写防抖和节流
javascript
// 防抖(debounce):事件停止触发后才执行
// 应用:搜索输入、窗口resize
function debounce(fn, delay, immediate = false) {
let timer = null;
return function (...args) {
const callNow = immediate && !timer;
clearTimeout(timer);
timer = setTimeout(() => {
timer = null;
if (!immediate) fn.apply(this, args);
}, delay);
if (callNow) fn.apply(this, args);
};
}
// 节流(throttle):固定时间间隔内只执行一次
// 应用:滚动事件、拖拽
function throttle(fn, interval) {
let lastTime = 0;
return function (...args) {
const now = Date.now();
if (now - lastTime >= interval) {
lastTime = now;
fn.apply(this, args);
}
};
}
// 使用
const handleSearch = debounce((query) => {
fetch(`/api/search?q=${query}`);
}, 300);
const handleScroll = throttle(() => {
console.log('滚动位置:', window.scrollY);
}, 200);26. 手写Promise.all 和 Promise.race
javascript
// Promise.all
function promiseAll(promises) {
return new Promise((resolve, reject) => {
const results = [];
let completed = 0;
const promiseArr = Array.from(promises);
if (promiseArr.length === 0) {
resolve([]);
return;
}
promiseArr.forEach((p, index) => {
Promise.resolve(p).then(
value => {
results[index] = value;
completed++;
if (completed === promiseArr.length) {
resolve(results);
}
},
reject // 任一失败直接reject
);
});
});
}
// Promise.race
function promiseRace(promises) {
return new Promise((resolve, reject) => {
for (const p of promises) {
Promise.resolve(p).then(resolve, reject);
}
});
}27. 手写深拷贝
javascript
function deepClone(obj, map = new WeakMap()) {
// 基本类型直接返回
if (obj === null || typeof obj !== 'object') return obj;
// 处理特殊对象
if (obj instanceof Date) return new Date(obj);
if (obj instanceof RegExp) return new RegExp(obj);
// 处理循环引用
if (map.has(obj)) return map.get(obj);
const clone = Array.isArray(obj) ? [] : {};
map.set(obj, clone);
// 处理Symbol键
const allKeys = [
...Object.keys(obj),
...Object.getOwnPropertySymbols(obj)
];
for (const key of allKeys) {
clone[key] = deepClone(obj[key], map);
}
return clone;
}
// 测试
const original = {
a: 1,
b: { c: 2 },
d: [1, 2, { e: 3 }],
f: new Date(),
g: /regex/gi,
[Symbol('key')]: 'symbol value'
};
original.self = original; // 循环引用
const cloned = deepClone(original);
cloned.b.c = 99;
console.log(original.b.c); // 2(未被修改)
// 2026推荐: 大多数场景用 structuredClone()28. 手写 new 操作符和 instanceof
javascript
// 手写 new
function myNew(Constructor, ...args) {
// 1. 创建空对象,原型指向构造函数的prototype
const obj = Object.create(Constructor.prototype);
// 2. 执行构造函数,绑定this
const result = Constructor.apply(obj, args);
// 3. 如果构造函数返回对象,则使用该对象;否则返回新创建的对象
return result instanceof Object ? result : obj;
}
// 手写 instanceof
function myInstanceOf(obj, Constructor) {
if (obj === null || typeof obj !== 'object') return false;
let proto = Object.getPrototypeOf(obj);
while (proto !== null) {
if (proto === Constructor.prototype) return true;
proto = Object.getPrototypeOf(proto);
}
return false;
}
// 测试
function Person(name) { this.name = name; }
const p = myNew(Person, '张三');
console.log(p.name); // '张三'
console.log(myInstanceOf(p, Person)); // true
console.log(myInstanceOf(p, Object)); // true29. 手写 EventEmitter(发布-订阅模式)
javascript
class EventEmitter {
constructor() {
this.events = new Map();
}
on(event, listener) {
if (!this.events.has(event)) {
this.events.set(event, []);
}
this.events.get(event).push(listener);
return this; // 支持链式调用
}
off(event, listener) {
if (!this.events.has(event)) return this;
const listeners = this.events.get(event);
this.events.set(event, listeners.filter(l => l !== listener && l._original !== listener));
return this;
}
once(event, listener) {
const wrapper = (...args) => {
listener.apply(this, args);
this.off(event, wrapper);
};
wrapper._original = listener;
return this.on(event, wrapper);
}
emit(event, ...args) {
if (!this.events.has(event)) return false;
this.events.get(event).forEach(listener => {
listener.apply(this, args);
});
return true;
}
}
// 使用
const emitter = new EventEmitter();
const handler = (data) => console.log('收到:', data);
emitter.on('message', handler);
emitter.once('connect', () => console.log('已连接'));
emitter.emit('message', 'hello'); // 收到: hello
emitter.emit('connect'); // 已连接
emitter.emit('connect'); // (无输出,once只触发一次)
emitter.off('message', handler);性能优化与内存管理
30. 常见的内存泄漏场景?
javascript
// 1. 意外的全局变量
function leak() {
name = '全局变量'; // 忘记声明,挂到window上
}
// 2. 未清除的定时器和回调
const timer = setInterval(() => {
// 引用了大对象...
}, 1000);
// 组件销毁时忘记 clearInterval(timer)
// 3. 闭包引用
function createHandler() {
const largeData = new Array(1000000);
return function handler() {
// largeData被闭包持有,无法回收
};
}
// 解决:不需要时手动解除引用
// 4. DOM引用
const elements = {};
elements.button = document.getElementById('btn');
document.body.removeChild(elements.button);
// DOM节点从页面移除了,但elements.button仍引用它
elements.button = null; // ✅ 手动释放
// 5. 未取消的事件监听
element.addEventListener('click', handler);
// 元素移除前需要 removeEventListener
// 6. AbortController解决请求泄漏
const controller = new AbortController();
fetch('/api/data', { signal: controller.signal });
// 组件卸载时
controller.abort();31. 如何优化JavaScript性能?
javascript
// 1. 避免重复计算 - 使用缓存/记忆化
function memoize(fn) {
const cache = new Map();
return function (...args) {
const key = JSON.stringify(args);
if (cache.has(key)) return cache.get(key);
const result = fn.apply(this, args);
cache.set(key, result);
return result;
};
}
const expensiveCalc = memoize((n) => {
console.log('计算中...');
return n * n;
});
expensiveCalc(5); // 计算中... 25
expensiveCalc(5); // 25(命中缓存)
// 2. 大列表虚拟滚动(仅渲染可见区域)
// 使用 IntersectionObserver 替代 scroll 事件
const observer = new IntersectionObserver(
(entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
loadContent(entry.target);
observer.unobserve(entry.target);
}
});
},
{ rootMargin: '200px' }
);
// 3. Web Workers 处理耗时任务
const worker = new Worker('heavy-task.js');
worker.postMessage({ data: largeArray });
worker.onmessage = (e) => {
console.log('结果:', e.data);
};
// 4. requestAnimationFrame 优化动画
function smoothAnimation() {
// 更新动画...
requestAnimationFrame(smoothAnimation);
}
requestAnimationFrame(smoothAnimation);
// 5. 使用 AbortController 避免请求竞态
let currentController = null;
async function search(query) {
currentController?.abort();
currentController = new AbortController();
try {
const res = await fetch(`/api/search?q=${query}`, {
signal: currentController.signal
});
return await res.json();
} catch (e) {
if (e.name !== 'AbortError') throw e;
}
}进阶深入
32. Generator 和 Iterator 协议
javascript
// Iterator 协议 - 对象实现 [Symbol.iterator]() 方法
const range = {
from: 1,
to: 5,
[Symbol.iterator]() {
let current = this.from;
const last = this.to;
return {
next() {
return current <= last
? { value: current++, done: false }
: { done: true };
}
};
}
};
for (const n of range) console.log(n); // 1, 2, 3, 4, 5
console.log([...range]); // [1, 2, 3, 4, 5]
// Generator 函数 - 用 function* 声明,yield 暂停执行
function* fibonacci() {
let [a, b] = [0, 1];
while (true) {
yield a;
[a, b] = [b, a + b];
}
}
const fib = fibonacci();
fib.next(); // { value: 0, done: false }
fib.next(); // { value: 1, done: false }
fib.next(); // { value: 1, done: false }
fib.next(); // { value: 2, done: false }
// 取前10个斐波那契数
const first10 = [];
for (const n of fibonacci()) {
if (first10.length >= 10) break;
first10.push(n);
}
// Generator 的双向通信
function* conversation() {
const name = yield '你叫什么名字?';
const age = yield `${name},你多大了?`;
return `${name} 今年 ${age} 岁`;
}
const chat = conversation();
chat.next(); // { value: '你叫什么名字?' }
chat.next('张三'); // { value: '张三,你多大了?' }
chat.next(25); // { value: '张三 今年 25 岁', done: true }
// 异步Generator(async generator)
async function* fetchPages(baseUrl) {
let page = 1;
while (true) {
const res = await fetch(`${baseUrl}?page=${page}`);
const data = await res.json();
if (data.length === 0) return;
yield data;
page++;
}
}
// 使用 for await...of 消费异步迭代器
for await (const pageData of fetchPages('/api/items')) {
processItems(pageData);
}33. CommonJS 和 ESM 的区别?
| 特性 | CommonJS (CJS) | ES Modules (ESM) |
|---|---|---|
| 语法 | require() / module.exports | import / export |
| 加载时机 | 运行时加载 | 编译时静态分析 |
| 加载方式 | 同步 | 异步 |
| 值类型 | 值的拷贝 | 值的引用(实时绑定) |
| Tree Shaking | ❌ 不支持 | ✅ 支持 |
| 顶层await | ❌ | ✅ |
| this | module.exports | undefined |
javascript
// CommonJS
const fs = require('fs');
const { readFile } = require('fs');
module.exports = { myFunction };
module.exports.default = myFunction;
// ESM
import fs from 'fs';
import { readFile } from 'fs';
export function myFunction() {}
export default myFunction;
// ⚠️ 关键区别:值的拷贝 vs 引用
// CommonJS - 值的拷贝
// lib.js
let count = 0;
module.exports = { count, increment() { count++; } };
// main.js
const lib = require('./lib');
lib.increment();
console.log(lib.count); // 0(拷贝,不会变)
// ESM - 值的引用(实时绑定)
// lib.mjs
export let count = 0;
export function increment() { count++; }
// main.mjs
import { count, increment } from './lib.mjs';
increment();
console.log(count); // 1(引用,实时更新)
// 动态导入(两者都支持)
const module = await import('./heavy-module.js');
// import.meta(ESM独有)
console.log(import.meta.url); // 当前模块的URL
console.log(import.meta.resolve('./lib.js')); // 解析模块路径34. JavaScript 垃圾回收机制?
javascript
// 主要算法:标记-清除(Mark and Sweep)
// 1. 从根对象(全局对象、当前调用栈)出发
// 2. 标记所有可达对象
// 3. 清除未被标记的对象
// V8引擎的分代回收策略
// ┌─────────────┐ ┌──────────────────┐
// │ 新生代(Young) │ │ 老生代(Old) │
// │ Scavenge算法 │ │ Mark-Sweep-Compact │
// │ 1-8MB │ │ 上百MB │
// │ 存活时间短 │ │ 存活时间长/大对象 │
// └─────────────┘ └──────────────────┘
// ↓ 晋升(多次GC后仍存活)
// → 老生代
// WeakRef 和 FinalizationRegistry(高级GC交互)
// WeakRef - 不阻止GC回收
class Cache {
#cache = new Map();
#registry = new FinalizationRegistry((key) => {
// 对象被GC时自动回调,清理缓存条目
const ref = this.#cache.get(key);
if (ref && !ref.deref()) {
this.#cache.delete(key);
console.log(`缓存条目 ${key} 已被GC清理`);
}
});
set(key, value) {
const ref = new WeakRef(value);
this.#cache.set(key, ref);
this.#registry.register(value, key);
}
get(key) {
const ref = this.#cache.get(key);
if (!ref) return undefined;
const value = ref.deref();
if (!value) {
this.#cache.delete(key);
return undefined;
}
return value;
}
}
// 内存分析工具
// Chrome DevTools → Memory → Heap Snapshot
// 关注: Retained Size(对象及其引用链占用的总内存)
// 关注: Detached DOM树(已从页面移除但仍在内存中的DOM节点)35. 手写 Function.prototype.bind
javascript
Function.prototype.myBind = function (context, ...outerArgs) {
if (typeof this !== 'function') {
throw new TypeError('must be a function');
}
const fn = this;
const bound = function (...innerArgs) {
// 如果作为构造函数调用(new bound()),this指向新对象
const isNew = this instanceof bound;
return fn.apply(
isNew ? this : context,
[...outerArgs, ...innerArgs]
);
};
// 维护原型链,支持new操作
if (fn.prototype) {
bound.prototype = Object.create(fn.prototype);
}
return bound;
};
// 测试
function greet(greeting, punctuation) {
return `${greeting}, ${this.name}${punctuation}`;
}
const boundGreet = greet.myBind({ name: '张三' }, 'Hello');
console.log(boundGreet('!')); // 'Hello, 张三!'
// 测试new调用
function Person(name) { this.name = name; }
const BoundPerson = Person.myBind({ name: '被忽略' });
const p = new BoundPerson('张三');
console.log(p.name); // '张三'(new时bind的context被忽略)36. 手写 Array.prototype.flat
javascript
// 方法1: 递归
Array.prototype.myFlat = function (depth = 1) {
const result = [];
const flatten = (arr, d) => {
for (const item of arr) {
if (Array.isArray(item) && d > 0) {
flatten(item, d - 1);
} else {
result.push(item);
}
}
};
flatten(this, depth);
return result;
};
// 方法2: reduce + 递归(更简洁)
Array.prototype.myFlat2 = function (depth = 1) {
return depth > 0
? this.reduce((acc, val) =>
acc.concat(Array.isArray(val) ? val.myFlat2(depth - 1) : val), [])
: this.slice();
};
// 方法3: 完全展平(Infinity)用迭代
function flatDeep(arr) {
const stack = [...arr];
const result = [];
while (stack.length) {
const item = stack.pop();
if (Array.isArray(item)) {
stack.push(...item);
} else {
result.unshift(item);
}
}
return result;
}
// 测试
const nested = [1, [2, [3, [4, [5]]]]];
console.log(nested.myFlat()); // [1, 2, [3, [4, [5]]]]
console.log(nested.myFlat(2)); // [1, 2, 3, [4, [5]]]
console.log(nested.myFlat(Infinity)); // [1, 2, 3, 4, 5]37. 手写函数组合(compose / pipe)
javascript
// compose - 从右到左执行
function compose(...fns) {
if (fns.length === 0) return (v) => v;
if (fns.length === 1) return fns[0];
return fns.reduce((a, b) => (...args) => a(b(...args)));
}
// pipe - 从左到右执行
function pipe(...fns) {
if (fns.length === 0) return (v) => v;
if (fns.length === 1) return fns[0];
return fns.reduce((a, b) => (...args) => b(a(...args)));
}
// 使用
const add10 = (x) => x + 10;
const multiply2 = (x) => x * 2;
const toString = (x) => `结果: ${x}`;
const composed = compose(toString, multiply2, add10);
console.log(composed(5)); // '结果: 30'(先+10=15,再*2=30,再转字符串)
const piped = pipe(add10, multiply2, toString);
console.log(piped(5)); // '结果: 30'(同上,但读起来更直观)
// 异步compose(支持async函数)
function asyncPipe(...fns) {
return (input) =>
fns.reduce((chain, fn) => chain.then(fn), Promise.resolve(input));
}
const process = asyncPipe(
async (id) => fetch(`/api/users/${id}`),
async (res) => res.json(),
(user) => user.name.toUpperCase()
);
const name = await process(1); // 'ZHANG SAN'38. JavaScript 设计模式
javascript
// 1. 单例模式(Singleton)
class Database {
static #instance = null;
static getInstance() {
if (!Database.#instance) {
Database.#instance = new Database();
}
return Database.#instance;
}
constructor() {
if (Database.#instance) {
throw new Error('使用 getInstance()');
}
this.connection = 'connected';
}
}
const db1 = Database.getInstance();
const db2 = Database.getInstance();
console.log(db1 === db2); // true
// 2. 观察者模式(Observer) - 与发布订阅的区别
// 观察者模式:Subject直接通知Observer(耦合)
class Subject {
#observers = new Set();
subscribe(observer) { this.#observers.add(observer); }
unsubscribe(observer) { this.#observers.delete(observer); }
notify(data) {
this.#observers.forEach(obs => obs.update(data));
}
}
// 发布订阅模式:通过EventEmitter中间人解耦
// Publisher → EventBus ← Subscriber
// 3. 策略模式(Strategy)
const validators = {
required: (value) => value !== '' || '此字段必填',
email: (value) => /\S+@\S+\.\S+/.test(value) || '邮箱格式不正确',
minLength: (min) => (value) =>
value.length >= min || `最少${min}个字符`,
};
function validate(value, rules) {
return rules.map(rule => {
const validator = typeof rule === 'string'
? validators[rule]
: rule;
return validator(value);
}).filter(result => result !== true);
}
const errors = validate('', ['required', validators.minLength(6)]);
// ['此字段必填', '最少6个字符']
// 4. 代理模式(结合Proxy)
function createLoggedObject(target) {
return new Proxy(target, {
get(target, prop) {
console.log(`[GET] ${String(prop)}`);
return Reflect.get(target, prop);
},
set(target, prop, value) {
console.log(`[SET] ${String(prop)} = ${JSON.stringify(value)}`);
return Reflect.set(target, prop, value);
}
});
}39. 尾调用优化和递归优化
javascript
// 普通递归 - 可能栈溢出
function factorial(n) {
if (n <= 1) return 1;
return n * factorial(n - 1); // 非尾调用:需要保持当前栈帧
}
// factorial(100000) → ❌ RangeError: Maximum call stack size exceeded
// 尾调用优化(TCO)- 函数的最后一步是调用另一个函数
function factorialTCO(n, acc = 1) {
if (n <= 1) return acc;
return factorialTCO(n - 1, n * acc); // 尾调用:直接返回函数调用结果
}
// ⚠️ 目前只有Safari支持TCO
// 实际解决方案1: 改用循环
function factorialLoop(n) {
let result = 1;
for (let i = 2; i <= n; i++) result *= i;
return result;
}
// 实际解决方案2: 蹦床函数(Trampoline)
function trampoline(fn) {
return function (...args) {
let result = fn(...args);
while (typeof result === 'function') {
result = result();
}
return result;
};
}
const factorialT = trampoline(function f(n, acc = 1n) {
if (n <= 1) return acc;
return () => f(n - 1, BigInt(n) * acc); // 返回函数而非直接递归
});
console.log(factorialT(100000)); // ✅ 不会栈溢出40. 正则表达式高级用法
javascript
// 命名捕获组
const dateRegex = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
const match = '2026-03-25'.match(dateRegex);
console.log(match.groups); // { year: '2026', month: '03', day: '25' }
// 后行断言(Lookbehind)
const price = '价格:¥100 和 $200';
price.match(/(?<=¥)\d+/g); // ['100'](匹配¥后面的数字)
price.match(/(?<=\$)\d+/g); // ['200'](匹配$后面的数字)
// 前行断言(Lookahead)
'100px 200em 300rem'.match(/\d+(?=px)/g); // ['100']
// 否定断言
'100px 200em 300rem'.match(/\d+(?!px)\w+/g); // ['200em', '300rem']
// matchAll - 获取所有匹配及捕获组
const text = '联系人: 张三(13812345678), 李四(13987654321)';
const phoneRegex = /(\p{Script=Han}+)\((\d{11})\)/gu;
for (const match of text.matchAll(phoneRegex)) {
console.log(`${match[1]}: ${match[2]}`);
}
// 张三: 13812345678
// 李四: 13987654321
// v标志(Unicode Sets)- ES2024
const emojiOnly = /^[\p{Emoji}--\p{ASCII}]+$/v;
emojiOnly.test('😀🎉'); // true
emojiOnly.test('hello'); // false
emojiOnly.test('😀a'); // false
// String.prototype.replaceAll + 正则
const template = 'Hello {{name}}, welcome to {{city}}';
const data = { name: '张三', city: '上海' };
const result = template.replaceAll(
/\{\{(\w+)\}\}/g,
(_, key) => data[key] || ''
);
// 'Hello 张三, welcome to 上海'41. 并发控制:如何限制同时进行的异步操作数?
javascript
// 并发限制器
class ConcurrencyLimiter {
#running = 0;
#queue = [];
constructor(maxConcurrency) {
this.maxConcurrency = maxConcurrency;
}
async run(fn) {
if (this.#running >= this.maxConcurrency) {
// 排队等待
const { promise, resolve } = Promise.withResolvers();
this.#queue.push(resolve);
await promise;
}
this.#running++;
try {
return await fn();
} finally {
this.#running--;
if (this.#queue.length > 0) {
const next = this.#queue.shift();
next(); // 释放下一个等待的任务
}
}
}
}
// 使用:同时最多3个请求
const limiter = new ConcurrencyLimiter(3);
const urls = Array.from({ length: 20 }, (_, i) => `/api/item/${i}`);
const results = await Promise.all(
urls.map(url => limiter.run(() => fetch(url).then(r => r.json())))
);
// 简化版:并发控制的 map 函数
async function asyncMap(items, fn, concurrency = 5) {
const results = [];
const executing = new Set();
for (const [index, item] of items.entries()) {
const p = fn(item, index).then(result => {
executing.delete(p);
return result;
});
executing.add(p);
results.push(p);
if (executing.size >= concurrency) {
await Promise.race(executing);
}
}
return Promise.all(results);
}
// 使用
const data = await asyncMap(
urls,
(url) => fetch(url).then(r => r.json()),
3 // 最多3个并发
);42. 柯里化 vs 偏函数的区别?
javascript
// 柯里化(Currying):将多参数函数转为一系列单参数函数
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn.apply(this, args);
}
return (...moreArgs) => curried(...args, ...moreArgs);
};
}
const add = curry((a, b, c) => a + b + c);
add(1)(2)(3); // 6
add(1, 2)(3); // 6
add(1)(2, 3); // 6
// 偏函数(Partial Application):固定部分参数,返回新函数
function partial(fn, ...presetArgs) {
return (...laterArgs) => fn(...presetArgs, ...laterArgs);
}
const add10 = partial((a, b) => a + b, 10);
add10(5); // 15
// 实际应用
const log = curry((level, module, message) => {
console.log(`[${level}] [${module}] ${message}`);
});
const errorLog = log('ERROR');
const apiError = errorLog('API');
apiError('请求超时'); // [ERROR] [API] 请求超时
// 使用bind实现偏函数
const multiply = (a, b) => a * b;
const double = multiply.bind(null, 2);
double(5); // 10总结
2026年JavaScript面试重点:
- ✅ 数据类型与类型判断 - typeof/instanceof/Object.prototype.toString,深浅拷贝(structuredClone)
- ✅ 闭包与作用域 - 经典必考,理解词法作用域、闭包应用和内存影响
- ✅ 原型链 - prototype、__proto__、继承方式,class私有字段
- ✅ this指向 - 五种绑定规则和优先级
- ✅ Promise & async/await - 链式调用、错误处理、并行/串行控制
- ✅ 事件循环 - 宏任务/微任务执行顺序(必出代码题)
- ✅ ES2024/2025新特性 - Object.groupBy、toSorted、Promise.withResolvers
- ✅ 手写代码 - 防抖节流、深拷贝、Promise.all、EventEmitter
- ✅ Proxy/Reflect - Vue3响应式原理基础
- ✅ 性能优化 - 内存泄漏、记忆化、Web Workers、AbortController
学习建议:
- 闭包、原型链、事件循环是面试三座大山,必须能手画流程图讲清楚
- Promise和async/await要能手写,理解微任务执行时机
- 关注ES2024/2025已进入生产的新特性(groupBy、toSorted等)
- 手写代码要多练习,面试中高频出现
- 理解Proxy机制对学习Vue3/React有很大帮助