ECMAScript 6.0(简称 ES6)是 JavaScript 语言的下一代标准。它在语言层面为 JavaScript 引入了很多新语法和 API ,使得 JavaScript 语言可以用来编写复杂的大型应用程序。例如:
- 规范模块化;
- Class 语法;
- 用
let
声明代码块内有效的变量 ,用const
声明常量; - 箭头函数;
- async 函数;
- Set 和 Map 数据结构。
通过这些新特性,可以更加高效地编写代码,专注于解决问题本身。但遗憾的是不同浏览器对这些特性的支持不一致,使用了这些特性的代码可能会在部分浏览器下无法运行。为了解决兼容性问题,需要把 ES6 代码转换成 ES5 代码,Babel 是目前解决这个问题最好的工具。 Babel 的插件机制让它可灵活配置,支持把任何新语法转换成 ES5 的写法。
规范模块化
ES6 模块的设计思想是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。CommonJS 和 AMD 模块,都只能在运行时确定这些东西。比如,CommonJS 模块就是对象,输入时必须查找对象属性。
// CommonJS模块
let { stat, exists, readfile } = require('fs');
// 等同于
let _fs = require('fs');
let stat = _fs.stat;
let exists = _fs.exists;
let readfile = _fs.readfile;
上面代码的实质是整体加载fs
模块(即加载fs的所有方法),生成一个对象(_fs
),然后再从这个对象上面读取 3 个方法。这种加载称为“运行时加载”,因为只有运行时才能得到这个对象,导致完全没办法在编译时做“静态优化”。
ES6 模块不是对象,而是通过export
命令显式指定输出的代码,再通过import
命令输入。
// ES6模块
import { stat, exists, readFile } from 'fs';
上面代码的实质是从fs
模块加载 3 个方法,其他方法不加载。
ES6 模块与 CommonJS 模块完全不同,它们有三个重大差异。
- CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。
- CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。
- CommonJS 模块的require()是同步加载模块,ES6 模块的import命令是异步加载,有一个独立的模块依赖的解析阶段。
Set 和 Map 数据结构
Set 本身是一个构造函数,用来生成 Set 数据结构。它类似于数组,但是成员的值都是唯一的,没有重复的值。
Set函数可以接受一个数组(或者具有 iterable 接口的其他数据结构)作为参数,用来初始化。
// 例一
const set = new Set([1, 2, 3, 4, 4]);
[...set] // [1, 2, 3, 4]
// 例二
const items = new Set([1, 2, 3, 4, 5, 5, 5, 5]);
items.size // 5
// 例三
const set = new Set(document.querySelectorAll('div'));
set.size // 56
// 类似于
const set = new Set();
document.querySelectorAll('div').forEach(div => set.add(div));
set.size // 56
Map 数据结构类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。也就是说,Object 结构提供了“字符串 — 值”的对应,Map 结构提供了“值 — 值”的对应,是一种更完善的 Hash 结构实现。
Map构造函数接受数组作为参数,实际上执行的是下面的算法。
const items = [
['name', '张三'],
['title', 'Author']
];
const map = new Map();
items.forEach(
([key, value]) => map.set(key, value)
);
任何具有 Iterator 接口、且每个成员都是一个双元素的数组的数据结构都可以当作Map构造函数的参数。这就是说,Set和Map都可以用来生成新的 Map。
const set = new Set([
['foo', 1],
['bar', 2]
]);
const m1 = new Map(set);
m1.get('foo') // 1
const m2 = new Map([['baz', 3]]);
const m3 = new Map(m2);
m3.get('baz') // 3
其他:
- 如果对同一个键多次赋值,后面的值将覆盖前面的值。
- 如果读取一个未知的键,则返回undefined。
- 只有对同一个对象的引用,Map 结构才将其视为同一个键。
const map = new Map();
map.set(['a'], 555);
map.get(['a']) // undefined
// 上面代码的set和get方法,表面是针对同一个键,但实际上这是两个不同的数组实例,内存地址是不一样的,因此get方法无法读取该键,返回undefined。
async 函数
async 函数是 Generator 函数的语法糖。
例如:
有一个 Generator 函数,依次读取两个文件
const fs = require('fs');
const readFile = function (fileName) {
return new Promise(function (resolve, reject) {
fs.readFile(fileName, function(error, data) {
if (error) return reject(error);
resolve(data);
});
});
};
const gen = function* () {
const f1 = yield readFile('/etc/fstab');
const f2 = yield readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};
上面代码的函数gen可以写成async函数,就是下面这样。
const asyncReadFile = async function () {
const f1 = await readFile('/etc/fstab');
const f2 = await readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};
一比较就会发现,async
函数就是将 Generator 函数的星号(*
)替换成async
,将yield
替换成await
,仅此而已。
async函数对 Generator 函数的改进,体现在以下四点:
- 内置执行器
- 更好的语义
- 更广的适用性
- 返回值是 Promise