成员名称JavaScript 支持许多众所周知的成员名称,它们对创建遵循特定于环境模式的对象很有用。一个例子就是 iterator,可使用它在支持迭代行为的对象上命名函数。如果想创建一个伪装成标准 ECMAScript 迭代器的斐波纳契数列生成对象,需要创建一个包含 iterator 函数的对象。但是,由于任何人都能使用该名称,所以 ECMAScript 6 坚持要求您使用 iteratorSymbol 代替:
清单 3. 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| let fibonacci = {
[Symbol.iterator]: function*() {
let pre = 0, cur = 1;
for (;;) {
let temp = pre;
pre = cur;
cur += temp;
yield cur;
}
}
}
for (let n of fibonacci) {
// truncate the sequence at 1000
if (n > 1000)
break;
console.log(n);
}
|
同样地,Symbol 的主要功能是帮助程序员避免库之间的名称冲突。这最初有点难掌握,但可以尝试将 Symbol 视为基于它提供的字符串名称的唯一哈希值。
集合类型如果您使用 ECMAScript 超过 10 分钟,您就会知道该语言支持数组——数组自 1.0 版开始就是该规范的核心部分。尽管数组存在限制(最主要的限制是大小固定),但它们能很好地为我们服务;可能在未来数年也是如此。
但我们是时候承认一些事实了,即使我们从不会向任何人提起:数组……不是万能的。
为了帮助收拾残局,ECMAScript 6 向标准 JavaScript 环境添加了两个集合类型:Map 和 Set。
Map 是一组名称/值对,与 ECMAScript 对象非常相似。不同之处在于,Map 包含的方法使它比原始 ECMAScript 对象更容易使用:
- get() 和 set() 分别查找和设置键/值对
- clear() 将完全清空集合
- keys() 返回 Map 中的键的一个可迭代集合
- values() 对值执行同样的操作
另外,像 Array 一样,Map 包含受函数语言启发的方法,比如 forEach() 在 Map 自身上运行。
清单 4. 1
2
3
4
5
6
7
8
9
| let m = new Map();
m.set("key1", "value1");
m.set("key2", "value2");
m.forEach((key, value, map) => {
console.log(key,"=",value," from ", map);
})
console.log(m.keys());
console.log(m.values());
|
Set 看起来更像传统的对象集合,因为对象可简单地添加到集合中。但 Set 会依次检查每个对象,以确保它们未与集合中已存在的值重复:
清单 5. 1
2
3
4
5
6
| let s = new Set();
s.add("Ted");
s.add("Jenni");
s.add("Athen");
s.add("Ted"); // duplicate!
console.log(s.size); // 3
|
像 Map 一样,Set 之上也拥有方法,使它可以执行函数式交互,比如 forEach。从根本上讲,Set 像一个数组,但没有尖角括号。它动态增长,而且缺少任何形式的排序机制。如果使用 Set,您不能像数组一样按索引来查找对象。
弱引用ECMAScript 6 还引入了 WeakMaps 和 WeakSets,它们分别是通过弱引用(而不是通过传统的强引用)持有自己的值的 Map 和 Set。ECMAScript 规范非常清楚地描述了 WeakMap 内持有的对象上发生的事;对它的解释同样适用于 WeakSet:
如果一个被用作 WeakMap 键/值对的对象仅能跟随从 WeakMap 内开始的引用链访问,那么这个键/值对就无法访问,会自动从 WeakMap 删除。 本文不再赘述 WeakMaps 和 WeakSets 的效用。它们主要用于库代码(尤其是与缓存相关的代码),在应用程序代码中可能不会过多地出现。
Promise异步操作是 Node.js 的核心部分(通常在事件驱动编程的标签行下),但实现它们向来不轻松。最初,Node.js 社区似乎决定使用事件订阅,但一段时间后,开发人员都迁移到一种更倾向于回调驱动的风格。这给我们带来了不堪回首的回调地狱,Node.js 代码似乎“布满了”屏幕:
清单 6. 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
| let bookOrder = {'isbn': '978-0375504525', 'qty': 1};
function processBookOrders() {
setTimeout(function() {
// pretend this is an async call to an orders database
console.log('Getting the next order');
let nextBookOrder = bookOrder;
setTimeout(function() {
// pretend this is an async call to a books database
console.log(`Looking for ISBN ${nextBookOrder.isbn}`);
if (nextBookOrder.isbn == '978-0375504525') {
console.log('The book title is \'As I Lay Dying\'');
// pretend this is an async call to check stock levels
setTimeout(function() {
if (nextBookOrder.qty == 1)
console.log('The book is in stock!');
else
console.log('We do not have enough copies of the book in stock.');
}, 1000); // end of call to check stock levels
} else
console.log('This is not a book we carry.');
}, 1000); // end of call to the books database
}, 1000); // end of call to the orders database
};
processBookOrders();
|
这段代码处理一个书店的订单,该书店仅有一本书。处理该订单所需的所有逻辑与多个嵌套函数调用一致。如果这是在实际工作中,每个回调之间可能有许多行代码,使代码非常难理解、调试和维护。
另请注意,对于上面的示例,我使用了两个空格的缩进。想象坚持使用 4 或 8 空格缩进的开发人员对代码的滚动浏览。
经历了大量痛苦和愤怒之后,ECMAScript 社区在异步计算中发布了一种回调的替代方案:现在的标准 Promise 类型。
在 JavaScript 中使用 Promise 是事半功倍的。首先,构建代码来“实现某种用途”(如上面的清单所示)的人现在返回 Promise,而不是使用传统的同步执行或 Node.js 回调语法。这使调用方能使用 Promise 的 then() 方法将顺序调用链接起来,使用 catch() 定义发生失败时应执行的操作:
doSomething.then(function(result) {
console.log(result); // It did something
}).catch(function(err) {
console.log(err); // Error
});
|
在 doSomething() 端,现在可编写代码来生成一个 Promise 实例,实现方法通常是将一个执行异步操作的函数放在 Promise 内:
let promise = new Promise(function(resolve, reject) {
let result = // do a thing, usually async in nature
if (success) {
resolve(result);
}
else {
reject(Error("It didn't work"));
}
}); |