Symbol 是 JS 原始数据类型列表中(numbers
、strings
、booleans
、null
、undefined
)的最新补充。Symbol
是一个唯一标识符,常用于唯一标识对象中的属性。
碗这个对象中有几个水果属性(水果也是对象),当有两个相同的水果在碗里时,会出现混乱,我们不知道什么时候拿哪一个,这就是问题。我们需要一种方式来唯一的标识这这些香蕉。
Symbol
是一种独特的且不可变
的数据类型,经常用来标识对象属性
。
const sym1 = Symbol('apple');
console.log(sym1); // Symbol(apple)
它将创建唯一的标识符,并将其存储在 sym1
中。描述 “apple
” 只是用来描述标识符的一种方式,但是不能用来访问标识符本身
。
const sym2 = Symbol('banana');
const sym3 = Symbol('banana');
console.log(sym2 === sym3); // false
描述只是用来描述符号
,它并不是标识符本身的一部分
。无论描述是什么,每次都创建新的标识符
。
示例说明,下面是代表上图中的 bowl(碗)的代码
const bowl = {
'apple': { color: 'red', weight: 136.078 },
'banana': { color: 'yellow', weight: 183.15 },
'orange': { color: 'orange', weight: 170.097 }
};
碗中包含水果,它们是 bowl 的属性对象。但是,当我们添加第二个香蕉时,遇到了问题。
const bowl = {
'apple': { color: 'red', weight: 136.078 },
'banana': { color: 'yellow', weight: 183.151 },
'orange': { color: 'orange', weight: 170.097 },
'banana': { color: 'yellow', weight: 176.845 }
};
console.log(bowl);
// Object {apple: Object, banana: Object, orange: Object}
新添加的香蕉将上一个香蕉覆盖
了。为了解决该问题,我们可以使用标识符。
const bowl = {
[Symbol('apple')]: { color: 'red', weight: 136.078 },
[Symbol('banana')]: { color: 'yellow', weight: 183.15 },
[Symbol('orange')]: { color: 'orange', weight: 170.097 },
[Symbol('banana')]: { color: 'yellow', weight: 176.845 }
};
console.log(bowl);
// Object {Symbol(apple): Object, Symbol(banana): Object, Symbol(orange): Object, Symbol(banana): Object}
通过更改 bowl 的属性并使用标识符,每个属性都是唯一的标识符,第一个香蕉不会被第二个香蕉覆盖。
ES6 中的两个新协议:
这两个协议不是内置的,但是它们可以帮助你理解 ES6 中的新迭代概念,就像给你展示标识符的使用案例一样。
可迭代协议用来定义和自定义对象的迭代行为
。也就是说在 ES6 中,你可以灵活地指定循环访问对象中的值的方式
。对于某些对象,它们已经内置了这一行为。例如,字符串
和数组
就是内置可迭代类型
的例子。
const digits = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
for (const digit of digits) {
console.log(digit);
}
任何可迭代的对象都可以使用新的 for...of
循环。
为了使对象可迭代
,它必须实现可迭代接口
。接口其实就是为了让对象可迭代,它必须包含默认的迭代器方法
。该方法将定义对象如何被迭代。
迭代器协议用来定义对象生成一系列值的标准方式。实际上就是现在有了定义对象如何迭代的流程
。通过执行 .next()
方法来完成这一流程
。
当对象执行 .next()
方法时,就变成了迭代器
。.next()
方法是无参数函数,返回具有两个属性的对象
:
value
:表示对象内值序列的下个值的数据done
:表示迭代器是否已循环访问完值序列的布尔值。如果 done
为 true
,则迭代器已到达值序列的末尾处;如果 done
为 false
,则迭代器能够生成值序列中的另一个值。const digits = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
const arrayIterator = digits[Symbol.iterator]();
console.log(arrayIterator.next());
console.log(arrayIterator.next());
console.log(arrayIterator.next());
/**
Object {value: 0, done: false}
Object {value: 1, done: false}
Object {value: 2, done: false}
*/
Set 就是唯一项的集合。例如,{2, 4, 5, 6}
是 Set,因为每个数字都是唯一的,只出现一次。但是,{1, 1, 2, 4}
不是 Set,因为它包含重复的项目(1 出现了两次!)。
在 ES6 中,有一个新的内置对象的行为和数学意义上的集合相同
,使用起来类似于数组
。这个新对象就叫做“Set”。
Set 与数组之间的最大区别是:
不基于索引
,不能根据集合中的条目在集合中的位置引用这些条目不能单独被访问
基本上,Set 是让你可以存储唯一条目的对象
。你可以向 Set 中添加条目
,删除条目
,并循环访问
Set。这些条目可以是原始值
或对象
。
const games1 = new Set();
console.log(games1); // Set {},其中没有条目
// 根据值列表创建 Set,则使用数组
const games2 = new Set(['Super Mario Bros.', 'Banjo-Kazooie', 'Mario Kart', 'Super Mario Bros.']);
console.log(games2);
// Set {'Super Mario Bros.', 'Banjo-Kazooie', 'Mario Kart'}
// 会自动移除重复的条目 "Super Mario Bros.",很整洁!
const games = new Set(['Super Mario Bros.', 'Banjo-Kazooie', 'Mario Kart', 'Super Mario Bros.']);
games.add('Banjo-Tooie');
games.add('Age of Empires');
games.delete('Super Mario Bros.');
console.log(games);
// Set {'Banjo-Kazooie', 'Mario Kart', 'Banjo-Tooie', 'Age of Empires'}
games.clear()
console.log(games); // Set {}
.add()
添加不管成功与否,都会返回该 Set 对象
。另一方面,.delete()
则会返回一个布尔值
,该值取决于是否成功删除
(即如果该元素存在,返回 true
,否则返回 false
)。
.size
属性可以返回 Set 中的条目数
:
const months = new Set(['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']);
console.log(months.size); // 12
.has()
方法可以检查 Set 中是否存在某个条目
。如果 Set 中有该条目,则 .has()
将返回 true
。如果 Set 中不存在该条目,则 .has()
将返回 false
。
console.log(months.has('September')); // true
.values()
方法可以返回 Set 中的值
。.values()
方法的返回值是 SetIterator
对象。
console.log(months.values());
/**
SetIterator {'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'}
*/
ES6 中的新可迭代协议和迭代器协议中,Set 是内置可迭代类型
。这意味着循环时的两件事:
for...of
循环来循环访问 Set 中的每一项。使用 SetIterator
因为 .values()
方法返回新的迭代器对象(称为 SetIterator
),你可以将该迭代器对象存储在变量中,并使用 .next()
访问 Set 中的每一项。
const iterator = months.values();
iterator.next();
// Object {value: 'January', done: false}
iterator.next();
// Object {value: 'February', done: false}
// ...,一直运行到 done 等于 true 时,标志着 Set 的结束。
使用for...of
循环
const colors = new Set(['red', 'orange', 'yellow', 'green', 'blue', 'violet', 'brown', 'black']);
for (const color of colors) {
console.log(color);
}
/**
red
orange
yellow
green
blue
violet
brown
black
*/
WeakSet 和普通 Set 很像,但是具有以下关键区别:
只能包含对象
无法迭代
,意味着不能循环访问其中的对象.clear()
方法const student1 = { name: 'James', age: 26, gender: 'male' };
const student2 = { name: 'Julia', age: 27, gender: 'female' };
const student3 = { name: 'Richard', age: 31, gender: 'male' };
const roster = new WeakSet([student1, student2, student3]);
console.log(roster);
/**
WeakSet {Object {name: 'Julia', age: 27, gender: 'female'}, Object {name: 'Richard', age: 31, gender: 'male'}, Object {name: 'James', age: 26, gender: 'male'}}
*/
// 添加对象以外的内容,系统将报错!
roster.add('Amanda');
// Uncaught TypeError: Invalid value used in weak set(…)
在 JavaScript 中,创建新的值时会分配内存,并且当这些值不再需要时,将自动释放内存。这种内存不再需要后释放内存的过程称为垃圾回收。
WeakSet 通过专门使用对象作为键值来利用这一点。如果将对象设为 null
,则本质上是删除该对象。当 JavaScript 的垃圾回收器运行时,该对象之前占用的内存将被释放,以便稍后在程序中使用。
student3 = null;
console.log(roster);
/**
WeakSet {Object {name: 'Julia', age: 27, gender: 'female'}, Object {name: 'James', age: 26, gender: 'male'}}
*/
这种机制的好处在于你不用去担心要删掉对 WeakSet 中已删除对象的引用
,JavaScript 会帮你删除!如果对象被删除,当垃圾回收器运行时,该对象也会从 WeakSet 中删除。这样的话,如果你想要一种高效、轻便的解决方法去创建一组对象,就可以使用 WeakSet。垃圾回收的发生时间点取决于很多因素
Map 和 WeakMap 在很多方面与 Set 和 WeakSet 相同,它们都有类似的属性和方法。Map 和 Set 都是可迭代的
,这意味着我们可以循环遍历它们
。而WeakMap 和 WeakSet 不会阻止对象被当作垃圾回收
。但是 Map 是唯一的,因为它们是键值对的集合
,而 Set 是唯一值的集合
。可以说 Set 类似于数组
,而Map 类似于对象
。
本质上,Map 是一个可以存储键值对的对象,键和值
都可以是对象、原始值或二者的结合。
const employees = new Map();
console.log(employees); // Map {}
.set()
方法添加键值
const employees = new Map();
employees.set('[email protected]', {
firstName: 'James',
lastName: 'Parkes',
role: 'Content Developer'
});
employees.set('[email protected]', {
firstName: 'Julia',
lastName: 'Van Cleve',
role: 'Content Developer'
});
employees.set('[email protected]', {
firstName: 'Richard',
lastName: 'Kalehoff',
role: 'Content Developer'
});
console.log(employees);
/**
Map {'[email protected]' => Object {...}, '[email protected]' => Object {...}, '[email protected]' => Object {...}}
*/
.delete()
方法移除键值对
employees.delete('[email protected]');
employees.delete('[email protected]');
console.log(employees);
/**
Map {'[email protected]' => Object {firstName: 'James', lastName: 'Parkes', role: 'Course Developer'}}
*/
.clear()
方法从 Map 中删除所有键值对
employees.clear()
console.log(employees); // Map {}
如果成功地删除了键值对,.delete()
方法会返回 true
,失败则返回 false
。.set()
如果成功执行,则返回 Map 对象本身
。如果你使用 .set()
向 Map 中添加键已存在的键值对
,不会收到错误,但是该键值对将覆盖
Map 中的现有键值对。
.has()
方法并向其传入一个键来检查 Map 中是否存在该键值对
。
const members = new Map();
members.set('Evelyn', 75.68);
members.set('Liam', 20.16);
members.set('Sophia', 0);
members.set('Marcus', 10.25);
console.log(members.has('Xavier')); // false
console.log(members.has('Marcus')); // true
.get()
方法传入一个键,检索 Map 中的值。
console.log(members.get('Evelyn')); // 75.68
三种方式循环访问:
for...of
循环来循环访问每个键值对.forEach()
方法循环访问每个键值对使用 MapIterator
在 Map 上使用 .keys()
和 .values()
方法将返回新的迭代器对象,叫做 MapIterator
。你可以将该迭代器对象存储在新的变量中,并使用 .next()
循环访问每个键或值。
// 访问 Map 的键
let iteratorObjForKeys = members.keys();
iteratorObjForKeys.next();// Object {value: 'Evelyn', done: false}
iteratorObjForKeys.next(); // Object {value: 'Liam', done: false}
// 等等
// 访问 Map 的值
let iteratorObjForValues = members.values();
iteratorObjForValues.next(); // Object {value: 75.68, done: false}
// 等等
使用 for…of 循环
for (const member of members) {
console.log(member);
}
// 键值对会拆分为一个数组,第一个元素是键,第二个元素是值。
/**
['Evelyn', 75.68]
['Liam', 20.16]
['Sophia', 0]
['Marcus', 10.25]
*/
使用 forEach 循环
members.forEach((value, key) => console.log(value, key));
/**
'Evelyn' 75.68
'Liam' 20.16
'Sophia' 0
'Marcus' 10.25
*/
WeakMap 和普通 Map 很像,但是具有以下关键区别:
对象作为键
无法迭代
,意味着无法循环访问.clear()
方法const book1 = { title: 'Pride and Prejudice', author: 'Jane Austen' };
const book2 = { title: 'The Catcher in the Rye', author: 'J.D. Salinger' };
const book3 = { title: 'Gulliver's Travels', author: 'Jonathan Swift' };
const library = new WeakMap();
library.set(book1, true);
library.set(book2, false);
library.set(book3, true);
console.log(library);
/**
WeakMap {Object {title: 'Pride and Prejudice', author: 'Jane Austen'} => true, Object {title: 'The Catcher in the Rye', author: 'J.D. Salinger'} => false, Object {title: 'Gulliver's Travels', author: 'Jonathan Swift'} => true}
*/
// 添加对象以外的内容作为键,系统将报错!
library.set('The Grapes of Wrath', false);
// Uncaught TypeError: Invalid value used as weak map key(…)
book1 = null;
console.log(library);
/**
WeakMap {Object {title: 'The Catcher in the Rye', author: 'J.D. Salinger'} => false, Object {title: 'Gulliver’s Travels', author: 'Jonathan Swift'} => true}
*/
使用 JavaScript Promise 是处理异步请求
的一种新方法,是对我们过去代码构建方式的一种改良。处理一个请求需要来回等待,造成大量的停机时间,能够在停机时间同时进行其它工作,并且通知我们请求处理完成,就是 Promise 在 JavaScript 中的作用。
JavaScript Promise 是用新的 Promise 构造函数
- new Promise()
创建而成的。promise 使你能够展开一些可以异步完成的工作,并回到常规工作。创建 promise 时,必须向其提供异步运行的代码
。将该代码当做参数提供给构造函数:
new Promise(function () {
window.setTimeout(function createSundae(flavor = 'chocolate') {
const sundae = {};
// 请求冰淇淋
// 得到锥形蛋筒
// 加热冰淇淋
// 舀一大勺到蛋筒里!
}, Math.random() * 2000);
});
JavaScript 如何通知我们它已经完成操作,准备好让我们恢复工作?
它通过向初始函数中传入两个函数来实现这一点,通常我们将这两个函数称为 resolve
和 reject
。
resolve
函数-请求完成new Promise(function (resolve, reject) {
window.setTimeout(function createSundae(flavor = 'chocolate') {
const sundae = {};
// 请求冰淇淋
// 得到锥形蛋筒
// 加热冰淇淋
// 舀一大勺到蛋筒里!
resolve(sundae);
}, Math.random() * 2000);
});
当 sundae 被成功创建后,它会调用 resolve
方法并向其传递我们要返回的数据,在本例中,返回的数据是完成的 sundae。因此 resolve
方法用来表示请求已完成,并且成功完成了请求。
reject
函数-请求失败如果请求存在问题,无法完成请求,那么我们可以使用传递给该函数的第二个函数。通常,该函数存储在一个叫做"reject
"的标识符中,表示如果请求因为某种原因失败了,应该使用该函数
。
new Promise(function (resolve, reject) {
window.setTimeout(function createSundae(flavor = 'chocolate') {
const sundae = {};
// 请求冰淇淋
// 得到锥形蛋筒
// 加热冰淇淋
// 舀一大勺到蛋筒里!
if ( /* iceCreamConeIsEmpty(flavor) */ ) {
reject(`Sorry, we're out of that flavor :-(`);
}
resolve(sundae);
}, Math.random() * 2000);
});
如果请求无法完成,则使用 reject
方法。注意,即使请求失败了,我们依然可以返回数据。
Promise构造函数需要一个可以运行的函数,运行一段时间后,将成功完成(使用 resolve
方法)或失败(使用 reject
方法)。当结果最终确定时(请求成功完成或失败),现在 promise 已经实现了,并且将通知我们,这样我们便能决定将如何对结果做处理。
首先要注意的是,Promise 将立即返回一个对象。
const myPromiseObj = new Promise(function (resolve, reject) {
// 圣代创建代码
});
该对象上具有一个 .then()
方法,我们可以让该方法通知我们 promise 中的请求成功与否。.then()
方法会接收两个函数:
mySundae.then(function(sundae) {
console.log(`Time to eat my delicious ${sundae}`);
}, function(msg) {
console.log(msg);
self.goCry(); // 不是一个真正的方法
});
传递给 .then()
的第一个函数将被调用,并传入 Promise 的 resolve
函数需要使用的数据。这里,该函数将接收 sundae
对象。第二个函数传入的数据会在 Promise 的 reject
函数被调用时使用。
更多 Promise
JavaScript 代理会让一个对象代表另一个对象,来处理另一个对象的所有交互。代理可以直接处理请求,接收或发送目标对象数据,以及处理一大堆其他的事情。
使用 Proxy 构造函数 new Proxy();
。Proxy 构造函数接收两个项目:
第二个对象叫做处理器。
创建 Proxy 的最简单方式是提供对象和空的 handler(处理器)对象
。
var richard = {status: 'looking for work'};
var agent = new Proxy(richard, {});
agent.status; // 返回 'looking for work'
上述代码并没有对 Proxy 执行任何特殊操作,只是将请求直接传递给源对象
!如果我们希望 Proxy 对象截获请求,这就是 handler 对象的作用了!
让 Proxy 变得有用的关键是当做第二个对象传递给 Proxy 构造函数的 handler 对象
。handler 对象由将用于访问属性的方法
构成。
get
用来截获对属性的调用:
const richard = {status: 'looking for work'};
const handler = {
get(target, propName) {
console.log(target); // `richard` 对象,不是 `handler` 也不是 `agent`
console.log(propName); // 代理(本例中为`agent`)正在检查的属性名称
}
};
const agent = new Proxy(richard, handler);
agent.status; // 注销 richard 对象(不是代理对象!)和正在访问的属性的名称(`status`)
在上述代码中,handler
对象具有一个 get
方法(因为被用在 Proxy 中,所以将"function"(方法)称之为"trap"(捕获器))。当代码 agent.status;
在最后一行运行时,因为存在 get
捕获器,它将截获该调用以获得 status
(状态)属性并运行 get
捕获器方法。这样将会输出 Proxy 的目标对象(richard 对象),然后输出被请求的属性(status
属性)的名称。它的作用就是这些!它不会实际地输出属性
!这很重要 —— 如果使用了捕获器,你需要确保为该捕获器提供所有的功能
。
从 Proxy 内部访问目标对象
如果我们想真正地提供真实的结果,我们需要返回目标对象的属性:
const richard = {status: 'looking for work'};
const handler = {
get(target, propName) {
console.log(target);
console.log(propName);
return target[propName];
}
};
const agent = new Proxy(richard, handler);
agent.status; // (1)打印 richard 对象,(2)打印被访问的属性,(3)返回 richard.status 中的文本
在 get
trap 中添加了最后一行 return target[propName];
,这样将会访问目标对象的属性
并返回它。
直接获取 Proxy 的返回信息
使用 Proxy 提供直接的反馈:
const richard = {status: 'looking for work'};
const handler = {
get(target, propName) {
return `He's following many leads, so you should offer a contract as soon as possible!`;
}
};
const agent = new Proxy(richard, handler);
agent.status; // 返回文本 `He's following many leads, so you should offer a contract as soon as possible!`
对于上述代码,Proxy 甚至不会检查目标对象,直接对调用代码做出响应
。
因此每当 Proxy 上的属性被访问,get
trap 将接管任务。如果我们想截获调用以更改属性
,则需要使用 set
trap!
set
trap 用来截获将更改属性的代码。set trap 将接收: 它代理的对象 被设置的属性 Proxy 的新值
const richard = {status: 'looking for work'};
const handler = {
set(target, propName, value) {
if (propName === 'payRate') { // 如果工资正在确定,则需要15%作为佣金。
value = value * 0.85;
}
target[propName] = value;
}
};
const agent = new Proxy(richard, handler);
agent.payRate = 1000; // 将演员的工资设置为 1,000美元
agent.payRate; // 850美元是演员的实际工资
在上述代码中,注意 set
trap 会检查是否设置了 payRate
属性。如果设置了,Proxy 就从中拿走 15% 的费用作为自己的佣金!当演员的薪酬是一千美元时,因为 payRate
属性已设置,代码从中扣除 15% 的费用,并将实际 payRate
属性设为 850;
实际上总共有 13 种不同的 Trap,它们都可以用在处理程序中!
对于 ES5 的 getter 和 setter 方法,你需要提前知道要获取/设置的属性
:
var obj = {
_age: 5,
_height: 4,
get age() {
console.log(`getting the "age" property`);
console.log(this._age);
},
get height() {
console.log(`getting the "height" property`);
console.log(this._height);
}
};
对于上述代码,注意在初始化对象时,我们需要设置 get age()
和 get height()
。因此,当我们调用下面的代码时,将获得以下结果:
obj.age; // 打印 'getting the "age" property' 和 5
obj.height; // 打印 'getting the "height" property' 和 4
但是当我们向该对象添加新的属性时,并没有显示 age
和 height
属性那样生成的 getting the "weight" property
消息。
obj.weight = 120; // 在对象上设置一个新的属性
obj.weight; // 只打印120
对于 ES6 中的 Proxy,我们不需要提前知道这些属性:
const proxyObj = new Proxy({age: 5, height: 4}, {
get(targetObj, property) {
console.log(`getting the ${property} property`);
console.log(targetObj[property]);
}
});
proxyObj.age; // 打印 'getting the age property' 和 5
proxyObj.height; // 打印 'getting the height property' 和 4
当我们添加新的属性时
proxyObj.weight = 120; // 在对象上设置一个新的属性
proxyObj.weight; // 打印 'getting the weight property' 和 120
因此 proxy 对象的某些功能可能看起来类似于现有的 ES5 getter/setter 方法,但是对于 proxy,在初始化对象时,不需要针对每个属性使用 getter/setter 初始化对象
。
Proxy 是一种强大的创建和管理对象之间的交互的新方式。
每当函数被调用时,JavaScript 引擎就会在函数顶部启动,并运行每行代码,直到到达底部。无法中途停止运行代码,并稍后重新开始。一直都是这种“运行到结束”的工作方式:
function getEmployee() {
console.log('the function has started');
const names = ['Amanda', 'Diego', 'Farrin', 'James', 'Kagure', 'Kavita', 'Orit', 'Richard'];
for (const name of names) {
console.log(name);
}
console.log('the function has ended');
}
getEmployee();
运行上述代码将在控制台中输出以下内容:
the function has started
Amanda
Diego
Farrin
James
Kagure
Kavita
Orit
Richard
the function has ended
如果你想先输出前三名员工的姓名,然后停止一段时间,稍后再从停下的地方继续输出更多员工的姓名呢?普通函数无法这么做,因为无法中途“暂停”运行函数。
如果我们希望能够中途暂停运行函数,则需要使用 ES6 中新提供的一种函数,叫做 generator
(生成器)函数!
function* getEmployee() {
console.log('the function has started');
const names = ['Amanda', 'Diego', 'Farrin', 'James', 'Kagure', 'Kavita', 'Orit', 'Richard'];
for (const name of names) {
console.log( name );
}
console.log('the function has ended');
}
星号表示该函数实际上是生成器!尝试运行该函数
getEmployee();
// 这是我在 Chrome 中获得的回应:
getEmployee {[[GeneratorStatus]]: "suspended", [[GeneratorReceiver]]: Window}
注意,生成器的星号实际上可以放在 function
关键字和函数名称之间的任何位置
。
生成器被调用时,它不会运行函数中的任何代码,而是创建和返回迭代器。该迭代器可以用来运行实际生成器的内部代码。
const generatorIterator = getEmployee();
generatorIterator.next();
关键字 yield
是 ES6 中新出现的关键字。只能用在生成器函数中。yield
会导致生成器暂停下来。我们向我们的生成器中添加 yield
:
function* getEmployee() {
console.log('the function has started');
const names = ['Amanda', 'Diego', 'Farrin', 'James', 'Kagure', 'Kavita', 'Orit', 'Richard'];
for (const name of names) {
console.log(name);
yield;
}
console.log('the function has ended');
}
注意,现在 for...of
循环中出现了 yield
。如果我们调用该生成器(生成迭代器),然后调用 .next()
,将获得以下输出:
const generatorIterator = getEmployee();
generatorIterator.next();
/**
the function has started
Amanda
*/
generatorIterator.next(); // Diego
generatorIterator.next(); // Farrin
// ...
它能完全记住上次停下的地方!它获取到数组中的下一项(Diego),记录它,然后再次触发了 yield
,再次暂停。
function* getEmployee() {
console.log('the function has started');
const names = ['Amanda', 'Diego', 'Farrin', 'James', 'Kagure', 'Kavita', 'Orit', 'Richard'];
for (const name of names) {
yield name;
}
console.log('the function has ended');
}
注意,现在从 console.log(name);
切换成了 yield name;
。做出这一更改后,当生成器运行时,它会把姓名从函数里返回出去,然后暂停执行代码。我们看看具体效果:
const generatorIterator = getEmployee();
let result = generatorIterator.next();
result.value // 是 "Amanda"
generatorIterator.next().value // 是 "Diego"
generatorIterator.next().value // 是 "Farrin"
示例
迭代器的 .next()
方法需要被调用多少次,才能完全完成/用尽下面的 udacity 生成器函数:
function* udacity() {
yield 'Richard';
yield 'James'
}
它被调用的次数将比生成器函数中的 yield
表达式的数量多一次。
.next()
的第一次调用将启动该函数,并运行为第一个 yield
。.next()
的第二次调用将从暂停的地方继续,并运行第二个 yield
。.next()
的第三次(即最后一次)调用将再次从暂停的地方继续,并运行到函数结尾处。我们可以使用关键字 yield
从生成器中获取数据。我们还可以将数据发送回生成器中。方式是使用 .next()
方法:
function* displayResponse() {
const response = yield;
console.log(`Your response is "${response}"!`);
}
const iterator = displayResponse();
iterator.next(); // 开始运行生成器函数
iterator.next('Hello Udacity Student'); // 将数据发送到生成器中
// 上面的一行打印到控制台:你的响应是 "Hello Udacity Student"!
使用数据调用 .next()
(即 .next('Richard')
)会将该数据发送到生成器函数中上次离开的地方。它会将 yield
关键字替换为你提供的数据。
关键字 yield
用来暂停生成器并向生成器外发送数据,然后 .next()
方法用来向生成器中传入数据。下面是使用这两种过程来一次次地循环访问姓名列表的示例:
function* getEmployee() {
const names = ['Amanda', 'Diego', 'Farrin', 'James', 'Kagure', 'Kavita', 'Orit', 'Richard'];
const facts = [];
for (const name of names) {
// yield *出* 每个名称并将返回的数据存储到 facts 数组中
facts.push(yield name);
}
return facts;
}
const generatorIterator = getEmployee();
// 从生成器中获取第一个名称
let name = generatorIterator.next().value;
// 将数据传入 *并* 获取下一个名称
name = generatorIterator.next(`${name} is cool!`).value;
// 将数据传入 *并* 获取下一个名称
name = generatorIterator.next(`${name} is awesome!`).value;
// 将数据传入 *并* 获取下一个名称
name = generatorIterator.next(`${name} is stupendous!`).value;
// 你懂的
name = generatorIterator.next(`${name} is rad!`).value;
name = generatorIterator.next(`${name} is impressive!`).value;
name = generatorIterator.next(`${name} is stunning!`).value;
name = generatorIterator.next(`${name} is awe-inspiring!`).value;
// 传递最后一个数据,生成器结束并返回数组
const positions = generatorIterator.next(`${name} is magnificent!`).value;
// 在自己的行上显示每个名称及其描述
positions.join('\n');
示例
function* createSundae() {
const toppings = [];
toppings.push(yield);
toppings.push(yield);
toppings.push(yield);
return toppings;
}
var it = createSundae();
it.next('hot fudge');
it.next('sprinkles');
it.next('whipped cream');
it.next();
注意,第一次调用 .next()
将初始化生成器,将在第一个 yield
位置暂停。第二次调用 .next()
将向该 yield
提供数据。
数数有多少个 yield
,以及每次调用 .next()
时,数据是如何被传入的。
toppings
数组的最后一项将是 undefined
。因为:
.next()
传入了一些数据,但是该数据没有存储在任何位置。.next()
应该会获得一些数据(空数据),因为生成到对 toppings.push()
的最后一次调用中。生成器是强大的新型函数,能够暂停执行代码,同时保持自己的状态。生成器适用于一次一个地循环访问列表项,以便单独处理每项,然后再转到下一项。还可以使用迭代器来处理嵌套回调
。例如,假设某个函数需要获得所有仓库的列表和被加星标的次数。在获得每个仓库的星标数量之前,需要获得用户的信息。获得用户的个人资料后,代码可以利用该信息查找所有的仓库。