ES6+ 关键特性

解构赋值

解构赋值是一种从数组或对象中提取值并赋给变量的语法糖。

数组解构

// 基本数组解构
const [a, b, c] = [1, 2, 3];
console.log(a, b, c); // 1, 2, 3

// 跳过某些元素
const [first, , third] = [1, 2, 3];
console.log(first, third); // 1, 3

// 剩余元素
const [head, ...tail] = [1, 2, 3, 4, 5];
console.log(head); // 1
console.log(tail); // [2, 3, 4, 5]

// 默认值
const [x = 10, y = 20] = [5];
console.log(x, y); // 5, 20

// 交换变量
let m = 1, n = 2;
[m, n] = [n, m];
console.log(m, n); // 2, 1

// 嵌套解构
const nested = [1, [2, 3], 4];
const [a, [b, c], d] = nested;
console.log(a, b, c, d); // 1, 2, 3, 4

对象解构

// 基本对象解构
const { name, age } = { name: '张三', age: 25 };
console.log(name, age); // 张三, 25

// 重命名变量
const { name: userName, age: userAge } = { name: '李四', age: 30 };
console.log(userName, userAge); // 李四, 30

// 默认值
const { x = 100, y = 200 } = { x: 50 };
console.log(x, y); // 50, 200

// 嵌套解构
const user = {
  id: 1,
  profile: {
    name: '王五',
    contacts: {
      email: 'wang@example.com',
      phone: '1234567890'
    }
  }
};

const { 
  profile: { 
    name, 
    contacts: { email } 
  } 
} = user;
console.log(name, email); // 王五, wang@example.com

// 函数参数解构
function greet({ name, greeting = '你好' }) {
  console.log(`${greeting}, ${name}!`);
}

greet({ name: '赵六' }); // 你好, 赵六!
greet({ name: '孙七', greeting: '早上好' }); // 早上好, 孙七!

展开运算符

展开运算符 (...) 可以将数组或对象"展开"成独立的元素。

// 数组展开
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const combined = [...arr1, ...arr2];
console.log(combined); // [1, 2, 3, 4, 5, 6]

// 添加元素
const newArr = [0, ...arr1, 4];
console.log(newArr); // [0, 1, 2, 3, 4]

// 复制数组
const copy = [...arr1];
console.log(copy); // [1, 2, 3]

// 对象展开
const obj1 = { a: 1, b: 2 };
const obj2 = { c: 3, d: 4 };
const merged = { ...obj1, ...obj2 };
console.log(merged); // { a: 1, b: 2, c: 3, d: 4 }

// 覆盖属性
const updated = { ...obj1, b: 10 };
console.log(updated); // { a: 1, b: 10 }

// 函数参数展开
function sum(x, y, z) {
  return x + y + z;
}
const nums = [1, 2, 3];
console.log(sum(...nums)); // 6

// 注意事项:浅拷贝
const original = { nested: { value: 1 } };
const shallowCopy = { ...original };
shallowCopy.nested.value = 2;
console.log(original.nested.value); // 2 (原对象也被修改)

可选链与空值合并

这两个操作符大大简化了处理可能为 nullundefined 的值。

// 可选链操作符 (?.)
const user = {
  name: '张三',
  address: {
    city: '北京'
  }
};

// 不使用可选链
const zip1 = user && user.address && user.address.zip;

// 使用可选链
const zip2 = user?.address?.zip;
console.log(zip2); // undefined (不会报错)

// 数组可选链
const arr = [1, 2, 3];
console.log(arr?.[0]); // 1
console.log(arr?.[5]); // undefined

// 函数可选链
const obj = {
  greet: () => console.log('你好')
};
obj.greet?.(); // 你好
obj.sayHi?.(); // undefined (不会报错)

// 空值合并操作符 (??)
// 只在值为 null 或 undefined 时返回右侧值
const value1 = 0 ?? 10;
console.log(value1); // 0 (0 不是 null/undefined)

const value2 = '' ?? 'default';
console.log(value2); // '' (空字符串不是 null/undefined)

const value3 = null ?? 'default';
console.log(value3); // 'default'

const value4 = undefined ?? 'default';
console.log(value4); // 'default'

// 与 || 的区别
const val1 = 0 || 'default';
console.log(val1); // 'default' (0 是 falsy)

const val2 = 0 ?? 'default';
console.log(val2); // 0 (0 不是 null/undefined)

// 组合使用
const config = {
  timeout: 3000
};
const timeout = config?.timeout ?? 5000;
console.log(timeout); // 3000

Promise 与异步处理

Promise 是处理异步操作的核心机制。

// 创建 Promise
const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    const success = true;
    if (success) {
      resolve('操作成功');
    } else {
      reject('操作失败');
    }
  }, 1000);
});

// 使用 Promise
promise
  .then(result => console.log(result))
  .catch(error => console.error(error))
  .finally(() => console.log('操作完成'));

// async/await 语法糖
async function fetchData() {
  try {
    const response = await fetch('https://api.example.com/data');
    
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    
    const data = await response.json();
    return data;
  } catch (error) {
    console.error('获取数据失败:', error);
    throw error; // 重新抛出错误让调用者处理
  }
}

// 并行执行多个异步操作
async function fetchMultipleData() {
  try {
    const [users, posts, comments] = await Promise.all([
      fetch('/api/users').then(r => r.json()),
      fetch('/api/posts').then(r => r.json()),
      fetch('/api/comments').then(r => r.json())
    ]);
    
    return { users, posts, comments };
  } catch (error) {
    console.error('批量获取失败:', error);
  }
}

// Promise.race - 返回最先完成的 Promise
async function fetchWithTimeout(url, timeout = 5000) {
  const timeoutPromise = new Promise((_, reject) => {
    setTimeout(() => reject(new Error('请求超时')), timeout);
  });
  
  return Promise.race([
    fetch(url).then(r => r.json()),
    timeoutPromise
  ]);
}

// Promise.allSettled - 等待所有 Promise 完成(无论成功失败)
async function fetchAllIgnoreErrors() {
  const promises = [
    fetch('/api/users').then(r => r.json()),
    fetch('/api/invalid').then(r => r.json()), // 可能失败
    fetch('/api/posts').then(r => r.json())
  ];
  
  const results = await Promise.allSettled(promises);
  
  const successful = results
    .filter(r => r.status === 'fulfilled')
    .map(r => r.value);
  
  const failed = results
    .filter(r => r.status === 'rejected')
    .map(r => r.reason);
  
  return { successful, failed };
}

模块化

ES6 模块系统使得代码组织更加清晰。

// math.js - 导出多个函数
export const add = (a, b) => a + b;
export const subtract = (a, b) => a - b;
export const multiply = (a, b) => a * b;
export const divide = (a, b) => {
  if (b === 0) throw new Error('除数不能为零');
  return a / b;
};

// 默认导出
export default class Calculator {
  constructor() {
    this.result = 0;
  }
  
  add(n) {
    this.result += n;
    return this;
  }
  
  getResult() {
    return this.result;
  }
}

// user.js - 导出对象
export const user = {
  name: '张三',
  age: 25
};

export function greet(name) {
  console.log(`你好, ${name}!`);
}

// main.js - 导入
import Calculator, { add, subtract } from './math.js';
import { user, greet } from './user.js';
import * as MathUtils from './math.js'; // 导入所有

// 使用
const calc = new Calculator();
calc.add(5).add(3);
console.log(calc.getResult()); // 8

console.log(add(10, 5)); // 15
greet(user.name); // 你好, 张三!
console.log(MathUtils.multiply(4, 5)); // 20

// 动态导入(代码分割)
async function loadModule() {
  const { heavyFunction } = await import('./heavy-module.js');
  heavyFunction();
}

// index.js - 重新导出
export { default as Calculator } from './math.js';
export * from './user.js';

核心概念深入

事件循环 (Event Loop)

JavaScript 是单线程语言,通过事件循环实现异步操作。

// 执行顺序示例
console.log('1. 同步代码开始');

setTimeout(() => {
  console.log('4. 宏任务:setTimeout');
}, 0);

Promise.resolve()
  .then(() => {
    console.log('3. 微任务:Promise.then');
  })
  .then(() => {
    console.log('3.5 微任务:Promise.then 链');
  });

console.log('2. 同步代码结束');

// 输出顺序:
// 1. 同步代码开始
// 2. 同步代码结束
// 3. 微任务:Promise.then
// 3.5 微任务:Promise.then 链
// 4. 宏任务:setTimeout

事件循环流程:

  1. 执行同步代码:调用栈中的代码优先执行
  2. 检查微任务队列:执行所有微任务(Promise.then、MutationObserver、queueMicrotask)
  3. 执行渲染:如果需要更新 DOM,浏览器会进行渲染
  4. 检查宏任务队列:执行一个宏任务(setTimeout、setInterval、I/O、UI 事件)
  5. 重复步骤 2-4
// 微任务和宏任务的优先级
setTimeout(() => {
  console.log('宏任务 1');
}, 0);

queueMicrotask(() => {
  console.log('微任务 1');
});

Promise.resolve().then(() => {
  console.log('微任务 2');
});

setTimeout(() => {
  console.log('宏任务 2');
}, 0);

// 输出:
// 微任务 1
// 微任务 2
// 宏任务 1
// 宏任务 2

实际应用:避免阻塞主线程

// 错误:长时间阻塞主线程
function heavyComputation() {
  const start = Date.now();
  while (Date.now() - start < 1000) {
    // 阻塞 1 秒,页面会卡死
  }
}

// 正确:将大任务拆分成小块
function chunkedComputation(items, callback) {
  let index = 0;
  const chunkSize = 100; // 每次处理 100 个
  
  function processChunk() {
    const end = Math.min(index + chunkSize, items.length);
    
    for (; index < end; index++) {
      callback(items[index]);
    }
    
    if (index < items.length) {
      setTimeout(processChunk, 0); // 让出主线程
    }
  }
  
  processChunk();
}

闭包 (Closure)

闭包是指函数能够记住并访问其词法作用域,即使该函数在其词法作用域之外执行。

// 基本闭包
function outer() {
  let count = 0; // 外部函数的变量
  
  return function inner() {
    count++; // 内部函数访问外部变量
    console.log(`计数: ${count}`);
  };
}

const counter = outer();
counter(); // 计数: 1
counter(); // 计数: 2
counter(); // 计数: 3

// 闭包的应用:私有变量
function createCounter(initialValue = 0) {
  let count = initialValue;
  
  return {
    increment: () => ++count,
    decrement: () => --count,
    getCount: () => count,
    reset: () => {
      count = initialValue;
      return count;
    }
  };
}

const myCounter = createCounter(10);
console.log(myCounter.increment()); // 11
console.log(myCounter.increment()); // 12
console.log(myCounter.decrement()); // 11
console.log(myCounter.getCount()); // 11
console.log(myCounter.reset()); // 10

// 闭包的应用:函数工厂
function multiply(x) {
  return function(y) {
    return x * y;
  };
}

const double = multiply(2);
const triple = multiply(3);

console.log(double(5)); // 10
console.log(triple(5)); // 15

// 闭包的应用:缓存(记忆化)
function memoize(fn) {
  const cache = new Map();
  
  return function(...args) {
    const key = JSON.stringify(args);
    
    if (cache.has(key)) {
      console.log('从缓存获取');
      return cache.get(key);
    }
    
    const result = fn(...args);
    cache.set(key, result);
    console.log('计算并缓存');
    return result;
  };
}

const expensiveCalculation = memoize((a, b) => {
  console.log('执行复杂计算...');
  return a + b;
});

console.log(expensiveCalculation(1, 2)); // 计算并缓存, 3
console.log(expensiveCalculation(1, 2)); // 从缓存获取, 3
console.log(expensiveCalculation(2, 3)); // 计算并缓存, 5

// 闭包的陷阱
function createFunctions() {
  const functions = [];
  
  // 错误示例:var 没有块级作用域
  for (var i = 0; i < 3; i++) {
    functions.push(() => console.log(i));
  }
  
  return functions;
}

const funcs = createFunctions();
funcs[0](); // 3 (不是 0)
funcs[1](); // 3 (不是 1)
funcs[2](); // 3 (不是 2)

// 解决方案 1:使用 let
function createFunctionsCorrect() {
  const functions = [];
  
  for (let i = 0; i < 3; i++) {
    functions.push(() => console.log(i));
  }
  
  return functions;
}

const funcsCorrect = createFunctionsCorrect();
funcsCorrect[0](); // 0
funcsCorrect[1](); // 1
funcsCorrect[2](); // 2

// 解决方案 2:使用闭包
function createFunctionsWithClosure() {
  const functions = [];
  
  for (var i = 0; i < 3; i++) {
    functions.push((function(j) {
      return () => console.log(j);
    })(i));
  }
  
  return functions;
}

原型链 (Prototype Chain)

JavaScript 使用原型链实现继承和属性共享。

// 构造函数
function Person(name, age) {
  this.name = name;
  this.age = age;
}

// 原型方法
Person.prototype.greet = function() {
  console.log(`你好, 我是${this.name}, ${this.age}`);
};

Person.prototype.toString = function() {
  return `${this.name}(${this.age})`;
};

// 创建实例
const person1 = new Person('张三', 25);
const person2 = new Person('李四', 30);

person1.greet(); // 你好, 我是张三, 25岁
person2.greet(); // 你好, 我是李四, 30岁

// 原型链关系
console.log(person1.__proto__ === Person.prototype); // true
console.log(Person.prototype.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__ === null); // true

// 属性查找过程
console.log(person1.hasOwnProperty('name')); // true (自身属性)
console.log(person1.hasOwnProperty('greet')); // false (原型属性)
console.log('greet' in person1); // true (包括原型链)

// ES6 class 语法(原型继承的语法糖)
class Animal {
  constructor(name, sound) {
    this.name = name;
    this.sound = sound;
  }
  
  speak() {
    console.log(`${this.name}发出${this.sound}的声音`);
  }
  
  static create(name, sound) {
    return new Animal(name, sound);
  }
}

class Dog extends Animal {
  constructor(name, breed) {
    super(name, '汪汪'); // 调用父类构造函数
    this.breed = breed;
  }
  
  // 重写父类方法
  speak() {
    console.log(`${this.name}(${this.breed})汪汪叫`);
  }
  
  fetch() {
    console.log(`${this.name}去捡球`);
  }
}

const dog = new Dog('旺财', '金毛');
dog.speak(); // 旺财(金毛)汪汪叫
dog.fetch(); // 旺财去捡球

console.log(dog instanceof Dog); // true
console.log(dog instanceof Animal); // true
console.log(dog instanceof Object); // true

// 静态方法
const dog2 = Animal.create('小白', '喵喵');

this 绑定规则

this 的值取决于函数的调用方式,而不是定义方式。

// 1. 默认绑定(全局对象或 undefined)
function globalFunction() {
  console.log(this);
}

globalFunction(); // 非严格模式: Window, 严格模式: undefined

// 2. 隐式绑定(对象调用)
const obj = {
  name: '对象',
  greet() {
    console.log(`你好, ${this.name}`);
  }
};

obj.greet(); // 你好, 对象 (this 指向 obj)

// 隐式绑定丢失
const greet = obj.greet;
greet(); // 你好, undefined (this 指向全局对象)

// 3. 显式绑定(call, apply, bind)
function introduce(greeting, punctuation) {
  console.log(`${greeting}, 我是${this.name}${punctuation}`);
}

const person = { name: '张三' };

// call: 立即调用,参数逐个传递
introduce.call(person, '你好', '!'); // 你好, 我是张三!

// apply: 立即调用,参数数组传递
introduce.apply(person, ['早上好', '。']); // 早上好, 我是张三。

// bind: 返回新函数
const boundIntroduce = introduce.bind(person);
boundIntroduce('晚上好', '~'); // 晚上好, 我是张三~

// 4. new 绑定(构造函数)
function Person(name) {
  this.name = name;
}

const p = new Person('李四');
console.log(p.name); // 李四

// 5. 箭头函数(没有自己的 this,继承外层)
const obj2 = {
  name: '箭头函数对象',
  greet: function() {
    // 普通函数
    setTimeout(function() {
      console.log(`普通函数: ${this.name}`); // undefined
    }, 100);
    
    // 箭头函数
    setTimeout(() => {
      console.log(`箭头函数: ${this.name}`); // 箭头函数对象
    }, 100);
  }
};

obj2.greet();

// 实际应用:保持 this 上下文
class Button {
  constructor(element) {
    this.element = element;
    this.clickCount = 0;
    
    // 使用 bind 保持 this
    this.element.addEventListener('click', this.handleClick.bind(this));
    
    // 或者使用箭头函数
    this.element.addEventListener('click', () => this.handleClick());
    
    // 或者在定义时使用箭头函数
    this.element.addEventListener('click', this.handleClickArrow);
  }
  
  handleClick() {
    this.clickCount++;
    console.log(`点击次数: ${this.clickCount}`);
  }
  
  handleClickArrow = () => {
    this.clickCount++;
    console.log(`点击次数: ${this.clickCount}`);
  }
}

实用技巧

防抖与节流

// 防抖:在事件触发 n 秒后才执行,如果在这段时间内再次触发,则重新计时
function debounce(func, delay) {
  let timeoutId;
  
  return function(...args) {
    clearTimeout(timeoutId);
    
    timeoutId = setTimeout(() => {
      func.apply(this, args);
    }, delay);
  };
}

// 使用示例:搜索框输入
const searchInput = document.getElementById('search');
searchInput.addEventListener('input', debounce((e) => {
  console.log('搜索:', e.target.value);
  // 发送 API 请求
}, 500));

// 节流:在固定时间间隔内只执行一次
function throttle(func, interval) {
  let lastTime = 0;
  
  return function(...args) {
    const now = Date.now();
    
    if (now - lastTime >= interval) {
      lastTime = now;
      func.apply(this, args);
    }
  };
}

// 使用示例:滚动事件
window.addEventListener('scroll', throttle(() => {
  console.log('滚动位置:', window.scrollY);
}, 100));

深拷贝与浅拷贝

// 浅拷贝
const shallowCopy1 = { ...original };
const shallowCopy2 = Object.assign({}, original);
const shallowCopy3 = [...originalArray];

// 深拷贝
function deepClone(obj, hash = new WeakMap()) {
  // 处理基本类型和 null
  if (obj === null || typeof obj !== 'object') {
    return obj;
  }
  
  // 处理循环引用
  if (hash.has(obj)) {
    return hash.get(obj);
  }
  
  // 处理日期
  if (obj instanceof Date) {
    return new Date(obj);
  }
  
  // 处理正则表达式
  if (obj instanceof RegExp) {
    return new RegExp(obj.source, obj.flags);
  }
  
  // 处理数组
  if (Array.isArray(obj)) {
    const clone = [];
    hash.set(obj, clone);
    obj.forEach((item, index) => {
      clone[index] = deepClone(item, hash);
    });
    return clone;
  }
  
  // 处理对象
  const clone = {};
  hash.set(obj, clone);
  
  Object.keys(obj).forEach(key => {
    clone[key] = deepClone(obj[key], hash);
  });
  
  return clone;
}

// 现代方法:structuredClone(浏览器和 Node.js 支持)
const original = {
  name: '张三',
  nested: { value: 1 },
  date: new Date(),
  regex: /test/gi
};

const cloned = structuredClone(original);