Skip to content

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);  // true

3. == 和 === 的区别?类型转换规则?

  • === 严格相等:不进行类型转换,类型和值都相同才为 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的区别?

特性varletconst
作用域函数作用域块级作用域块级作用域
变量提升✅ 提升并初始化为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');
  };
}
// 解决:不需要的大对象及时设为null

7. 作用域链是什么?

作用域链是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); // true

class 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
// 宏任务: 5

17. 宏任务和微任务有哪些?

宏任务 (Macrotask)微任务 (Microtask)
setTimeout / setIntervalPromise.then / catch / finally
setImmediate(Node)queueMicrotask()
I/O 操作MutationObserver
UI 渲染process.nextTick(Node)
requestAnimationFrameasync/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
// setTimeout

ES6+核心特性

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)
特性MapWeakMapSetWeakSet
键类型任意对象-对象
可迭代
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, 1

24. 其他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));   // true

29. 手写 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.exportsimport / export
加载时机运行时加载编译时静态分析
加载方式同步异步
值类型值的拷贝值的引用(实时绑定)
Tree Shaking❌ 不支持✅ 支持
顶层await
thismodule.exportsundefined
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面试重点:

  1. 数据类型与类型判断 - typeof/instanceof/Object.prototype.toString,深浅拷贝(structuredClone)
  2. 闭包与作用域 - 经典必考,理解词法作用域、闭包应用和内存影响
  3. 原型链 - prototype、__proto__、继承方式,class私有字段
  4. this指向 - 五种绑定规则和优先级
  5. Promise & async/await - 链式调用、错误处理、并行/串行控制
  6. 事件循环 - 宏任务/微任务执行顺序(必出代码题)
  7. ES2024/2025新特性 - Object.groupBy、toSorted、Promise.withResolvers
  8. 手写代码 - 防抖节流、深拷贝、Promise.all、EventEmitter
  9. Proxy/Reflect - Vue3响应式原理基础
  10. 性能优化 - 内存泄漏、记忆化、Web Workers、AbortController

学习建议:

  • 闭包、原型链、事件循环是面试三座大山,必须能手画流程图讲清楚
  • Promise和async/await要能手写,理解微任务执行时机
  • 关注ES2024/2025已进入生产的新特性(groupBy、toSorted等)
  • 手写代码要多练习,面试中高频出现
  • 理解Proxy机制对学习Vue3/React有很大帮助