2. JavaScript ES6+ 核心语法:let/const、箭头函数、模板字符串、解构赋值、展开运算符、模块化(import/export)

好,咱们进入第二章。ES6+ 的语法,说白了就是现代 JavaScript 的基石。你想想看,现在哪个前端项目不用这些?我当年刚入行时还在写 var 和拼接字符串,那叫一个痛苦。后来 ES6 一出来,整个编码体验直接起飞。

这一章我会带你逐个击破。每个知识点我都会结合实战经验来讲,保证你学完就能用。

2.1 let 与 const:告别 var 的混乱时代

先说说 letconst。它们解决了 var 的两个大问题:作用域混乱和变量提升带来的坑。

let:块级作用域

let 只在它所在的 {} 内有效。我见过不少新手用 var 在 for 循环里定义变量,结果循环结束后变量还活着,导致各种诡异 bug。

// 错误示范(用 var)
for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100);
}
// 输出:3, 3, 3(因为 var 没有块级作用域)

// 正确示范(用 let)
for (let i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100);
}
// 输出:0, 1, 2

const:常量声明

const 声明一个只读的常量。注意,它保证的是引用地址不变,而不是值不可变。举个例子:

const person = { name: '张三' };
person.name = '李四'; // 可以修改属性
// person = { name: '王五' }; // 报错!不能重新赋值
我的习惯:能用 const 就用 const,只有明确需要重新赋值时才用 let。var?我建议你直接忘掉它。
避坑指南:我曾经在项目中用 const 声明了一个数组,然后直接 push 元素,一切正常。但后来有人试图用 arr = [] 清空数组,结果报错了。记住:const 管的是「不能重新指向」,不是「不能修改内容」。

2.2 箭头函数:更简洁的 this 绑定

箭头函数有两个核心特点:语法简洁,以及不绑定自己的 this

基本语法

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

// 箭头函数
const add = (a, b) => a + b;

// 如果只有一个参数,可以省略括号
const double = x => x * 2;

// 如果没有参数,必须写空括号
const sayHello = () => console.log('Hello');

this 指向问题

箭头函数没有自己的 this,它会捕获外层作用域的 this。这一点在 React 组件中特别有用。

// 传统函数的问题
const obj = {
  name: 'obj',
  logName: function() {
    setTimeout(function() {
      console.log(this.name); // undefined(this 指向 window)
    }, 100);
  }
};

// 箭头函数的解决方案
const obj2 = {
  name: 'obj2',
  logName: function() {
    setTimeout(() => {
      console.log(this.name); // 'obj2'(this 指向 obj2)
    }, 100);
  }
};

实战经验:我在写 React 类组件时,经常用箭头函数来避免手动绑定 this。比如 handleClick = () => { ... },这样就不用在 constructor 里写 this.handleClick = this.handleClick.bind(this) 了。

注意:箭头函数不能用作构造函数,也不能用 arguments 对象。如果你需要这些特性,还是用传统函数吧。

2.3 模板字符串:告别字符串拼接

模板字符串用反引号(`)包裹,支持嵌入变量和表达式。我个人觉得这是 ES6 最实用的特性之一。

const name = '张三';
const age = 25;

// 传统拼接
const msg1 = '我叫' + name + ',今年' + age + '岁。';

// 模板字符串
const msg2 = `我叫${name},今年${age}岁。`;

多行字符串

以前写多行字符串得用 \n 或者数组 join,现在直接写:

const html = `
  <div>
    <h1>标题</h1>
    <p>内容</p>
  </div>
`;

嵌入表达式

花括号里可以放任何 JavaScript 表达式:

const price = 100;
const discount = 0.8;
const finalPrice = `最终价格:${price * discount} 元`;
我的习惯:在写 SQL 查询或 HTML 模板时,我几乎必用模板字符串。它让代码可读性提升了一个档次。

2.4 解构赋值:快速提取数据

解构赋值让你从数组或对象中提取数据变得极其方便。说白了,就是「拆包」。

数组解构

const arr = [1, 2, 3];
const [a, b, c] = arr;
console.log(a, b, c); // 1 2 3

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

// 默认值
const [x, y, z = 0] = [1, 2];
console.log(x, y, z); // 1 2 0

对象解构

const user = { name: '李四', age: 30, email: 'li@example.com' };
const { name, age } = user;
console.log(name, age); // 李四 30

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

// 嵌套解构
const data = { person: { name: '王五', address: { city: '北京' } } };
const { person: { address: { city } } } = data;
console.log(city); // 北京

实战经验:我在处理 API 返回数据时,经常用解构赋值直接提取需要的字段。比如 const { data, status } = await axios.get('/api/user'),一行代码搞定。

2.5 展开运算符:合并与复制

展开运算符(...)可以把数组或对象「展开」。它有两个主要用途:复制和合并。

数组展开

const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];

// 合并数组
const merged = [...arr1, ...arr2];
console.log(merged); // [1, 2, 3, 4, 5, 6]

// 复制数组(浅拷贝)
const copy = [...arr1];
copy.push(4);
console.log(arr1); // [1, 2, 3](原数组不变)

对象展开

const obj1 = { a: 1, b: 2 };
const obj2 = { c: 3, d: 4 };

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

// 覆盖属性
const base = { name: '张三', age: 20 };
const update = { age: 25, email: 'new@example.com' };
const result = { ...base, ...update };
console.log(result); // { name: '张三', age: 25, email: 'new@example.com' }
注意:展开运算符做的是浅拷贝。如果对象里有嵌套对象,修改嵌套对象会影响原对象。我曾经因为这个踩过坑,后来改用深拷贝库解决了。

2.6 模块化:import/export

模块化是现代前端开发的标配。ES6 的 importexport 让代码组织变得清晰有序。

导出(export)

// 导出单个变量
export const name = '张三';

// 导出函数
export function sayHello() {
  console.log('Hello');
}

// 导出类
export class Person {
  constructor(name) {
    this.name = name;
  }
}

// 默认导出
export default function() {
  console.log('默认导出');
}

// 统一导出
const a = 1;
const b = 2;
export { a, b };

导入(import)

// 导入默认导出
import myFunction from './myModule';

// 导入命名导出
import { name, sayHello } from './myModule';

// 导入所有
import * as myModule from './myModule';
console.log(myModule.name);

// 重命名导入
import { name as userName } from './myModule';

// 混合导入
import defaultExport, { namedExport } from './myModule';
导出方式 导入方式 说明
export default import xxx from 每个模块只能有一个默认导出
export { a, b } import { a, b } from 命名导出,可以有多个
export const c import { c } from 直接导出声明

实战经验:我在项目中通常这样组织:工具函数用命名导出,组件用默认导出。这样既清晰又方便。

我的习惯:使用 import 时,我会把第三方库的导入放在最前面,然后是项目内部模块。这样一眼就能看出依赖关系。

好了,ES6+ 的核心语法就这些。你想想看,这些特性是不是让代码变得更简洁、更易读了?下一章我们会把这些知识用到实际项目中,到时候你就知道它们有多强大了。