ES6(ES2015)新特性完整指南

1 阅读2分钟

ES6(ES2015)新特性

发布时间:2015年6月 ES6(即 ES2015)是 JavaScript 历史上变化最大的一次升级,正式引入了块级作用域、类、模块、Promise、Generator、Proxy 等一整套现代语法与能力。

说明:本文件只保留 ES2015 正式特性,不混入 ES2016+ 才加入的能力(例如 Array.prototype.includes()padStart()Object.entries()Promise.finally()Array.prototype.flat() 等)。


1. let 和 const

let

  • 块级作用域,只在当前 {} 内有效
  • 不存在传统意义上的变量提升,存在暂时性死区(TDZ)
  • 同一作用域内不能重复声明
{
  let a = 10;
  var b = 1;
}

console.log(b);  // 1
console.log(a);  // ReferenceError

const

  • 用于声明常量,必须立即初始化
  • 也是块级作用域
  • 对于引用类型,地址不可变,但内容可以变
const PI = 3.14;
const arr = [1, 2, 3];
arr.push(4);      // 允许
// arr = [];      // TypeError

暂时性死区(TDZ)

{
  // console.log(x);  // ReferenceError
  let x = 1;
}

2. 解构赋值(Destructuring)

数组解构

let [a, b, c] = [1, 2, 3];
let [x, , y] = [1, 2, 3];
let [m, ...n] = [1, 2, 3, 4];

console.log(a, b, c);  // 1 2 3
console.log(x, y);     // 1 3
console.log(n);        // [2, 3, 4]

对象解构

let { name, age } = { name: '张三', age: 18 };
let { name: userName } = { name: '李四' };

console.log(name);      // 张三
console.log(userName);  // 李四

默认值

let [a = 1, b = 2] = [3];
let { x = 10, y = 20 } = { x: 5 };

console.log(a, b);  // 3 2
console.log(x, y);  // 5 20

函数参数解构

function greet({ name, age }) {
  console.log(`你好,${name},今年 ${age} 岁`);
}

greet({ name: '王五', age: 20 });

3. 字符串扩展

模板字符串

支持换行、插值、表达式:

let name = '张三';
let age = 18;

let str = `你好,${name},明年 ${age + 1} 岁`;
console.log(str);

标签模板

function tag(strings, ...values) {
  console.log(strings);
  console.log(values);
}

let name = '张三';
let age = 18;
tag`姓名:${name},年龄:${age}`;

新方法

'hello'.includes('ell');      // true
'hello'.startsWith('he');     // true
'hello'.endsWith('lo');       // true
'hello'.repeat(3);            // 'hellohellohello'

Unicode 增强

'𠮷'.length;                  // 2
'𠮷'.codePointAt(0);          // 134071
String.fromCodePoint(134071); // '𠮷'
'\u{20BB7}';                  // '𠮷'

String.raw()

String.raw`Hi\n${2 + 3}`;
// 'Hi\\n5'

4. 数值与 Math 扩展

二进制和八进制表示法

0b111110111 === 503;  // true
0o767 === 503;        // true

Number 新方法与常量

Number.isFinite(15);        // true
Number.isNaN(NaN);          // true
Number.isInteger(5);        // true
Number.EPSILON;             // 2.220446049250313e-16
Number.MAX_SAFE_INTEGER;    // 9007199254740991
Number.MIN_SAFE_INTEGER;    // -9007199254740991

Math 新方法

Math.trunc(4.9);      // 4
Math.sign(-5);        // -1
Math.cbrt(8);         // 2
Math.hypot(3, 4);     // 5

5. 函数扩展

默认参数

function log(x, y = 'World') {
  console.log(x, y);
}

log('Hello');          // Hello World
log('Hello', 'China'); // Hello China

rest 参数

function sum(...nums) {
  return nums.reduce((total, item) => total + item, 0);
}

sum(1, 2, 3);  // 6

箭头函数

const add = (a, b) => a + b;
const square = n => n * n;
const getObj = id => ({ id, name: 'test' });

注意:

  • 箭头函数没有自己的 this
  • 没有 arguments
  • 不能当构造函数使用
const obj = {
  id: 1,
  normal() {
    console.log(this.id);
  },
  arrow: () => {
    console.log(this.id);
  }
};

函数名 name 属性

function foo() {}
console.log(foo.name);  // 'foo'

尾调用优化(规范层面)

尾调用是函数最后一步调用另一个函数。ES2015 在规范层面定义了相关优化空间,但实际引擎支持并不统一,因此开发中不要强依赖它。


6. 数组扩展

扩展运算符 ...

let arr1 = [1, 2];
let arr2 = [3, 4];
let arr3 = [...arr1, ...arr2];

function add(x, y) {
  return x + y;
}

add(...[1, 2]);  // 3

Array.from()

将类数组或可迭代对象转为真正数组:

Array.from({ length: 3 }, (_, i) => i);  // [0, 1, 2]
Array.from('hello');                      // ['h', 'e', 'l', 'l', 'o']

Array.of()

Array.of(1, 2, 3);  // [1, 2, 3]
Array.of(3);        // [3]

find() 和 findIndex()

[1, 2, 3, 4].find(x => x > 2);       // 3
[1, 2, 3, 4].findIndex(x => x > 2);  // 2

fill()

[1, 2, 3].fill(0);        // [0, 0, 0]
[1, 2, 3].fill(0, 1, 2);  // [1, 0, 3]

copyWithin()

复制数组内部指定位置的成员到其他位置:

[1, 2, 3, 4, 5].copyWithin(0, 3);  // [4, 5, 3, 4, 5]

keys()、values()、entries()

let arr = ['a', 'b'];

for (let key of arr.keys()) {
  console.log(key);  // 0, 1
}

for (let value of arr.values()) {
  console.log(value);  // 'a', 'b'
}

for (let [index, value] of arr.entries()) {
  console.log(index, value);
}

7. 对象扩展

属性简写

let name = '张三';
let age = 18;
let obj = { name, age };

属性名表达式

let key = 'name';
let obj = {
  [key]: '张三'
};

方法简写

let obj = {
  hello() {
    return '你好';
  }
};

Object.is()

Object.is(NaN, NaN);  // true
Object.is(+0, -0);    // false
NaN === NaN;          // false

Object.assign()

let target = { a: 1 };
let source = { b: 2 };
let result = Object.assign(target, source);

console.log(result);  // { a: 1, b: 2 }

Object.setPrototypeOf() / Object.getPrototypeOf()

let proto = { greet() { return 'hello'; } };
let obj = {};

Object.setPrototypeOf(obj, proto);
console.log(Object.getPrototypeOf(obj) === proto);  // true

super 关键字(对象方法中)

let proto = {
  foo() {
    return 'hello';
  }
};

let obj = {
  foo() {
    return super.foo() + ' world';
  }
};

Object.setPrototypeOf(obj, proto);
obj.foo();  // 'hello world'

8. Symbol

表示独一无二的值,是第 7 种原始数据类型:

let s1 = Symbol('foo');
let s2 = Symbol('foo');

console.log(s1 === s2);  // false

作为对象属性名

const id = Symbol('id');
let user = {
  [id]: 1001,
  name: '张三'
};

console.log(user[id]);
console.log(Object.getOwnPropertySymbols(user));

内置 Symbol

let arr = [1, 2, 3];
let iter = arr[Symbol.iterator]();
console.log(iter.next());

9. Set、Map、WeakSet、WeakMap

Set

值唯一的集合:

let set = new Set([1, 2, 2, 3]);
set.add(4);
set.delete(1);
set.has(2);   // true
set.size;     // 3
[...set];     // [2, 3, 4]

Map

键可以是任意类型:

let map = new Map();
map.set('name', '张三');
map.set(1, '数字键');

map.get('name');   // '张三'
map.has(1);        // true
map.size;          // 2

WeakSet / WeakMap

  • 只能存对象(ES2015 语义下)
  • 是弱引用,不阻止垃圾回收
  • 不可遍历
let obj = {};
let weakMap = new WeakMap();
weakMap.set(obj, 'data');

let weakSet = new WeakSet();
weakSet.add(obj);

10. Proxy 和 Reflect

Proxy

可以拦截对象的读取、赋值、删除、函数调用等操作:

let obj = { name: '张三' };
let proxy = new Proxy(obj, {
  get(target, key) {
    return key in target ? target[key] : '不存在';
  },
  set(target, key, value) {
    target[key] = value;
    return true;
  }
});

console.log(proxy.name);  // 张三
console.log(proxy.age);   // 不存在
proxy.age = 18;

Reflect

与 Proxy 拦截器一一对应,提供默认行为:

Reflect.get(obj, 'name');
Reflect.set(obj, 'age', 18);
Reflect.has(obj, 'name');
Reflect.deleteProperty(obj, 'name');

11. Promise

Promise 是 ES2015 正式引入的异步编程解决方案,用于解决回调地狱。

let promise = new Promise((resolve, reject) => {
  setTimeout(() => resolve('成功'), 1000);
});

promise
  .then(res => {
    console.log(res);
  })
  .catch(err => {
    console.error(err);
  });

常用静态方法

Promise.resolve(42);
Promise.reject(new Error('err'));
Promise.all([p1, p2, p3]);
Promise.race([p1, p2, p3]);

状态

  • pending
  • fulfilled
  • rejected

12. Iterator 和 for...of

Iterator

任何实现了 next() 方法并返回 { value, done } 的对象,都可以被视为迭代器。

let arr = [1, 2, 3];
let iter = arr[Symbol.iterator]();

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

for...of

用于遍历可迭代对象:

for (let val of [1, 2, 3]) {
  console.log(val);
}

for (let [key, val] of new Map([['a', 1], ['b', 2]])) {
  console.log(key, val);
}

13. Generator 生成器

Generator 是一种可以暂停和恢复执行的函数。

function* helloGenerator() {
  yield 'hello';
  yield 'world';
  return 'ending';
}

let gen = helloGenerator();
console.log(gen.next());
console.log(gen.next());
console.log(gen.next());

与 Iterator 的关系

Generator 函数执行后返回一个迭代器对象,因此它是生成迭代器的语法糖。


14. Class 类

ES6 提供了更清晰的类语法,本质仍然基于原型。

class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }

  greet() {
    return `我是 ${this.name},今年 ${this.age} 岁`;
  }

  static create(name) {
    return new Person(name, 0);
  }
}

class Student extends Person {
  constructor(name, age, grade) {
    super(name, age);
    this.grade = grade;
  }

  greet() {
    return `${super.greet()},读 ${this.grade} 年级`;
  }
}

关键点

  • constructor 是构造方法
  • extends 用于继承
  • 子类中必须先 super() 再使用 this
  • static 定义静态方法

15. 模块化(Module)

ES2015 正式定义了原生模块语法。

export 导出

export const name = '张三';
export function add(a, b) {
  return a + b;
}

export default class Person {}

import 导入

import { name, add } from './module.js';
import Person from './module.js';
import * as mod from './module.js';

特点

  • 静态分析友好
  • 支持按需导出与导入
  • import 会提升到模块顶部执行

16. 正则、二进制数组与其他补充

RegExp 扩展

ES2015 为正则引入了 uy 标志:

/^.$/u.test('𠮷');     // true,正确识别 Unicode 字符
let r = /a/y;
r.lastIndex = 1;
r.exec('ba');         // ['a']
  • u:Unicode 模式
  • y:粘连匹配,要求从 lastIndex 位置开始匹配

二进制数组(TypedArray)

let buffer = new ArrayBuffer(16);
let int32View = new Int32Array(buffer);
int32View[0] = 42;

console.log(int32View[0]);  // 42

相关对象:

ArrayBuffer  -> 原始二进制数据缓冲区
DataView     -> 灵活读写二进制数据
TypedArray   -> 各种类型化数组视图

new.target

可用于判断函数是否通过 new 调用:

function Person(name) {
  if (!new.target) {
    throw new Error('必须使用 new 调用');
  }
  this.name = name;
}

总结

模块代表特性
变量声明letconst
结构处理解构赋值、模板字符串、展开运算符
函数能力默认参数、rest、箭头函数
数据结构SetMapWeakSetWeakMapSymbol
面向对象classextendssuper
异步编程Promise
元编程ProxyReflect
遍历机制Iteratorfor...ofGenerator
模块系统importexport
底层能力TypedArray、RegExp u/ynew.target

ES6(ES2015)奠定了现代 JavaScript 的基础。后续每年的 ECMAScript 更新,大多是在这套能力之上继续迭代和补充。