Skip to main content

ES6+ 新特性

阮一峰 ES6 入门

概括

概括一下就是这些:

let const

解构赋值

新增运算符 ... ?. ??

模板字符串

内置对象新增了方法

Symbol 和 BigInt

Map Set 数据结构

Proxy Reflect

箭头函数

generator

promise

async await

class

ESModules

在项目中使用

let const

  1. let 和 const 都具有块级作用域,因此在循环或条件语句中使用时,要注意变量的作用域。

  2. let 和 const 都存在“暂时性死区”(Temporal Dead Zone, TDZ)的问题。在声明之前,尝试访问这些变量会导致错误。

  3. 使用 let 和 const 声明的变量不能被重复声明。这避免了变量名冲突。

  4. const 用于声明常量,表示变量的绑定不可更改,但对象的属性仍然可以修改,因此要注意深浅拷贝问题。

  5. const 在声明时必须初始化,而 let 可以先声明后赋值。

  6. 尽量避免在全局作用域中使用 let 和 const,推荐在函数或模块中使用,以减少命名冲突。

让一个 const 声明的对象或数组不可变的方法:

  1. 使用 Object.freeze()
const obj = Object.freeze({ key: "value" });
obj.key = "newValue"; // 不会改变原对象

// 冻结对象
const arr = Object.freeze([
Object.freeze({ key: "value" }),
Object.freeze({ key: "anotherValue" }),
]);
  1. 深度冻结

如果对象包含嵌套对象,需要使用递归冻结。

function deepFreeze(obj) {
Object.freeze(obj);
Object.keys(obj).forEach((key) => {
if (obj[key] && typeof obj[key] === "object") {
deepFreeze(obj[key]);
}
});
}

const nestedObj = { key: { subKey: "value" } };
deepFreeze(nestedObj);
  1. 使用 Immutable.js 或类似库

使用库如 Immutable.js 创建不可变的数据结构。

const { List, Map } = require("immutable");
const immutableList = List([Map({ key: "value" })]);
  1. 使用 ES6 的展开运算符:

对于数组,可以使用展开运算符创建新的数组副本,而不直接修改原数组。

const arr = [1, 2, 3];
const newArr = [...arr]; // 创建新数组

解构赋值

  1. 数组的结构赋值
const arr = [1, 2, 3];

// 基本用法
const [a, b] = arr; // a = 1, b = 2

// 跳过元素
const [first, , third] = arr; // first = 1, third = 3

// 剩余参数
const [x, ...rest] = arr; // x = 1, rest = [2, 3]
  1. 对象的结构赋值
const obj = { name: "Alice", age: 25 };

// 基本用法
const { name, age } = obj; // name = 'Alice', age = 25

// 重命名变量
const { name: fullName } = obj; // fullName = 'Alice'

// 默认值
const { height = 180 } = obj; // height = 180, 因为 obj 中没有 height
  1. 嵌套结构赋值
const nested = { a: 1, b: { c: 2 } };

// 对象嵌套
const {
b: { c },
} = nested; // c = 2

const arrNested = [
[1, 2],
[3, 4],
];
const [[one, two], [three, four]] = arrNested; // one = 1, two = 2
  1. 解构赋值与函数参数
function display({ name, age }) {
console.log(`Name: ${name}, Age: ${age}`);
}

const person = { name: "Bob", age: 30 };
display(person); // 输出: Name: Bob, Age: 30
  1. 交换变量值
let a = 1;
let b = 2;
[a, b] = [b, a]; // a = 2, b = 1
  1. 结构赋值重命名
const person = { name: "Alice", age: 25 };

// 重命名
const { name: fullName, age: years } = person;

新增的运算符

  1. 扩展运算符 (...)

作用

  • 在数组或对象中展开元素。

使用场景

  • 合并数组或对象。
  • 复制数组或对象。
  • 函数参数收集。
// 数组
const arr1 = [1, 2, 3];
const arr2 = [...arr1, 4, 5]; // 合并数组

// 对象
const obj1 = { a: 1, b: 2 };
const obj2 = { ...obj1, c: 3 }; // 合并对象

// 函数参数收集
function sum(...numbers) {
return numbers.reduce((total, num) => total + num, 0);
}
console.log(sum(1, 2, 3)); // 6
  1. 可选链运算符 (?.)

作用:

  • 允许安全地访问对象的深层属性,避免因访问未定义的属性而导致的错误。

使用场景:

  • 当不确定某个对象的某个属性是否存在时,可以避免手动检查每个层级。
const user = {
profile: {
name: "Alice",
},
};

// 安全访问
const userName = user.profile?.name; // 'Alice'
const userAge = user.profile?.age; // undefined
  1. 空值合并运算符 (??)

作用:

  • 返回其左侧操作数,如果其为 null 或 undefined,则返回右侧操作数。 使用场景:

  • 在赋值时提供默认值,特别是处理可能为空的值。

const value1 = null;
const value2 = 5;
const result = value1 ?? value2; // 5

const value3 = 0;
const result2 = value3 ?? 10; // 0,因为 0 不是 null 或 undefined
  1. 逻辑赋值运算符
// 或赋值运算符
x ||= y;
// 等同于
x || (x = y);

// 与赋值运算符
x &&= y;
// 等同于
x && (x = y);

// Null 赋值运算符
x ??= y;
// 等同于
x ?? (x = y);

模板字符串

模板字符串(template string)是增强版的字符串,用反引号(`)标识。

模板字符串使用反引号( )包裹,可以跨多行书写字符串。

如果在模板字符串中需要使用反引号,则前面要用反斜杠转义。

模板字符串中嵌入变量,需要将变量名写在$之中。大括号内部可以放入任意的 JavaScript 表达式,可以进行运算,以及引用对象属性。

模板字符串之中还能调用函数。

let name = "Bob",
time = "today";
console.log(`Hello ${name}, how are you ${time}?`);

function fn() {
return "Hello World";
}

console.log(`foo ${fn()} bar`);

内置对象新增了方法

字符串扩展

正则扩展

数值扩展

函数扩展

数组扩展

对象扩展

Symbol 和 BigInt

Symbol:

用于创建唯一的标识符,避免命名冲突,适用于对象属性。

Symbol 使用场景 对象属性:可以使用 Symbol 作为对象的属性键,以避免属性冲突。

const uniqueKey = Symbol("key");
const obj = {
[uniqueKey]: "value",
};

console.log(obj[uniqueKey]); // 'value'

Symbol 还可以用于定义自定义的迭代器,例如实现 Symbol.iterator 方法。

const myIterable = {
[Symbol.iterator]() {
let step = 0;
return {
next() {
step++;
return step <= 3 ? { value: step, done: false } : { done: true };
},
};
},
};

for (const value of myIterable) {
console.log(value); // 1, 2, 3
}

BigInt:

用于表示任意精度的整数,适用于大数计算,特别是在需要超出安全整数范围时。

const bigInt1 = BigInt(123456789012345678901234567890);
const bigInt2 = 123456789012345678901234567890n;

console.log(bigInt1); // 123456789012345678901234567890n
console.log(bigInt1 === bigInt2); // true

注意事项 BigInt 不能与 Number 直接进行运算,必须进行类型转换。

const num = 5;
const bigNum = 10n;

// 下面的代码会抛出错误
// console.log(num + bigNum);

// 正确方式
console.log(num + Number(bigNum)); // 15

Map Set 数据结构

Map 数据结构

Map 对象保存键值对,并且能够记住键的原始插入顺序。任何值(对象或者基本类型)都可以作为一个键或一个值。

Set 数据结构

Set 对象允许你存储任何类型的唯一值,无论是原始值或者是对象引用。

Proxy Reflect

Proxy(代理) 是 ES6 中新增的一个特性。Proxy 让我们能够以简洁易懂的方式控制外部对对象的访问。其功能非常类似于设计模式中的代理模式。 Reflect 是一个内置的对象,它提供拦截 JavaScript 操作的方法。这些方法与 proxy handler 的方法相同。Reflect 不是一个函数对象,因此它是不可构造的。

与大多数全局对象不同 Reflect 并非一个构造函数,所以不能通过 new 运算符对其进行调用,或者将 Reflect 对象作为一个函数来调用。Reflect 的所有属性和方法都是静态的(就像 Math 对象)。

箭头函数

箭头函数的 this 指向声明时所在作用域下的 this 值。

箭头函数和普通函数的区别

语法: 箭头函数使用 ()=> ,普通函数使用 function 关键字来定义函数。

箭头函数没有自己的 this,它会继承其所在作用域的 this 值。而普通函数的 this 则由函数调用时的上下文决定,可以通过 call、apply、bind 方法来改变。

箭头函数没有自己的 arguments 对象,它可以通过 rest 参数语法来接收不定数量的参数。而普通函数则有自己的 arguments 对象,它可以接收任意数量的参数。

箭头函数不能作为构造函数使用,不能使用 new 来实例化,因为它没有自己的 this,而普通函数可以用 new 来创建新的对象。

箭头函数不能使用 yield 关键字来定义生成器函数,而普通函数可以。

箭头函数不支持 call()/apply()函数特性

箭头函数没有 prototype 属性

原型函数不能定义成箭头函数

generator

生成器是一种特殊的函数,可以暂停执行并在后续时间继续执行。生成器在调用时不会立即执行,而是返回一个迭代器对象,可以通过该对象逐步执行生成器中的代码。

生成器使用 function* 语法定义,内部可以使用 yield 关键字来暂停执行并返回值。

function* myGenerator() {
yield 1;
yield 2;
yield 3;
}

const gen = myGenerator();

console.log(gen.next()); // { value: 1, done: false }
console.log(gen.next()); // { value: 2, done: false }
console.log(gen.next()); // { value: 3, done: false }
console.log(gen.next()); // { value: undefined, done: true }

生成器的特点

可暂停执行:生成器可以在执行过程中暂停和恢复。

迭代器协议:生成器自动实现了迭代器接口,可以与 for...of 循环一起使用。

生成器的应用场景

异步编程:生成器可以与 Promise 结合使用,简化异步代码的写法。

状态机:生成器可以用来构建状态机,通过 yield 暂停和恢复状态。

生成器是 JavaScript 中一个强大的特性,提供了一种优雅的方式来处理迭代和异步编程。它们通过暂停和恢复执行,使得代码更加灵活和可读。

promise

async await

class

ES6 的 class 可以看作只是一个语法糖,它的绝大部分功能,ES5 都可以做到,新的 class 写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。

function Point(x, y) {
this.x = x;
this.y = y;
}

Point.prototype.toString = function () {
return "(" + this.x + ", " + this.y + ")";
};

var p = new Point(1, 2);

// es6 写法
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}

toString() {
return "(" + this.x + ", " + this.y + ")";
}
}

继承

class Point {
/* ... */
}

class ColorPoint extends Point {
constructor(x, y, color) {
super(x, y); // 调用父类的constructor(x, y)
this.color = color;
}

toString() {
return this.color + " " + super.toString(); // 调用父类的toString()
}
}

ESModules

ES Modules(ECMAScript 模块)是 JavaScript 的模块化机制,允许将代码分割成小的、可重用的模块。它提供了一种标准化的方法来导入和导出代码,从而实现模块之间的依赖管理。

导出(Export)

使用 export 关键字来导出模块中的变量、函数或类。

// module.js
export const name = "Alice";
export function greet() {
console.log("Hello, " + name);
}

导入(Import)

使用 import 关键字来导入其他模块的导出。

// main.js
import { name, greet } from "./module.js";

console.log(name); // 'Alice'
greet(); // 'Hello, Alice'

默认导出

模块可以有一个默认导出,使用 export default 语法。

// module.js
const defaultFunction = () => {
console.log("This is the default function");
};
export default defaultFunction;
// main.js
import defaultFunc from "./module.js";
defaultFunc(); // 'This is the default function'

模块的特点

静态分析:ES Modules 在编译时解析,支持更好的优化和错误检测。

作用域:每个模块都有自己的作用域,避免全局命名冲突。

异步加载:支持异步加载模块,有助于性能优化。

使用场景

代码分离:将功能模块化,提升代码的可维护性。

库的开发:在开发共享库时,可以通过 ES Modules 导出接口。

ES Modules 提供了一种清晰、简洁的方式来组织 JavaScript 代码,使得模块的创建和使用更加直观。它在现代 JavaScript 开发中被广泛使用,支持更好的代码管理和优化。

ECMAScript 规范发布流程如下:

ECMAScript 规范是由 ECMA 国际(ECMA International)维护的 JavaScript 标准。其发布流程涉及多个步骤,以确保规范的质量和稳定性。

  1. 提案阶段:任何人可以提出新特性,分为五个阶段:Stage 0(草案)、Stage 1(初步)、Stage 2(实现)、Stage 3(规范)、Stage 4(完成)。

  2. 技术委员会(TC39):由专家和开发者组成,定期会议评估提案,决定是否推进。

  3. 规范文档:提案进入 Stage 3 后进行编辑和审核,发布草案供公众反馈。

  4. 正式发布:经过审查后纳入下一个 ECMAScript 版本,标记版本号(如 ES6, ES2015)。

  5. 反馈与改进:新版本发布后收集社区反馈,持续改进规范和特性。

ECMAScript 规范发布流程通过多个阶段和技术委员会的审查,确保新特性在质量和实用性上的稳定。这个过程强调社区参与和持续改进,以适应不断变化的开发需求。