本章收集了本书所有章节的概述部分。
Math
特性Number
属性Math
方法let
const
...
)Object
中的新方法for-of
循环then()
ES6 规范的介绍列出了所有新特性
ECMAScript 6 的一些主要增强功能包括模块、类声明、词法块作用域、迭代器和生成器、用于异步编程的 Promise、解构模式以及适当的尾调用。ECMAScript 内置库已经扩展,以支持更多的数据抽象,包括映射、集合和二进制数值数组,以及对字符串和正则表达式中 Unicode 补充字符的额外支持。现在可以通过子类化扩展内置库。
主要有三类特性
Math
特性 现在可以使用二进制和八进制表示法指定整数
> 0xFF // ES5: hexadecimal
255
> 0b11 // ES6: binary
3
> 0o10 // ES6: octal
8
Number
属性 全局对象 Number
获得了一些新属性
Number.EPSILON
用于比较浮点数,并考虑舍入误差。Number.isInteger(num)
检查 num
是否为整数(没有小数部分的数字)
> Number.isInteger(1.05)
false
> Number.isInteger(1)
true
> Number.isInteger(-3.1)
false
> Number.isInteger(-3)
true
Number.isSafeInteger(number)
Number.MIN_SAFE_INTEGER
Number.MAX_SAFE_INTEGER
Number.isNaN(num)
检查 num
是否为 NaN
值。与全局函数 isNaN()
相比,它不会将其参数强制转换为数字,因此对于非数字更安全
> isNaN('???')
true
> Number.isNaN('???')
false
Number
的另外三个方法与同名全局函数基本等效:Number.isFinite
、Number.parseFloat
、Number.parseInt
。Math
方法 全局对象 Math
具有用于数值、三角函数和按位运算的新方法。让我们看四个例子。
Math.sign()
返回数字的符号
> Math.sign(-8)
-1
> Math.sign(0)
0
> Math.sign(3)
1
Math.trunc()
删除数字的小数部分
> Math.trunc(3.1)
3
> Math.trunc(3.9)
3
> Math.trunc(-3.1)
-3
> Math.trunc(-3.9)
-3
Math.log10()
计算以 10 为底的对数
> Math.log10(100)
2
Math.hypot()
计算其参数的平方和的平方根(勾股定理)
> Math.hypot(3, 4)
5
新的字符串方法
> 'hello'.startsWith('hell')
true
> 'hello'.endsWith('ello')
true
> 'hello'.includes('ell')
true
> 'doo '.repeat(3)
'doo doo doo '
ES6 有一种新的字符串字面量,称为“模板字面量”
// String interpolation via template literals (in backticks)
const
first
=
'Jane'
;
const
last
=
'Doe'
;
console
.
log
(
`Hello
${
first
}
${
last
}
!`
);
// Hello Jane Doe!
// Template literals also let you create strings with multiple lines
const
multiLine
=
`
This is
a string
with multiple
lines`
;
符号是 ECMAScript 6 中的一种新的原始类型。它们通过工厂函数创建
const
mySymbol
=
Symbol
(
'mySymbol'
);
每次调用工厂函数时,都会创建一个新的唯一符号。可选参数是一个描述性字符串,在打印符号时显示(它没有其他用途)
> mySymbol
Symbol(mySymbol)
符号主要用作唯一的属性键——符号永远不会与任何其他属性键(符号或字符串)冲突。例如,可以通过使用存储在 Symbol.iterator
中的符号作为方法的键,使对象“可迭代”(可通过 for-of
循环和其他语言机制使用)(有关可迭代对象的更多信息,请参见关于迭代的章节)
const
iterableObject
=
{
[
Symbol
.
iterator
]()
{
// (A)
···
}
}
for
(
const
x
of
iterableObject
)
{
console
.
log
(
x
);
}
// Output:
// hello
// world
在 A 行中,一个符号被用作方法的键。这个唯一的标记使对象可迭代,并使我们能够使用 for-of
循环。
在 ECMAScript 5 中,您可能使用字符串来表示颜色等概念。在 ES6 中,您可以使用符号,并确保它们始终是唯一的
const
COLOR_RED
=
Symbol
(
'Red'
);
const
COLOR_ORANGE
=
Symbol
(
'Orange'
);
const
COLOR_YELLOW
=
Symbol
(
'Yellow'
);
const
COLOR_GREEN
=
Symbol
(
'Green'
);
const
COLOR_BLUE
=
Symbol
(
'Blue'
);
const
COLOR_VIOLET
=
Symbol
(
'Violet'
);
function
getComplement
(
color
)
{
switch
(
color
)
{
case
COLOR_RED
:
return
COLOR_GREEN
;
case
COLOR_ORANGE
:
return
COLOR_BLUE
;
case
COLOR_YELLOW
:
return
COLOR_VIOLET
;
case
COLOR_GREEN
:
return
COLOR_RED
;
case
COLOR_BLUE
:
return
COLOR_ORANGE
;
case
COLOR_VIOLET
:
return
COLOR_YELLOW
;
default
:
throw
new
Exception
(
'Unknown color: '
+
color
);
}
}
每次调用 Symbol('Red')
时,都会创建一个新的符号。因此,COLOR_RED
永远不会被误认为是另一个值。如果它是字符串 'Red'
,情况就会有所不同。
将符号强制(隐式转换)为字符串会引发异常
const
sym
=
Symbol
(
'desc'
);
const
str1
=
''
+
sym
;
// TypeError
const
str2
=
`
${
sym
}
`
;
// TypeError
唯一的解决办法是显式转换
const
str2
=
String
(
sym
);
// 'Symbol(desc)'
const
str3
=
sym
.
toString
();
// 'Symbol(desc)'
禁止强制转换可以防止一些错误,但也会使使用符号变得更加复杂。
以下操作识别符号作为属性键
Reflect.ownKeys()
[]
进行属性访问Object.assign()
以下操作忽略符号作为属性键
Object.keys()
Object.getOwnPropertyNames()
for-in
循环ES6 有两种新的字面量:模板字面量和标签模板字面量。这两种字面量的名称和外观相似,但它们却截然不同。因此,区分它们非常重要
模板字面量是可以跨越多行并包含插值表达式(通过 ${···}
插入)的字符串字面量
const
firstName
=
'Jane'
;
console
.
log
(
`Hello
${
firstName
}
!
How are you
today?`
);
// Output:
// Hello Jane!
// How are you
// today?
标签模板字面量(简称:标签模板)是通过在模板字面量之前提及一个函数来创建的
> String.raw`A \tagged\ template`
'A \\tagged\\ template'
标签模板是函数调用。在上一个例子中,调用了 String.raw
方法来生成标签模板的结果。
ES6 提供了两种声明变量的新方法:let
和 const
,它们在很大程度上取代了 ES5 中声明变量的方法 var
。
let
let
的工作方式与 var
类似,但它声明的变量是“块级作用域”的,只存在于当前块中。var
是“函数作用域”的。
在下面的代码中,您可以看到 let
声明的变量 tmp
只存在于从 A 行开始的块中
function
order
(
x
,
y
)
{
if
(
x
>
y
)
{
// (A)
let
tmp
=
x
;
x
=
y
;
y
=
tmp
;
}
console
.
log
(
tmp
===
x
);
// ReferenceError: tmp is not defined
return
[
x
,
y
];
}
const
const
的工作方式与 let
类似,但您声明的变量必须立即初始化,并且其值以后不能更改。
const
foo
;
// SyntaxError: missing = in const declaration
const
bar
=
123
;
bar
=
456
;
// TypeError: `bar` is read-only
由于 for-of
循环每次迭代都会创建一个“绑定”(变量的存储空间),因此可以使用 const
声明循环变量
for
(
const
x
of
[
'a'
,
'b'
])
{
console
.
log
(
x
);
}
// Output:
// a
// b
下表概述了在 ES6 中声明变量的六种方式(灵感来自 kangax 的表格)
提升 | 作用域 | 创建全局属性 | |
---|---|---|---|
var |
声明 | 函数 | 是 |
let |
暂时性死区 | 块 | 否 |
const |
暂时性死区 | 块 | 否 |
函数 |
完整 | 块 | 是 |
class |
否 | 块 | 否 |
import |
完整 | 模块全局 | 否 |
“解构”是一种从存储在(可能是嵌套的)对象和数组中的数据中提取多个值的便捷方法。它可以在接收数据的位置使用(例如赋值语句的左侧)。如何提取值是通过模式指定的(请继续阅读以获取示例)。
解构对象
const
obj
=
{
first
:
'Jane'
,
last
:
'Doe'
};
const
{
first
:
f
,
last
:
l
}
=
obj
;
// f = 'Jane'; l = 'Doe'
// {prop} is short for {prop: prop}
const
{
first
,
last
}
=
obj
;
// first = 'Jane'; last = 'Doe'
解构有助于处理返回值
const
obj
=
{
foo
:
123
};
const
{
writable
,
configurable
}
=
Object
.
getOwnPropertyDescriptor
(
obj
,
'foo'
);
console
.
log
(
writable
,
configurable
);
// true true
数组解构(适用于所有可迭代值)
const
iterable
=
[
'a'
,
'b'
];
const
[
x
,
y
]
=
iterable
;
// x = 'a'; y = 'b'
解构有助于处理返回值
const
[
all
,
year
,
month
,
day
]
=
/^(\d\d\d\d)-(\d\d)-(\d\d)$/
.
exec
(
'2999-12-31'
);
解构可以在以下位置使用(我展示了数组模式来演示;对象模式也可以使用)
// Variable declarations:
const
[
x
]
=
[
'a'
];
let
[
x
]
=
[
'a'
];
var
[
x
]
=
[
'a'
];
// Assignments:
[
x
]
=
[
'a'
];
// Parameter definitions:
function
f
([
x
])
{
···
}
f
([
'a'
]);
您还可以在 for-of
循环中进行解构
const
arr
=
[
'a'
,
'b'
];
for
(
const
[
index
,
element
]
of
arr
.
entries
())
{
console
.
log
(
index
,
element
);
}
// Output:
// 0 a
// 1 b
ECMAScript 6 中的参数处理已得到显著升级。它现在支持参数默认值、剩余参数 (varargs) 和解构。
此外,扩展运算符有助于函数/方法/构造函数调用和数组字面量。
通过等号 (=
) 为参数指定*默认参数值*。如果调用者未提供参数值,则使用默认值。在以下示例中,y
的默认参数值为 0
function
func
(
x
,
y
=
0
)
{
return
[
x
,
y
];
}
func
(
1
,
2
);
// [1, 2]
func
(
1
);
// [1, 0]
func
();
// [undefined, 0]
如果使用剩余运算符 (...
) 作为参数名称的前缀,则该参数将通过数组接收所有剩余参数
function
format
(
pattern
,
...
params
)
{
return
{
pattern
,
params
};
}
format
(
1
,
2
,
3
);
// { pattern: 1, params: [ 2, 3 ] }
format
();
// { pattern: undefined, params: [] }
如果在参数列表中使用对象模式进行解构,则可以模拟命名参数
function
selectEntries
({
start
=
0
,
end
=-
1
,
step
=
1
}
=
{})
{
// (A)
// The object pattern is an abbreviation of:
// { start: start=0, end: end=-1, step: step=1 }
// Use the variables `start`, `end` and `step` here
···
}
selectEntries
({
start
:
10
,
end
:
30
,
step
:
2
});
selectEntries
({
step
:
3
});
selectEntries
({});
selectEntries
();
A 行中的 = {}
使您可以在没有参数的情况下调用 selectEntries()
。
...
) 在函数和构造函数调用中,扩展运算符将可迭代值转换为参数
>
Math
.
max
(
-
1
,
5
,
11
,
3
)
11
>
Math
.
max
(...[
-
1
,
5
,
11
,
3
])
11
>
Math
.
max
(
-
1
,
...[
-
5
,
11
],
3
)
11
在数组字面量中,扩展运算符将可迭代值转换为数组元素
>
[
1
,
...[
2
,
3
],
4
]
[
1
,
2
,
3
,
4
]
在 ES5 中,单个结构(传统函数)扮演着三个角色
在 ES6 中,有更多专业化。现在,这三个职责的处理方式如下。就函数定义和类定义而言,定义是声明或表达式。
特别是对于回调,箭头函数很方便,因为它们不会遮蔽周围作用域的 this
。
对于较长的回调和独立函数,传统函数是可以的。一些 API 使用 this
作为隐式参数。在这种情况下,您别无选择,只能使用传统函数。
请注意,我区分了
尽管它们的行为有所不同(稍后解释),但所有这些实体都是函数。例如
> typeof (() => {}) // arrow function
'function'
> typeof function* () {} // generator function
'function'
> typeof class {} // class
'function'
箭头函数有两个优点。
首先,它们比传统的函数表达式更简洁
const
arr
=
[
1
,
2
,
3
];
const
squares
=
arr
.
map
(
x
=>
x
*
x
);
// Traditional function expression:
const
squares
=
arr
.
map
(
function
(
x
)
{
return
x
*
x
});
其次,它们的 this
是从周围环境中获取的(*词法*)。因此,您不再需要 bind()
或 that = this
。
function
UiComponent
()
{
const
button
=
document
.
getElementById
(
'myButton'
);
button
.
addEventListener
(
'click'
,
()
=>
{
console
.
log
(
'CLICK'
);
this
.
handleClick
();
// lexical `this`
});
}
以下变量在箭头函数内部都是词法的
arguments
super
this
new.target
方法定义
const
obj
=
{
myMethod
(
x
,
y
)
{
···
}
};
属性值简写
const
first
=
'Jane'
;
const
last
=
'Doe'
;
const
obj
=
{
first
,
last
};
// Same as:
const
obj
=
{
first
:
first
,
last
:
last
};
计算属性键
const
propKey
=
'foo'
;
const
obj
=
{
[
propKey
]
:
true
,
[
'b'
+
'ar'
]
:
123
};
这种新语法也可用于方法定义
const
obj
=
{
[
'h'
+
'ello'
]()
{
return
'hi'
;
}
};
console
.
log
(
obj
.
hello
());
// hi
计算属性键的主要用例是使使用符号作为属性键变得容易。
Object
中的新方法 Object
最重要的新方法是 assign()
。传统上,此功能在 JavaScript 世界中称为 extend()
。与这种经典操作的工作方式相比,Object.assign()
只考虑*自身*(非继承)属性。
const
obj
=
{
foo
:
123
};
Object
.
assign
(
obj
,
{
bar
:
true
});
console
.
log
(
JSON
.
stringify
(
obj
));
// {"foo":123,"bar":true}
一个类和一个子类
class
Point
{
constructor
(
x
,
y
)
{
this
.
x
=
x
;
this
.
y
=
y
;
}
toString
()
{
return
`(
${
this
.
x
}
,
${
this
.
y
}
)`
;
}
}
class
ColorPoint
extends
Point
{
constructor
(
x
,
y
,
color
)
{
super
(
x
,
y
);
this
.
color
=
color
;
}
toString
()
{
return
super
.
toString
()
+
' in '
+
this
.
color
;
}
}
使用类
> const cp = new ColorPoint(25, 8, 'green');
> cp.toString();
'(25, 8) in green'
> cp instanceof ColorPoint
true
> cp instanceof Point
true
在底层,ES6 类并不是什么全新的东西:它们主要提供更方便的语法来创建老式的构造函数。如果您使用 typeof
,您可以看到这一点
> typeof Point
'function'
JavaScript 已经存在模块很长时间了。但是,它们是通过库实现的,而不是内置于语言中。ES6 是 JavaScript 首次拥有内置模块。
ES6 模块存储在文件中。每个文件只有一个模块,每个模块只有一个文件。您可以通过两种方式从模块中导出内容。这两种方式可以混合使用,但通常最好单独使用它们。
可以有多个*命名导出*
//------ lib.js ------
export
const
sqrt
=
Math
.
sqrt
;
export
function
square
(
x
)
{
return
x
*
x
;
}
export
function
diag
(
x
,
y
)
{
return
sqrt
(
square
(
x
)
+
square
(
y
));
}
//------ main.js ------
import
{
square
,
diag
}
from
'lib'
;
console
.
log
(
square
(
11
));
// 121
console
.
log
(
diag
(
4
,
3
));
// 5
您也可以导入完整的模块
//------ main.js ------
import
*
as
lib
from
'lib'
;
console
.
log
(
lib
.
square
(
11
));
// 121
console
.
log
(
lib
.
diag
(
4
,
3
));
// 5
可以有一个*默认导出*。例如,一个函数
//------ myFunc.js ------
export
default
function
()
{
···
}
// no semicolon!
//------ main1.js ------
import
myFunc
from
'myFunc'
;
myFunc
();
或者一个类
//------ MyClass.js ------
export
default
class
{
···
}
// no semicolon!
//------ main2.js ------
import
MyClass
from
'MyClass'
;
const
inst
=
new
MyClass
();
请注意,如果默认导出函数或类(它们是匿名声明),则末尾没有分号。
脚本 | 模块 | |
---|---|---|
HTML 元素 | <script> |
<script type="module"> |
默认模式 | 非严格 | 严格 |
顶级变量是 | 全局的 | 模块本地的 |
顶级 this 的值 |
window |
undefined |
执行 | 同步 | 异步 |
声明式导入(import 语句) |
否 | 是 |
程序化导入(基于 Promise 的 API) | 是 | 是 |
文件扩展名 | .js |
.js |
for-of
循环 for-of
是 ES6 中的一个新循环,它取代了 for-in
和 forEach()
,并支持新的迭代协议。
使用它来循环遍历*可迭代*对象(数组、字符串、映射、集合等;请参阅“可迭代对象和迭代器”一章)
const
iterable
=
[
'a'
,
'b'
];
for
(
const
x
of
iterable
)
{
console
.
log
(
x
);
}
// Output:
// a
// b
break
和 continue
在 for-of
循环内有效
for
(
const
x
of
[
'a'
,
''
,
'b'
])
{
if
(
x
.
length
===
0
)
break
;
console
.
log
(
x
);
}
// Output:
// a
在循环遍历数组时访问元素及其索引(of
前的方括号表示我们正在使用解构)
const
arr
=
[
'a'
,
'b'
];
for
(
const
[
index
,
element
]
of
arr
.
entries
())
{
console
.
log
(
`
${
index
}
.
${
element
}
`
);
}
// Output:
// 0. a
// 1. b
循环遍历映射中的 [键,值] 条目(of
前的方括号表示我们正在使用解构)
const
map
=
new
Map
([
[
false
,
'no'
],
[
true
,
'yes'
],
]);
for
(
const
[
key
,
value
]
of
map
)
{
console
.
log
(
`
${
key
}
=>
${
value
}
`
);
}
// Output:
// false => no
// true => yes
新的静态 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)
除其他外,ECMAScript 6 中新增了以下四种数据结构:Map
、WeakMap
、Set
和 WeakSet
。
映射的键可以是任意值
> const map = new Map(); // create an empty Map
> const KEY = {};
> map.set(KEY, 123);
> map.get(KEY)
123
> map.has(KEY)
true
> map.delete(KEY);
true
> map.has(KEY)
false
您可以使用带有 [键,值] 对的数组(或任何可迭代对象)来设置映射中的初始数据
const
map
=
new
Map
([
[
1
,
'one'
],
[
2
,
'two'
],
[
3
,
'three'
],
// trailing comma is ignored
]);
集合是唯一元素的集合
const arr = [5, 1, 5, 7, 7, 5];
const unique = [...new Set(arr)]; // [ 5, 1, 7 ]
如您所见,如果将包含这些元素的可迭代对象(示例中的 arr
)传递给构造函数,则可以使用元素初始化集合。
弱映射是一种映射,它不会阻止对其键进行垃圾回收。这意味着您可以将数据与对象关联起来,而不必担心内存泄漏。例如
//----- Manage listeners
const
_objToListeners
=
new
WeakMap
();
function
addListener
(
obj
,
listener
)
{
if
(
!
_objToListeners
.
has
(
obj
))
{
_objToListeners
.
set
(
obj
,
new
Set
());
}
_objToListeners
.
get
(
obj
).
add
(
listener
);
}
function
triggerListeners
(
obj
)
{
const
listeners
=
_objToListeners
.
get
(
obj
);
if
(
listeners
)
{
for
(
const
listener
of
listeners
)
{
listener
();
}
}
}
//----- Example: attach listeners to an object
const
obj
=
{};
addListener
(
obj
,
()
=>
console
.
log
(
'hello'
));
addListener
(
obj
,
()
=>
console
.
log
(
'world'
));
//----- Example: trigger listeners
triggerListeners
(
obj
);
// Output:
// hello
// world
类型化数组是 ECMAScript 6 API,用于处理二进制数据。
代码示例
const
typedArray
=
new
Uint8Array
([
0
,
1
,
2
]);
console
.
log
(
typedArray
.
length
);
// 3
typedArray
[
0
]
=
5
;
const
normalArray
=
[...
typedArray
];
// [5,1,2]
// The elements are stored in typedArray.buffer.
// Get a different view on the same data:
const
dataView
=
new
DataView
(
typedArray
.
buffer
);
console
.
log
(
dataView
.
getUint8
(
0
));
// 5
ArrayBuffer
的实例存储要处理的二进制数据。使用两种*视图*来访问数据
Uint8Array
、Int16Array
、Float32Array
等)将 ArrayBuffer 解释为单一类型元素的索引序列。DataView
的实例允许您在 ArrayBuffer 内的任何字节偏移量处将数据作为多种类型(Uint8
、Int16
、Float32
等)的元素进行访问。以下浏览器 API 支持类型化数组(详细信息在专门章节中提及)
ES6 引入了一种遍历数据的新机制:*迭代*。迭代有两个核心概念
Symbol.iterator
的方法来做到这一点。该方法是*迭代器*的工厂。用 TypeScript 表示法表示为接口,这些角色如下所示
interface
Iterable
{
[
Symbol
.
iterator
]()
:
Iterator
;
}
interface
Iterator
{
next
()
:
IteratorResult
;
}
interface
IteratorResult
{
value
:
any
;
done
:
boolean
;
}
以下值是可迭代的
普通对象不可迭代(原因在专门章节中解释)。
通过迭代访问数据的语言结构
const
[
a
,
b
]
=
new
Set
([
'a'
,
'b'
,
'c'
]);
for-of
循环
for
(
const
x
of
[
'a'
,
'b'
,
'c'
])
{
console
.
log
(
x
);
}
Array.from()
:
const
arr
=
Array
.
from
(
new
Set
([
'a'
,
'b'
,
'c'
]));
...
)
const
arr
=
[...
new
Set
([
'a'
,
'b'
,
'c'
])];
const
map
=
new
Map
([[
false
,
'no'
],
[
true
,
'yes'
]]);
const
set
=
new
Set
([
'a'
,
'b'
,
'c'
]);
Promise.all()
、Promise.race()
Promise
.
all
(
iterableOverPromises
).
then
(
···
);
Promise
.
race
(
iterableOverPromises
).
then
(
···
);
yield*
:
yield
*
anIterable
;
您可以将生成器视为可以暂停和恢复的进程(代码片段)
function
*
genFunc
()
{
// (A)
console
.
log
(
'First'
);
yield
;
console
.
log
(
'Second'
);
}
请注意新语法:function*
是*生成器函数*的新“关键字”(也有*生成器方法*)。yield
是生成器可以暂停自身的运算符。此外,生成器还可以通过 yield
接收输入和发送输出。
当您调用生成器函数 genFunc()
时,您将获得一个*生成器对象* genObj
,您可以使用它来控制进程
const
genObj
=
genFunc
();
该进程最初在 A 行暂停。genObj.next()
恢复执行,genFunc()
内部的 yield
暂停执行
genObj
.
next
();
// Output: First
genObj
.
next
();
// output: Second
生成器有四种
function
*
genFunc
()
{
···
}
const
genObj
=
genFunc
();
const
genFunc
=
function
*
()
{
···
};
const
genObj
=
genFunc
();
const
obj
=
{
*
generatorMethod
()
{
···
}
};
const
genObj
=
obj
.
generatorMethod
();
class
MyClass
{
*
generatorMethod
()
{
···
}
}
const
myInst
=
new
MyClass
();
const
genObj
=
myInst
.
generatorMethod
();
生成器返回的对象是可迭代的;每个 yield
都会对迭代值序列做出贡献。因此,您可以使用生成器来实现可迭代对象,这些对象可以被各种 ES6 语言机制使用:for-of
循环、展开运算符 (...
) 等。
以下函数返回一个对象的属性的可迭代对象,每个属性对应一个 [键,值] 对
function
*
objectEntries
(
obj
)
{
const
propKeys
=
Reflect
.
ownKeys
(
obj
);
for
(
const
propKey
of
propKeys
)
{
// `yield` returns a value and then pauses
// the generator. Later, execution continues
// where it was previously paused.
yield
[
propKey
,
obj
[
propKey
]];
}
}
objectEntries()
的使用方法如下
const
jane
=
{
first
:
'Jane'
,
last
:
'Doe'
};
for
(
const
[
key
,
value
]
of
objectEntries
(
jane
))
{
console
.
log
(
`
${
key
}
:
${
value
}
`
);
}
// Output:
// first: Jane
// last: Doe
objectEntries()
的确切工作原理在专门的章节中进行了解释。如果没有生成器,实现相同的功能需要做更多的工作。
您可以使用生成器来极大地简化 Promise 的使用。让我们看一下基于 Promise 的函数 fetchJson()
以及如何通过生成器改进它。
function
fetchJson
(
url
)
{
return
fetch
(
url
)
.
then
(
request
=>
request
.
text
())
.
then
(
text
=>
{
return
JSON
.
parse
(
text
);
})
.
catch
(
error
=>
{
console
.
log
(
`ERROR:
${
error
.
stack
}
`
);
});
}
使用co 库和生成器,这段异步代码看起来就像同步代码
const
fetchJson
=
co
.
wrap
(
function
*
(
url
)
{
try
{
let
request
=
yield
fetch
(
url
);
let
text
=
yield
request
.
text
();
return
JSON
.
parse
(
text
);
}
catch
(
error
)
{
console
.
log
(
`ERROR:
${
error
.
stack
}
`
);
}
});
ECMAScript 2017 将具有异步函数,这些函数在内部基于生成器。使用它们,代码如下所示
async
function
fetchJson
(
url
)
{
try
{
let
request
=
await
fetch
(
url
);
let
text
=
await
request
.
text
();
return
JSON
.
parse
(
text
);
}
catch
(
error
)
{
console
.
log
(
`ERROR:
${
error
.
stack
}
`
);
}
}
所有版本都可以像这样调用
fetchJson
(
'http://example.com/some_file.json'
)
.
then
(
obj
=>
console
.
log
(
obj
));
生成器可以通过 yield
从 next()
接收输入。这意味着您可以在异步到达新数据时唤醒生成器,并且对于生成器来说,它感觉就像同步接收数据一样。
以下正则表达式特性是 ECMAScript 6 中新增的
/y
(粘性)将正则表达式的每个匹配项锚定到先前匹配项的末尾。/u
(unicode)将代理对(例如 \uD83D\uDE80
)作为代码点处理,并允许您在正则表达式中使用 Unicode 代码点转义符(例如 \u{1F680}
)。flags
允许您访问正则表达式的标志,就像 source
在 ES5 中已经允许您访问模式一样
> /abc/ig.source // ES5
'abc'
> /abc/ig.flags // ES6
'gi'
RegExp()
来复制正则表达式
> new RegExp(/abc/ig).flags
'gi'
> new RegExp(/abc/ig, 'i').flags // change flags
'i'
Promise 是回调的替代方案,用于传递异步计算的结果。它们需要异步函数的实现者付出更多努力,但为这些函数的用户提供了几个好处。
以下函数通过 Promise 异步返回结果
function
asyncFunc
()
{
return
new
Promise
(
function
(
resolve
,
reject
)
{
···
resolve
(
result
);
···
reject
(
error
);
});
}
您可以按如下方式调用 asyncFunc()
asyncFunc
()
.
then
(
result
=>
{
···
})
.
catch
(
error
=>
{
···
});
then()
调用 then()
始终返回一个 Promise,这使您能够链接方法调用
asyncFunc1
()
.
then
(
result1
=>
{
// Use result1
return
asyncFunction2
();
// (A)
})
.
then
(
result2
=>
{
// (B)
// Use result2
})
.
catch
(
error
=>
{
// Handle errors of asyncFunc1() and asyncFunc2()
});
then()
返回的 Promise P 如何解决取决于其回调的操作
asyncFunction2
的 Promise 的解决结果。此外,请注意 catch()
如何处理两个异步函数调用(asyncFunction1()
和 asyncFunction2()
)的错误。也就是说,未捕获的错误会一直传递,直到遇到错误处理程序。
如果您通过 then()
链接异步函数调用,它们将按顺序执行,一次执行一个
asyncFunc1
()
.
then
(()
=>
asyncFunc2
());
如果您不这样做并立即调用所有函数,它们基本上是并行执行的(在 Unix 进程术语中称为“fork”)
asyncFunc1
();
asyncFunc2
();
Promise.all()
使您能够在所有结果都返回时收到通知(在 Unix 进程术语中称为“join”)。它的输入是一个 Promise 数组,输出是一个 Promise,该 Promise 将使用结果数组来实现。
Promise
.
all
([
asyncFunc1
(),
asyncFunc2
(),
])
.
then
(([
result1
,
result2
])
=>
{
···
})
.
catch
(
err
=>
{
// Receives first rejection among the Promises
···
});
Promise API 用于异步传递结果。*Promise 对象*(简称:Promise)是结果的占位符,结果通过该对象传递。
状态
对状态更改做出反应
then()
注册的回调,用于在实现或拒绝时收到通知。then()
方法的对象。每当 API 只想在解决时收到通知时,它只需要可 thenable 对象(例如,从 then()
和 catch()
返回的值;或传递给 Promise.all()
和 Promise.race()
的值)。更改状态:有两个操作可以更改 Promise 的状态。在您调用其中任何一个操作一次后,进一步的调用将无效。
代理使您能够拦截和自定义对对象执行的操作(例如获取属性)。它们是一种*元编程*特性。
在以下示例中,proxy
是我们要拦截其操作的对象,而 handler
是处理拦截的对象。在这种情况下,我们只拦截一个操作,即 get
(获取属性)。
const
target
=
{};
const
handler
=
{
get
(
target
,
propKey
,
receiver
)
{
console
.
log
(
'get '
+
propKey
);
return
123
;
}
};
const
proxy
=
new
Proxy
(
target
,
handler
);
当我们获取属性 proxy.foo
时,处理程序会拦截该操作
> proxy.foo
get foo
123
有关可以拦截的操作列表,请参阅完整 API 的参考。