ES6入门

简介

ECMAScript 和 JavaScript的关系

前者是后者的规格,后者是前者的一种实现(另外的 ECMAScript 方言还有 JScript 和 ActionScript)。日常场合,这两个词是可以互换的。

ES6 与 ECMAScript2015 的关系

ES6 既是一个历史名词,也是一个泛指,含义是 5.1 版以后的 JavaScript 的下一代标准,涵盖了 ES2015、ES2016、ES2017 等等,而 ES2015 则是正式名称,特指该年发布的正式版本的语言标准。本书中提到 ES6 的地方,一般是指 ES2015 标准,但有时也是泛指“下一代 JavaScript 语言”。

ECMAScript 的历史

目前,各大浏览器对 ES6 的支持可以查看kangax.github.io/compat-table/es6/

Node.js 是 JavaScript 的服务器运行环境(runtime)。它对 ES6 的支持度更高。除了那些默认打开的功能,还有一些语法功能已经实现了,但是默认没有打开。使用下面的命令,可以查看 Node.js 默认没有打开的 ES6 实验性语法。

// Linux & Mac
$ node --v8-options | grep harmony

// Windows
$ node --v8-options | findstr harmony

Babel 转码器

Babel是一个广泛使用的 ES6 转码器,可以将 ES6 代码转为 ES5 代码,从而在老版本的浏览器执行。这意味着,你可以用 ES6 的方式编写程序,又不用担心现有环境是否支持。下面是一个例子:

// 转码前
input.map(item => item + 1);

// 转码后
input.map(function (item) {
  return item + 1;
});

下面的命令在项目目录中,安装 Babel。

$ npm install --save-dev @babel/core

配置文件 .babelrc

Babel 的配置文件是.babelrc,存放在项目的根目录下。使用 Babel 的第一步,就是配置这个文件。

该文件用来设置转码规则和插件,基本格式如下。

{
  "presets": [],
  "plugins": []
}

presets字段设定转码规则,官方提供以下的规则集,你可以根据需要安装。

# 最新转码规则
$ npm install --save-dev @babel/preset-env

# react 转码规则
$ npm install --save-dev @babel/preset-react

然后,将这些规则加入.babelrc

  {
    "presets": [
      "@babel/env",
      "@babel/preset-react"
    ],
    "plugins": []
  }

注意,以下所有 Babel 工具和模块的使用,都必须先写好.babelrc

命令行转码

Babel 提供命令行工具@babel/cli,用于命令行转码。

它的安装命令如下。

npm install --save-dev @babel/cli
# 转码结果输出到标准输出
$ npx babel example.js

# 转码结果写入一个文件
# --out-file 或 -o 参数指定输出文件
$ npx babel example.js --out-file compiled.js
# 或者
$ npx babel example.js -o compiled.js

# 整个目录转码
# --out-dir 或 -d 参数指定输出目录
$ npx babel src --out-dir lib
# 或者
$ npx babel src -d lib

# -s 参数生成source map文件
$ npx babel src -d lib -s

babel-node

@babel/node模块的babel-node命令,提供一个支持 ES6 的 REPL 环境。它支持 Node 的 REPL 环境的所有功能,而且可以直接运行 ES6 代码。

首先,安装这个模块。

$ npm install --save-dev @babel/node

然后,执行babel-node就进入 REPL 环境。

$ npx babel-node
> (x => x * 2)(1)
2

babel-node命令可以直接运行 ES6 脚本。将上面的代码放入脚本文件es6.js,然后直接运行。

# es6.js 的代码
# console.log((x => x * 2)(1));
$ npx babel-node es6.js
2

babel/register模块

@babel/register模块改写require命令,为它加上一个钩子。此后,每当使用require加载.js.jsx.es.es6后缀名的文件,就会先用 Babel 进行转码。

$ npm install --save-dev @babel/register

使用时,必须首先加载@babel/register

// index.js
require('@babel/register');
require('./es6.js');

然后,就不需要手动对index.js转码了。

$ node index.js
2

需要注意的是,@babel/register只会对require命令加载的文件转码,而不会对当前文件转码。另外,由于它是实时转码,所以只适合在开发环境使用。

polyfill

Babel 默认只转换新的 JavaScript 句法(syntax),而不转换新的 API,比如IteratorGeneratorSetMapProxyReflectSymbolPromise等全局对象,以及一些定义在全局对象上的方法(比如Object.assign)都不会转码。

举例来说,ES6 在Array对象上新增了Array.from方法。Babel 就不会转码这个方法。如果想让这个方法运行,可以使用core-jsregenerator-runtime(后者提供generator函数的转码),为当前环境提供一个垫片。

安装命令如下。

$ npm install --save-dev core-js regenerator-runtime

然后,在脚本头部,加入如下两行代码。

import 'core-js';
import 'regenerator-runtime/runtime';
// 或者
require('core-js');
require('regenerator-runtime/runtime);

Babel 默认不转码的 API 非常多,详细清单可以查看babel-plugin-transform-runtime模块的definitions.js文件。

浏览器环境

Babel 也可以用于浏览器环境,使用@babel/standalone模块提供的浏览器版本,将其插入网页。

<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script type="text/babel">
// Your ES6 code
</script>

注意,网页实时将 ES6 代码转为 ES5,对性能会有影响。生产环境需要加载已经转码完成的脚本。

Babel 提供一个REPL 在线编译器,可以在线将 ES6 代码转为 ES5 代码。转换后的代码,可以直接作为 ES5 代码插入网页运行。

let 和 const 命令

ES5 只有两种声明变量的方法:var命令和function命令。ES6 除了添加letconst命令,后面章节还会提到,另外两种声明变量的方法:import命令和class命令。所以,ES6 一共有 6 种声明变量的方法。

变量的解构赋值

数组

默认值

对象

默认值

字符串

数值和布尔值

函数参数

function move({x = 0, y = 0} = {}) {
  return [x, y];
}

move({x: 3, y: 8}); // [3, 8]
move({x: 3}); // [3, 0]
move({}); // [0, 0]
move(); // [0, 0]

上面代码中,函数move的参数是一个对象,通过对这个对象进行解构,得到变量xy的值。如果解构失败,xy等于默认值。

注意,下面的写法会得到不一样的结果。

function move({x, y} = { x: 0, y: 0 }) {
  return [x, y];
}

move({x: 3, y: 8}); // [3, 8]
move({x: 3}); // [3, undefined]
move({}); // [undefined, undefined]
move(); // [0, 0]

上面代码是为函数move的参数指定默认值,而不是为变量xy指定默认值,所以会得到与前一种写法不同的结果。

圆括号问题

解构赋值虽然很方便,但是解析起来并不容易。对于编译器来说,一个式子到底是模式,还是表达式,没有办法从一开始就知道,必须解析到(或解析不到)等号才能知道。

由此带来的问题是,如果模式中出现圆括号怎么处理。ES6 的规则是,只要有可能导致解构的歧义,就不得使用圆括号。

但是,这条规则实际上不那么容易辨别,处理起来相当麻烦。因此,建议只要有可能,就不要在模式中放置圆括号。

用途

  1. 交换变量的值
  2. 从函数返回多个值
  3. 函数参数的定义
  4. 提取JSON数据
  5. 函数参数的默认值
  6. 遍历Map结构
  7. 输入模块的指定方法

字符串的扩展

字符的Unicode表示法

JavaScript 共有 6 种方法可以表示一个字符。

'\z' === 'z'  // true
'\172' === 'z' // true
'\x7A' === 'z' // true
'\u007A' === 'z' // true
'\u{7A}' === 'z' // true

字符串的遍历器接口

直接输入U+2028和U+2029

JSON.stringify()的改造

模板字符串

实例:模板编译

标签模板

模板字符串的限制

字符串的新增方法

String.fromCodePoint()

String.raw()

实例方法:codePointAt()

实例方法:normalize()

实例方法:includes(), startsWith(), endsWith()

实例方法:repeat()

实例方法:padStart(),padEnd()

实例方法:trimStart(),trimEnd()

实例方法:matchAll()

实例方法:replaceAll()

正则的扩展

RegExp 构造函数

字符串的正则方法

u 修饰符

RegExp.prototype.unicode 属性

y 修饰符

RegExp.prototype.sticky 属性

RegExp.prototype.flags 属性

s 修饰符:dotAll 模式

后行断言

Unicode 属性类

具名组匹配

简介

解构赋值和替换

引用

正则匹配索引

String.prototype.matchAll()

数值的扩展

二进制和八进制表示法

NUmber.isFinite()、Number.isNaN()

Number.isFinite()用来检查一个数值是否为有限的(finite),即不是Infinity。对于非数值一律返回false。

Number.isFinite(NaN); // false
Number.isFinite(Infinity); // false
Number.isFinite(-Infinity); // false
Number.isFinite('foo'); // false
Number.isFinite(true); // false

Number.isNaN()用来检查一个值是否为NaN。对于非NaN一律返回false。
与传统的全局方法isFinite()isNaN()区别:传统方法先调用Number()再判断;而这新方法只对数值有效。

Number.parseInt()、Number.parseFloat()

将全局方法移到Number对象上,目的:减少全局性方法,模块化语言。

Number.isInteger()

用于判断一个数值是否为整数。25 和 25.0 被视为同一个值。
注意,由于 JavaScript 采用 IEEE 754 标准,数值存储为64位双精度格式,数值精度最多可以达到 53 个二进制位(1 个隐藏位与 52 个有效位)。如果数值的精度超过这个限度,第54位及后面的位就会被丢弃,这种情况下,Number.isInteger可能会误判。

Number.isInteger(3.0000000000000002) // true

上面代码中,Number.isInteger的参数明明不是整数,但是会返回true。原因就是这个小数的精度达到了小数点后16个十进制位,转成二进制位超过了53个二进制位,导致最后的那个2被丢弃了。
如果一个数值的绝对值小于Number.MIN_VALUE(5E-324),即小于 JavaScript 能够分辨的最小值,会被自动转为 0。这时,Number.isInteger也会误判。

Number.isInteger(5E-324) // false
Number.isInteger(5E-325) // true

上面代码中,5E-325由于值太小,会被自动转为0,因此返回true

总之,如果对数据精度的要求较高,不建议使用Number.isInteger()判断一个数值是否为整数。

Number.EPSILON

它表示 1 与大于 1 的最小浮点数之间的差。

Number.EPSILON === Math.pow(2, -52)
// true
Number.EPSILON
// 2.220446049250313e-16

引入一个这么小的量的目的,在于为浮点数计算,设置一个误差范围。我们知道浮点数计算是不精确的。

0.1 + 0.2
// 0.30000000000000004

0.1 + 0.2 - 0.3
// 5.551115123125783e-17

Number.EPSILON可以用来设置“能够接受的误差范围”。比如,误差范围设为 2 的-50 次方(即Number.EPSILON * Math.pow(2, 2)),即如果两个浮点数的差小于这个值,我们就认为这两个浮点数相等。

因此,Number.EPSILON的实质是一个可以接受的最小误差范围。

function withinErrorMargin (left, right) {
  return Math.abs(left - right) < Number.EPSILON * Math.pow(2, 2);
}

0.1 + 0.2 === 0.3 // false
withinErrorMargin(0.1 + 0.2, 0.3) // true

上面的代码为浮点数运算部署了一个误差检查函数。

安全整数和Number.isSafeInterger()

JavaScript 能够准确表示的整数范围在-253到253之间(不含两个端点),超过这个范围,无法精确表示这个值。

Math.pow(2, 53) // 9007199254740992

9007199254740992  // 9007199254740992
9007199254740993  // 9007199254740992

ES6 引入了Number.MAX_SAFE_INTEGERNumber.MIN_SAFE_INTEGER这两个常量,用来表示这个范围的上下限。

Number.MAX_SAFE_INTEGER === 9007199254740991
// true

Number.MIN_SAFE_INTEGER === -9007199254740991
// true

Number.isSafeInteger()则是用来判断一个整数是否落在这个范围之内。
实际使用这个函数时,需要注意。验证运算结果是否落在安全整数的范围内,不要只验证运算结果,而要同时验证参与运算的每个值。

Number.isSafeInteger(9007199254740993 - 990)
// true
9007199254740993 - 990
// 返回结果 9007199254740002
// 正确答案应该是 9007199254740003

Math对象的扩展

Math.trunc()

用于去除一个数的小数部分,返回整数部分。
对于非数值,Math.trunc内部使用Number方法将其先转为数值。
对于空值和无法截取整数的值,返回NaN。
对于没有部署这个方法的环境,可以用下面的代码模拟。

Math.trunc = Math.trunc || function(x) {
  return x < 0 ? Math.ceil(x) : Math.floor(x);
};

Math.sign()

用来判断一个数到底是正数、负数、还是零。对于非数值,会先将其转换为数值。

它会返回五种值。

  • 参数为正数,返回+1;
  • 参数为负数,返回-1;
  • 参数为 0,返回0;
  • 参数为-0,返回-0;
  • 其他值,返回NaN。

对于没有部署这个方法的环境,可以用下面的代码模拟。

Math.sign = Math.sign || function(x) {
  x = +x; // convert to a number
  if (x === 0 || isNaN(x)) {
    return x;
  }
  return x > 0 ? 1 : -1;
};

Math.cbrt()

用于计算一个数的立方根。
对于没有部署这个方法的环境,可以用下面的代码模拟。

Math.cbrt = Math.cbrt || function(x) {
  var y = Math.pow(Math.abs(x), 1/3);
  return x < 0 ? -y : y;
};

Math.clz32()

将参数转为 32 位无符号整数的形式,然后返回这个 32 位值里面有多少个前导 0。

Math.imul()

返回两个数以 32 位带符号整数形式相乘的结果,返回的也是一个 32 位的带符号整数。

Math.fround()

返回一个数的32位单精度浮点数形式。
对于没有部署这个方法的环境,可以用下面的代码模拟。

Math.fround = Math.fround || function (x) {
  return new Float32Array([x])[0];
};

Math.hypot()

返回所有参数的平方和的平方根。

对数方法

Math.expm1()

返回 e^x - 1^,即Math.exp(x) - 1

Math.log1p()

返回1 + x的自然对数,即Math.log(1 + x)。如果x小于-1,返回NaN

Math.log10()

返回以 10 为底的x的对数。如果x小于 0,则返回 NaN。

Math.log2()

返回以 2 为底的x的对数。如果x小于 0,则返回 NaN。

双曲函数方法

ES6 新增了 6 个双曲函数方法。

  • Math.sinh(x) 返回x的双曲正弦(hyperbolic sine)
  • Math.cosh(x) 返回x的双曲余弦(hyperbolic cosine)
  • Math.tanh(x) 返回x的双曲正切(hyperbolic tangent)
  • Math.asinh(x) 返回x的反双曲正弦(inverse hyperbolic sine)
  • Math.acosh(x) 返回x的反双曲余弦(inverse hyperbolic cosine)
  • Math.atanh(x) 返回x的反双曲正切(inverse hyperbolic tangent)

指数运算符

多个指数运算符连用时,是从最右边开始计算的。

// 相当于 2 ** (3 ** 2)
2 ** 3 ** 2
// 512

BigInt数据类型

这是 ECMAScript 的第八种数据类型。BigInt 只用来表示整数,没有位数的限制,任何位数的整数都可以精确表示。

函数的扩展

函数参数的默认值

参数默认值不是传值的,而是每次都重新计算默认值表达式的值。也就是说,参数默认值是惰性求值的。

let x = 99;
function foo(p = x + 1) {
  console.log(p);
}

foo() // 100

x = 100;
foo() // 101

上面代码中,参数p的默认值是x + 1。这时,每次调用函数foo,都会重新计算x + 1,而不是默认p等于 100。

下面是一个更复杂的例子。

var x = 1;
function foo(x, y = function() { x = 2; }) {
  var x = 3;
  y();
  console.log(x);
}

foo() // 3
x // 1

上面代码中,函数foo的参数形成一个单独作用域。这个作用域里面,首先声明了变量x,然后声明了变量yy的默认值是一个匿名函数。这个匿名函数内部的变量x,指向同一个作用域的第一个参数x。函数foo内部又声明了一个内部变量x,该变量与第一个参数x由于不是同一个作用域,所以不是同一个变量,因此执行y后,内部变量x和外部全局变量x的值都没变。

如果将var x = 3var去除,函数foo的内部变量x就指向第一个参数x,与匿名函数内部的x是一致的,所以最后输出的就是2,而外层的全局变量x依然不受影响。

var x = 1;
function foo(x, y = function() { x = 2; }) {
  x = 3;
  y();
  console.log(x);
}

foo() // 2
x // 1

rest参数

严格模式

name属性

箭头函数

箭头函数有几个使用注意点。

(1)函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。

(2)不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。

(3)不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。

(4)不可以使用yield命令,因此箭头函数不能用作 Generator 函数。
上面四点中,第一点尤其值得注意。this对象的指向是可变的,但是在箭头函数中,它是固定的。

尾调用优化

注意,目前只有 Safari 浏览器支持尾调用优化,Chrome 和 Firefox 都不支持。
4 1 1 3 1 2 2 2 3 1 3 5 5 1 1 2 3 5 8 13 21 34 55 89

函数参数的尾逗号

Function.prototype.toString()

catch命令的参数省略

数组的扩展

扩展运算符

属性名表达式

方法的name属性

属性的可枚举性和遍历

Array.from()

Array.of()

数组实例的copyWithin()

数组实例的find()和findIndex()

数组实例的fill()

数组实例的entries(),keys()和values()

数组实例的includes()

数组实例的flat()和flatMap()

数组的空位

Array.prototype.sort()的排序稳定性

对象的扩展

属性的简洁表示法

属性名表达式

方法的name属性

属性的可枚举性和遍历

super关键字

对象的扩展运算符

链判断运算符

Null判断运算符

对象的新增方法

Object.is()

Object.assign()

Object.getOwenPropertyDescriptors()

Symbol

Set 和Map 数据结构

Proxy

Reflect

Promise 对象

Iterator 和 for…of循环

Generator函数语法

Generator函数异步应用

async 函数

Class 的基本语法

Class 的继承

Module 的语法

Module 的加载实现

编程风格

读懂规格

异步遍历器

ArrayBuffer

最新提案

Decorator

参考链接

上一篇:Unexpected token '...'. Expected a property name解决


下一篇:Unexpected token '...'. Expected a property name解决