30. ES6 新特性概述
本章收集了本书所有章节的概述部分。
- 30.1. ES6 特性分类
- 30.2. 新的数字和
Math
特性
- 30.2.1. 新的整数字面量
- 30.2.2. 新的
Number
属性
- 30.2.3. 新的
Math
方法
- 30.3. 新的字符串特性
- 30.4. 符号
- 30.4.1. 用例 1:唯一的属性键
- 30.4.2. 用例 2:表示概念的常量
- 30.4.3. 陷阱:不能将符号强制转换为字符串
- 30.4.4. 哪些与属性键相关的操作识别符号?
- 30.5. 模板字面量
- 30.6. 变量和作用域
- 30.6.1.
let
- 30.6.2.
const
- 30.6.3. 声明变量的方式
- 30.7. 解构
- 30.7.1. 对象解构
- 30.7.2. 数组解构
- 30.7.3. 解构可以在哪里使用?
- 30.8. 参数处理
- 30.8.1. 默认参数值
- 30.8.2. 剩余参数
- 30.8.3. 通过解构命名参数
- 30.8.4. 展开运算符 (
...
)
- 30.9. ECMAScript 6 中的可调用实体
- 30.10. 箭头函数
- 30.11. 除类之外的新的 OOP 特性
- 30.11.1. 新的对象字面量特性
- 30.11.2.
Object
中的新方法
- 30.12. 类
- 30.13. 模块
- 30.13.1. 多个命名导出
- 30.13.2. 单个默认导出
- 30.13.3. 浏览器:脚本与模块
- 30.14.
for-of
循环
- 30.15. 新的数组特性
- 30.16. 映射和集合
- 30.16.1. 映射
- 30.16.2. 集合
- 30.16.3. 弱映射
- 30.17. 类型化数组
- 30.18. 可迭代对象和迭代器
- 30.18.1. 可迭代值
- 30.18.2. 支持迭代的结构
- 30.19. 生成器
- 30.19.1. 什么是生成器?
- 30.19.2. 生成器的种类
- 30.19.3. 用例:实现可迭代对象
- 30.19.4. 用例:更简单的异步代码
- 30.19.5. 用例:接收异步数据
- 30.20. 新的正则表达式特性
- 30.21. 用于异步编程的 Promise
- 30.21.1. 链式调用
then()
- 30.21.2. 并行执行异步函数
- 30.21.3. 词汇表:Promise
- 30.22. 使用代理进行元编程
30.1 ES6 特性分类
ES6 规范的介绍列出了所有新特性
ECMAScript 6 的一些主要增强功能包括模块、类声明、词法块作用域、迭代器和生成器、用于异步编程的 Promise、解构模式以及适当的尾调用。ECMAScript 内置库已经扩展,以支持更多的数据抽象,包括映射、集合和二进制数值数组,以及对字符串和正则表达式中 Unicode 补充字符的额外支持。现在可以通过子类化扩展内置库。
主要有三类特性
- 为已经存在的特性(例如,通过库)提供更好的语法。例如:
- 标准库中的新功能。例如:
- 全新的特性。例如:
30.2 新的数字和 Math
特性
30.2.1 新的整数字面量
现在可以使用二进制和八进制表示法指定整数
30.2.2 新的 Number
属性
全局对象 Number
获得了一些新属性
-
Number.EPSILON
用于比较浮点数,并考虑舍入误差。
-
Number.isInteger(num)
检查 num
是否为整数(没有小数部分的数字)
- 一个方法和常量,用于确定 JavaScript 整数是否为“安全”整数(在有符号 53 位范围内,不会丢失精度)
Number.isSafeInteger(number)
Number.MIN_SAFE_INTEGER
Number.MAX_SAFE_INTEGER
-
Number.isNaN(num)
检查 num
是否为 NaN
值。与全局函数 isNaN()
相比,它不会将其参数强制转换为数字,因此对于非数字更安全
Number
的另外三个方法与同名全局函数基本等效:Number.isFinite
、Number.parseFloat
、Number.parseInt
。
30.2.3 新的 Math
方法
全局对象 Math
具有用于数值、三角函数和按位运算的新方法。让我们看四个例子。
Math.sign()
返回数字的符号
Math.trunc()
删除数字的小数部分
Math.log10()
计算以 10 为底的对数
Math.hypot()
计算其参数的平方和的平方根(勾股定理)
30.3 新的字符串特性
新的字符串方法
ES6 有一种新的字符串字面量,称为“模板字面量”
30.4 符号
符号是 ECMAScript 6 中的一种新的原始类型。它们通过工厂函数创建
每次调用工厂函数时,都会创建一个新的唯一符号。可选参数是一个描述性字符串,在打印符号时显示(它没有其他用途)
30.4.1 用例 1:唯一的属性键
符号主要用作唯一的属性键——符号永远不会与任何其他属性键(符号或字符串)冲突。例如,可以通过使用存储在 Symbol.iterator
中的符号作为方法的键,使对象“可迭代”(可通过 for-of
循环和其他语言机制使用)(有关可迭代对象的更多信息,请参见关于迭代的章节)
在 A 行中,一个符号被用作方法的键。这个唯一的标记使对象可迭代,并使我们能够使用 for-of
循环。
30.4.2 用例 2:表示概念的常量
在 ECMAScript 5 中,您可能使用字符串来表示颜色等概念。在 ES6 中,您可以使用符号,并确保它们始终是唯一的
每次调用 Symbol('Red')
时,都会创建一个新的符号。因此,COLOR_RED
永远不会被误认为是另一个值。如果它是字符串 'Red'
,情况就会有所不同。
30.4.3 陷阱:不能将符号强制转换为字符串
将符号强制(隐式转换)为字符串会引发异常
唯一的解决办法是显式转换
禁止强制转换可以防止一些错误,但也会使使用符号变得更加复杂。
以下操作识别符号作为属性键
Reflect.ownKeys()
- 通过
[]
进行属性访问
Object.assign()
以下操作忽略符号作为属性键
Object.keys()
Object.getOwnPropertyNames()
-
for-in
循环
30.5 模板字面量
ES6 有两种新的字面量:模板字面量和标签模板字面量。这两种字面量的名称和外观相似,但它们却截然不同。因此,区分它们非常重要
- 模板字面量(代码):支持插值的字符串字面量
- 标签模板字面量(代码):函数调用
- Web 模板(数据):包含要填充的空白的 HTML
模板字面量是可以跨越多行并包含插值表达式(通过 ${···}
插入)的字符串字面量
标签模板字面量(简称:标签模板)是通过在模板字面量之前提及一个函数来创建的
标签模板是函数调用。在上一个例子中,调用了 String.raw
方法来生成标签模板的结果。
30.6 变量和作用域
ES6 提供了两种声明变量的新方法:let
和 const
,它们在很大程度上取代了 ES5 中声明变量的方法 var
。
30.6.1 let
let
的工作方式与 var
类似,但它声明的变量是“块级作用域”的,只存在于当前块中。var
是“函数作用域”的。
在下面的代码中,您可以看到 let
声明的变量 tmp
只存在于从 A 行开始的块中
30.6.2 const
const
的工作方式与 let
类似,但您声明的变量必须立即初始化,并且其值以后不能更改。
由于 for-of
循环每次迭代都会创建一个“绑定”(变量的存储空间),因此可以使用 const
声明循环变量
30.6.3 声明变量的方式
下表概述了在 ES6 中声明变量的六种方式(灵感来自 kangax 的表格)
|
提升 |
作用域 |
创建全局属性 |
var |
声明 |
函数 |
是 |
let |
暂时性死区 |
块 |
否 |
const |
暂时性死区 |
块 |
否 |
函数 |
完整 |
块 |
是 |
class |
否 |
块 |
否 |
import |
完整 |
模块全局 |
否 |
30.7 解构
“解构”是一种从存储在(可能是嵌套的)对象和数组中的数据中提取多个值的便捷方法。它可以在接收数据的位置使用(例如赋值语句的左侧)。如何提取值是通过模式指定的(请继续阅读以获取示例)。
30.7.1 对象解构
解构对象
解构有助于处理返回值
30.7.2 数组解构
数组解构(适用于所有可迭代值)
解构有助于处理返回值
30.7.3 解构可以在哪里使用?
解构可以在以下位置使用(我展示了数组模式来演示;对象模式也可以使用)
您还可以在 for-of
循环中进行解构
30.8 参数处理
ECMAScript 6 中的参数处理已得到显著升级。它现在支持参数默认值、剩余参数 (varargs) 和解构。
此外,扩展运算符有助于函数/方法/构造函数调用和数组字面量。
30.8.1 默认参数值
通过等号 (=
) 为参数指定*默认参数值*。如果调用者未提供参数值,则使用默认值。在以下示例中,y
的默认参数值为 0
30.8.2 剩余参数
如果使用剩余运算符 (...
) 作为参数名称的前缀,则该参数将通过数组接收所有剩余参数
30.8.3 通过解构的命名参数
如果在参数列表中使用对象模式进行解构,则可以模拟命名参数
A 行中的 = {}
使您可以在没有参数的情况下调用 selectEntries()
。
30.8.4 扩展运算符 (...
)
在函数和构造函数调用中,扩展运算符将可迭代值转换为参数
在数组字面量中,扩展运算符将可迭代值转换为数组元素
30.9 ECMAScript 6 中的可调用实体
在 ES5 中,单个结构(传统函数)扮演着三个角色
在 ES6 中,有更多专业化。现在,这三个职责的处理方式如下。就函数定义和类定义而言,定义是声明或表达式。
- 真实(非方法)函数
- 箭头函数(只有表达式形式)
- 传统函数(通过函数定义创建)
- 生成器函数(通过生成器函数定义创建)
- 方法
- 方法(由对象字面量和类定义中的方法定义创建)
- 生成器方法(由对象字面量和类定义中的生成器方法定义创建)
- 构造函数
特别是对于回调,箭头函数很方便,因为它们不会遮蔽周围作用域的 this
。
对于较长的回调和独立函数,传统函数是可以的。一些 API 使用 this
作为隐式参数。在这种情况下,您别无选择,只能使用传统函数。
请注意,我区分了
尽管它们的行为有所不同(稍后解释),但所有这些实体都是函数。例如
30.10 箭头函数
箭头函数有两个优点。
首先,它们比传统的函数表达式更简洁
其次,它们的 this
是从周围环境中获取的(*词法*)。因此,您不再需要 bind()
或 that = this
。
以下变量在箭头函数内部都是词法的
arguments
super
this
new.target
30.11 类之外的新 OOP 特性
30.11.1 新的对象字面量特性
方法定义
属性值简写
计算属性键
这种新语法也可用于方法定义
计算属性键的主要用例是使使用符号作为属性键变得容易。
30.11.2 Object
中的新方法
Object
最重要的新方法是 assign()
。传统上,此功能在 JavaScript 世界中称为 extend()
。与这种经典操作的工作方式相比,Object.assign()
只考虑*自身*(非继承)属性。
30.12 类
一个类和一个子类
使用类
在底层,ES6 类并不是什么全新的东西:它们主要提供更方便的语法来创建老式的构造函数。如果您使用 typeof
,您可以看到这一点
30.13 模块
JavaScript 已经存在模块很长时间了。但是,它们是通过库实现的,而不是内置于语言中。ES6 是 JavaScript 首次拥有内置模块。
ES6 模块存储在文件中。每个文件只有一个模块,每个模块只有一个文件。您可以通过两种方式从模块中导出内容。这两种方式可以混合使用,但通常最好单独使用它们。
30.13.1 多个命名导出
可以有多个*命名导出*
您也可以导入完整的模块
30.13.2 单个默认导出
可以有一个*默认导出*。例如,一个函数
或者一个类
请注意,如果默认导出函数或类(它们是匿名声明),则末尾没有分号。
30.13.3 浏览器:脚本与模块
|
脚本 |
模块 |
HTML 元素 |
<script> |
<script type="module"> |
默认模式 |
非严格 |
严格 |
顶级变量是 |
全局的 |
模块本地的 |
顶级 this 的值 |
window |
undefined |
执行 |
同步 |
异步 |
声明式导入(import 语句) |
否 |
是 |
程序化导入(基于 Promise 的 API) |
是 |
是 |
文件扩展名 |
.js |
.js |
30.14 for-of
循环
for-of
是 ES6 中的一个新循环,它取代了 for-in
和 forEach()
,并支持新的迭代协议。
使用它来循环遍历*可迭代*对象(数组、字符串、映射、集合等;请参阅“可迭代对象和迭代器”一章)
break
和 continue
在 for-of
循环内有效
在循环遍历数组时访问元素及其索引(of
前的方括号表示我们正在使用解构)
循环遍历映射中的 [键,值] 条目(of
前的方括号表示我们正在使用解构)
30.15 新的数组特性
新的静态 Array
方法
Array.from(arrayLike, mapFunc?, thisArg?)
Array.of(...items)
新的 Array.prototype
方法
- 迭代
Array.prototype.entries()
Array.prototype.keys()
Array.prototype.values()
- 搜索元素
Array.prototype.find(predicate, thisArg?)
Array.prototype.findIndex(predicate, thisArg?)
Array.prototype.copyWithin(target, start, end=this.length)
Array.prototype.fill(value, start=0, end=this.length)
30.16 映射和集合
除其他外,ECMAScript 6 中新增了以下四种数据结构:Map
、WeakMap
、Set
和 WeakSet
。
30.16.1 映射
映射的键可以是任意值
您可以使用带有 [键,值] 对的数组(或任何可迭代对象)来设置映射中的初始数据
30.16.2 集合
集合是唯一元素的集合
如您所见,如果将包含这些元素的可迭代对象(示例中的 arr
)传递给构造函数,则可以使用元素初始化集合。
30.16.3 弱映射
弱映射是一种映射,它不会阻止对其键进行垃圾回收。这意味着您可以将数据与对象关联起来,而不必担心内存泄漏。例如
30.17 类型化数组
类型化数组是 ECMAScript 6 API,用于处理二进制数据。
代码示例
ArrayBuffer
的实例存储要处理的二进制数据。使用两种*视图*来访问数据
- 类型化数组(
Uint8Array
、Int16Array
、Float32Array
等)将 ArrayBuffer 解释为单一类型元素的索引序列。
DataView
的实例允许您在 ArrayBuffer 内的任何字节偏移量处将数据作为多种类型(Uint8
、Int16
、Float32
等)的元素进行访问。
以下浏览器 API 支持类型化数组(详细信息在专门章节中提及)
- 文件 API
- XMLHttpRequest
- Fetch API
- Canvas
- WebSockets
- 以及更多
30.18 可迭代对象和迭代器
ES6 引入了一种遍历数据的新机制:*迭代*。迭代有两个核心概念
- *可迭代对象*是一种希望将其元素公开访问的数据结构。它通过实现键为
Symbol.iterator
的方法来做到这一点。该方法是*迭代器*的工厂。
- *迭代器*是指向遍历数据结构元素的指针(想想数据库中的游标)。
用 TypeScript 表示法表示为接口,这些角色如下所示
30.18.1 可迭代值
以下值是可迭代的
- 数组
- 字符串
- 映射
- 集合
- DOM 数据结构(正在进行中)
普通对象不可迭代(原因在专门章节中解释)。
30.18.2 支持迭代的结构
通过迭代访问数据的语言结构
- 通过数组模式进行解构
-
for-of
循环
-
Array.from()
:
- 扩展运算符 (
...
)
- 映射和集合的构造函数
-
Promise.all()
、Promise.race()
-
yield*
:
30.19 生成器
30.19.1 什么是生成器?
您可以将生成器视为可以暂停和恢复的进程(代码片段)
请注意新语法:function*
是*生成器函数*的新“关键字”(也有*生成器方法*)。yield
是生成器可以暂停自身的运算符。此外,生成器还可以通过 yield
接收输入和发送输出。
当您调用生成器函数 genFunc()
时,您将获得一个*生成器对象* genObj
,您可以使用它来控制进程
该进程最初在 A 行暂停。genObj.next()
恢复执行,genFunc()
内部的 yield
暂停执行
30.19.2 生成器的种类
生成器有四种
- 生成器函数声明
- 生成器函数表达式
- 对象字面量中的生成器方法定义
- 类定义(类声明或类表达式)中的生成器方法定义
30.19.3 用例:实现可迭代对象
生成器返回的对象是可迭代的;每个 yield
都会对迭代值序列做出贡献。因此,您可以使用生成器来实现可迭代对象,这些对象可以被各种 ES6 语言机制使用:for-of
循环、展开运算符 (...
) 等。
以下函数返回一个对象的属性的可迭代对象,每个属性对应一个 [键,值] 对
objectEntries()
的使用方法如下
objectEntries()
的确切工作原理在专门的章节中进行了解释。如果没有生成器,实现相同的功能需要做更多的工作。
30.19.4 用例:更简单的异步代码
您可以使用生成器来极大地简化 Promise 的使用。让我们看一下基于 Promise 的函数 fetchJson()
以及如何通过生成器改进它。
使用co 库和生成器,这段异步代码看起来就像同步代码
ECMAScript 2017 将具有异步函数,这些函数在内部基于生成器。使用它们,代码如下所示
所有版本都可以像这样调用
30.19.5 用例:接收异步数据
生成器可以通过 yield
从 next()
接收输入。这意味着您可以在异步到达新数据时唤醒生成器,并且对于生成器来说,它感觉就像同步接收数据一样。
30.20 新的正则表达式特性
以下正则表达式特性是 ECMAScript 6 中新增的
- 新的标志
/y
(粘性)将正则表达式的每个匹配项锚定到先前匹配项的末尾。
- 新的标志
/u
(unicode)将代理对(例如 \uD83D\uDE80
)作为代码点处理,并允许您在正则表达式中使用 Unicode 代码点转义符(例如 \u{1F680}
)。
- 新的数据属性
flags
允许您访问正则表达式的标志,就像 source
在 ES5 中已经允许您访问模式一样
- 您可以使用构造函数
RegExp()
来复制正则表达式
30.21 用于异步编程的 Promise
Promise 是回调的替代方案,用于传递异步计算的结果。它们需要异步函数的实现者付出更多努力,但为这些函数的用户提供了几个好处。
以下函数通过 Promise 异步返回结果
您可以按如下方式调用 asyncFunc()
30.21.1 链接 then()
调用
then()
始终返回一个 Promise,这使您能够链接方法调用
then()
返回的 Promise P 如何解决取决于其回调的操作
- 如果它返回一个 Promise(如 A 行所示),则该 Promise 的解决将转发给 P。这就是为什么 B 行的回调可以获取
asyncFunction2
的 Promise 的解决结果。
- 如果它返回不同的值,则该值用于解决 P。
- 如果抛出异常,则 P 将因该异常而被拒绝。
此外,请注意 catch()
如何处理两个异步函数调用(asyncFunction1()
和 asyncFunction2()
)的错误。也就是说,未捕获的错误会一直传递,直到遇到错误处理程序。
30.21.2 并行执行异步函数
如果您通过 then()
链接异步函数调用,它们将按顺序执行,一次执行一个
如果您不这样做并立即调用所有函数,它们基本上是并行执行的(在 Unix 进程术语中称为“fork”)
Promise.all()
使您能够在所有结果都返回时收到通知(在 Unix 进程术语中称为“join”)。它的输入是一个 Promise 数组,输出是一个 Promise,该 Promise 将使用结果数组来实现。
30.21.3 词汇表:Promise
Promise API 用于异步传递结果。*Promise 对象*(简称:Promise)是结果的占位符,结果通过该对象传递。
状态
- Promise 始终处于三种互斥状态之一
- 在结果准备好之前,Promise 处于*待定*状态。
- 如果结果可用,则 Promise 处于*已实现*状态。
- 如果发生错误,则 Promise 处于*已拒绝*状态。
- 如果“事情已完成”(已实现或已拒绝),则 Promise 处于*已解决*状态。
- Promise 只会被解决一次,然后保持不变。
对状态更改做出反应
-
*Promise 反应*是您使用 Promise 方法
then()
注册的回调,用于在实现或拒绝时收到通知。
- *可 thenable 对象*是具有 Promise 风格的
then()
方法的对象。每当 API 只想在解决时收到通知时,它只需要可 thenable 对象(例如,从 then()
和 catch()
返回的值;或传递给 Promise.all()
和 Promise.race()
的值)。
更改状态:有两个操作可以更改 Promise 的状态。在您调用其中任何一个操作一次后,进一步的调用将无效。
-
*拒绝*Promise 意味着 Promise 变为已拒绝。
-
*解决*Promise 会产生不同的效果,具体取决于您使用什么值来解决
- 使用普通(不可 thenable)值解决会实现 Promise。
- 使用可 thenable 对象 T 解决 Promise P 意味着 P 不能再被解决,现在将遵循 T 的状态,包括其实现或拒绝值。一旦 T 解决(或者如果 T 已经解决,则立即调用),将调用相应的 P 反应。
30.22 使用代理进行元编程
代理使您能够拦截和自定义对对象执行的操作(例如获取属性)。它们是一种*元编程*特性。
在以下示例中,proxy
是我们要拦截其操作的对象,而 handler
是处理拦截的对象。在这种情况下,我们只拦截一个操作,即 get
(获取属性)。
当我们获取属性 proxy.foo
时,处理程序会拦截该操作
有关可以拦截的操作列表,请参阅完整 API 的参考。