JavaScript 核心知识系列:从 ES6 到现代开发

2024-07-12 前端

JavaScript 是 Web 的灵魂。从 ES6(ECMAScript 2015)开始,这门语言经历了巨大的进化。本文梳理 ES6 到现代 JavaScript 的核心知识点,适合有一两年经验的开发者系统复习。


一、变量声明:var / let / const

// ❌ 不要再使用 var
var x = 1;    // 函数作用域,存在变量提升,可重复声明

// ✅ let:可变的块级作用域变量
let count = 0;
count = 1;    // ✅ 可以重新赋值

// ✅ const:不可变的块级作用域常量
const API_URL = 'https://api.example.com';
// API_URL = 'other';  // ❌ TypeError: Assignment to constant variable

// 但 const 对象/数组的内部是可变的!
const user = { name: 'Alice' };
user.name = 'Bob';   // ✅ 修改属性是允许的
// user = {};         // ❌ 重新赋值不允许
特性 var let const
作用域 函数 块 块
变量提升 ✅(undefined) ✅(TDZ) ✅(TDZ)
可重复声明 ✅ ❌ ❌
可重新赋值 ✅ ✅ ❌
必须初始化 ❌ ❌ ✅

TDZ(Temporal Dead Zone):let/const 在声明之前不可访问,这个区域叫「暂时性死区」。

console.log(a); // undefined(var 提升)
var a = 1;

console.log(b); // ❌ ReferenceError(TDZ)
let b = 2;

实用建议:默认使用 const,只有明确需要重新赋值时才用 let。彻底告别 var。


二、作用域与闭包

三种作用域

// 1. 全局作用域
const globalVar = '全局';

// 2. 函数作用域
function foo() {
  const funcVar = '函数内';
  console.log(globalVar); // ✅ 可以访问
}
// console.log(funcVar); // ❌ ReferenceError

// 3. 块级作用域(ES6 新增)
if (true) {
  const blockVar = '块内';
  let blockLet = '也是块内';
}
// console.log(blockVar); // ❌ ReferenceError

闭包(Closure)

闭包 = 函数 + 其外部作用域的变量引用。


三、箭头函数、模板字符串、解构、展开运算符

箭头函数

// 传统函数
function add(a, b) { return a + b; }

// 箭头函数
const add = (a, b) => a + b;        // 单行隐式返回
const greet = name => `Hello, ${name}`;  // 单参数省略括号
const noArgs = () => console.log('Hi');  // 无参数需要空括号

// 多行需要 {} 和显式 return
const complex = (a, b) => {
  const result = a * b;
  return result > 100 ? result : 0;
};

// ⚠️ 箭头函数没有自己的 this
const obj = {
  name: 'Alice',
  greet: function() {
    console.log(this.name); // 'Alice'
  },
  greetArrow: () => {
    console.log(this.name); // undefined(this 指向外层)
  }
};
特性 普通函数 箭头函数
this 动态绑定(调用者) 词法绑定(定义时)
arguments ✅ ❌
new 调用 ✅ ❌
prototype ✅ ❌
适用场景 方法、构造函数、事件处理器 回调、map/filter、简短函数

模板字符串

const name = 'Alice';
const age = 25;

// 旧方式
const old = 'My name is ' + name + ', I\'m ' + age + ' years old.';

// 模板字符串
const modern = `My name is ${name}, I'm ${age} years old.`;

// 多行字符串
const html = `
  <div class="card">
    <h2>${name}</h2>
    <p>Age: ${age}</p>
  </div>
`;

// 标签模板
function highlight(strings, ...values) {
  return strings.reduce((result, str, i) =>
    result + str + (values[i] ? `<mark>${values[i]}</mark>` : ''), '');
}
highlight`Hello, ${name}! You are ${age}.`;
// "Hello, <mark>Alice</mark>! You are <mark>25</mark>."

解构赋值

// 对象解构
const user = { name: 'Alice', age: 25, city: 'Beijing' };
const { name, age, city: location = 'Unknown' } = user;
// name='Alice', age=25, location='Beijing'(重命名+默认值)

// 嵌套解构
const response = {
  data: { user: { profile: { avatar: 'url', bio: 'hello' } } }
};
const { data: { user: { profile: { avatar, bio } } } } = response;

// 数组解构
const colors = ['red', 'green', 'blue'];
const [first, , third] = colors;  // first='red', third='blue'
const [head, ...rest] = colors;   // head='red', rest=['green','blue']

// 函数参数直接解构
function display({ name, age, city = 'Unknown' }) {
  console.log(`${name}, ${age}, ${city}`);
}
display(user); // "Alice, 25, Beijing"

// 交换变量(无需临时变量)
let a = 1, b = 2;
[a, b] = [b, a]; // a=2, b=1

展开运算符

// 数组展开
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const merged = [...arr1, ...arr2];  // [1,2,3,4,5,6]
const copy = [...arr1];              // 浅拷贝

// 对象展开
const defaults = { theme: 'light', lang: 'zh' };
const config = { ...defaults, lang: 'en' };  // lang 被覆盖
// { theme: 'light', lang: 'en' }

// 函数参数展开
const numbers = [5, 12, 8, 130, 44];
Math.max(...numbers);  // 130(替代 apply)

四、Promise 与 async/await

Promise 基础

方法 行为 使用场景
Promise.all 全部成功 → resolve;任一失败 → reject 并行请求,全部需要
Promise.allSettled 全部完成(不管成败) 批量操作,需要每个结果
Promise.race 第一个完成决定结果 超时控制
Promise.any 任一成功 → resolve;全失败 → reject 多源请求,取最快成功

async/await

// 异步函数:返回 Promise
async function loadUserData(id) {
  try {
    const user = await fetchUser(id);
    const posts = await fetchPosts(user.id);    // 串行
    return { user, posts };
  } catch (error) {
    console.error('Load failed:', error.message);
    throw error;  // 重新抛出
  }
}

// 并行请求
async function loadDashboard() {
  const [user, stats, notifications] = await Promise.all([
    fetchUser(1),
    fetchStats(),
    fetchNotifications()
  ]);
  return { user, stats, notifications };
}

// 顶层 await(ES2022,ESM 模块中)
// const config = await fetch('/api/config').then(r => r.json());

// 错误处理模式
const [error, data] = await fetchUser(1)
  .then(data => [null, data])
  .catch(err => [err, null]);
if (error) { /* 处理错误 */ }

实用建议:

  1. 永远不要忘记 try/catch 或 .catch()。
  2. 没有依赖关系的异步操作用 Promise.all 并行。
  3. 在 forEach/map 中使用 async 时要小心——它们不会等待。

五、模块化(ESM)

// ===== math.js =====
export const PI = 3.14159;

export function add(a, b) {
  return a + b;
}

export function multiply(a, b) {
  return a * b;
}

// 默认导出(每个模块只能有一个)
export default function greet(name) {
  return `Hello, ${name}!`;
}

// ===== app.js =====
// 命名导入
import { add, multiply, PI } from './math.js';
// 重命名
import { add as sum } from './math.js';
// 全部导入
import * as MathUtils from './math.js';
// 默认导入
import greet from './math.js';
// 混合导入
import greet, { add, PI } from './math.js';
// 动态导入(返回 Promise)
const module = await import('./math.js');
特性 CommonJS (CJS) ES Modules (ESM)
语法 require / module.exports import / export
加载时机 运行时同步 编译时静态分析
Tree Shaking ❌ ✅
顶层 await ❌ ✅
Node.js 默认 .mjs 或 "type":"module"
浏览器 ❌(需打包) ✅ 原生支持

六、数组方法(map / filter / reduce)


七、事件循环(Event Loop)

console.log('1');

setTimeout(() => console.log('2'), 0);

Promise.resolve().then(() => console.log('3'));

console.log('4');

// 输出顺序:1 → 4 → 3 → 2

执行顺序:

  1. 同步代码(调用栈)→ 先执行完
  2. 微任务(Microtasks):Promise.then/catch/finally、MutationObserver、queueMicrotask
  3. 宏任务(Macrotasks):setTimeout、setInterval、I/O、UI 渲染
// 更复杂的例子
console.log('start');

setTimeout(() => {
  console.log('timeout1');
  Promise.resolve().then(() => console.log('promise in timeout'));
}, 0);

Promise.resolve()
  .then(() => console.log('promise1'))
  .then(() => console.log('promise2'));

console.log('end');

// 输出:
// start → end → promise1 → promise2 → timeout1 → promise in timeout
类型 示例 优先级
同步代码 普通函数调用 最先
微任务 Promise.then, queueMicrotask 每个宏任务后,下一个宏任务前
宏任务 setTimeout, setInterval, I/O 最后
渲染 requestAnimationFrame 宏任务之间(浏览器环境)

实用建议:理解 Event Loop 是处理异步 bug 的关键。当遇到「先执行了什么后执行了什么」的问题时,画出调用栈、微任务队列和宏任务队列。


八、原型链与 class

原型链

// JavaScript 的继承基于原型链
const arr = [1, 2, 3];
// arr → Array.prototype → Object.prototype → null

arr.push(4);     // 继承自 Array.prototype
arr.toString();  // 继承自 Object.prototype

// 原型链查找
const obj = { a: 1 };
console.log(obj.hasOwnProperty('a')); // true(自己的属性)
console.log(obj.hasOwnProperty('toString')); // false(继承的)
console.log('toString' in obj); // true

class(语法糖)


九、Map / Set / WeakMap

数据结构 键类型 可遍历 垃圾回收 典型场景
Object String/Symbol ✅ ❌ 通用键值
Map 任意类型 ✅ ❌ 需要非字符串键
WeakMap 仅 Object ❌ ✅ DOM 元数据、私有数据
Set 任意类型 ✅ ❌ 去重、集合运算

十、最佳实践速查

实践 说明
✅ const 优先,let 其次 明确不可变性意图
✅ 使用 === 而非 == 避免隐式类型转换
✅ async/await 替代回调地狱 可读性大幅提升
✅ for...of + Object.entries 遍历 替代传统 for 循环
✅ 可选链 ?. 和空值合并 ?? 安全访问深层属性
✅ 使用 ESM 模块化 Tree Shaking 友好
❌ 避免 eval() 安全风险和性能问题
❌ 不要修改内置原型 破坏他人代码
❌ 避免 with 语句 已弃用,作用域混乱

结语

JavaScript 从「10 天设计出来的语言」到如今最活跃的生态系统,其核心概念始终没变:原型链、闭包、事件循环、异步编程。理解这些,你就掌握了 JavaScript 的灵魂。建议常去 MDN 查阅文档,那是前端开发者最好的朋友。

下一篇我们将进入 Vue 系列,探索组件化开发的魅力!