下面是对ES6中迭代器和生成器的整理,非常详细、容易理解,希望可以帮助到有需要的小伙伴~
文章目录
迭代器是什么
ECMAScript 5规范表示集合的数据结构有数组(Array)和对象(Object )
ECMAScript 6 规范又新增了Set和 Map 两种集合。
这四种集合的操作都是不一样的。
数组 - for
对象 - for…in
Set - for…Each
Map - for…each
迭代器可以为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署Iterator接口,就可以完成遍历操作。
迭代器的作用:
- 为各种数据结构,提供一个统一的、简便的访问接口。
- 使得数据结构的成员能够按某种次序排列。
- ECMAScript 6新增了for…of循环语句,用于遍历迭代器。
Iterator接口
在JavaScript 中迭代器(lterator )是一个对象(JS没有给,需要自己创建),该对象提供 next()方法用于返回序列中的下一项。
该方法返回包含done和value 两个属性的对象。
value - 返回的是当前的值
done - 表示是否迭代完毕
实例:数组的迭代器
/*
构建迭代器 -> 返回迭代器对象
* 该迭代器对象必须具备next()方法
* 作用 - 用于返回序列中的下一项
* 返回值 - 是一个对象
* done属性 - 表示是否迭代完毕
* false -表示当前没有迭代完毕
* true -表示当前迭代完毕
*value属性–表示当前迭代的值
*/
function fn(array) {
var index = 0;
return {
next : function () {
return index < array.length ? {
done : false,
value : array[index++] // 先赋值,再加
} : {
done : true
}
}
}
}
let arr = ['one','two','three','four','five'];
let iterator = fn(arr);
console.log(iterator.next()); // { done: false, value: 'one' }
console.log(iterator.next()); // { done: false, value: 'two' }
console.log(iterator.next()); // { done: false, value: 'three' }
console.log(iterator.next()); // { done: false, value: 'four' }
console.log(iterator.next()); // { done: false, value: 'five' }
/*
for (var i=0; i<arr.length; i++) {
console.log(arr[i]);
}*/
一种数据结构只要部署了lterator 接口,就可以称这种数据结构是“可遍历的”。
ECMAScript 6规范规定默认的 lterator 接口部署在数据结构的 Symbol.iterator属性。(即:一个数据结构 只要有Symbol.iterator属性就可以被迭代)
Symboliterator属性本身是一个函数,就是当前数据结构默认的迭代器生成函数。执行这个函数,就会返回一个迭代器。
JavaScript 中原生具有lterator接口的数据结构如下:
- Array
- Map
- Set
- String
- TypedArray (ES6 新增)
- 函数的argurnents 对象
- NodeList 对象
- ……
迭代协议
ECMAScript 6新增了几种协议,这些协议可以被任何遵循某些约定的对象来实现。
-
可迭代协议
可迭代协议允许JavaScript 对象去定义或定制它们的迭代行为。为了变成可迭代对象,对象必须实现@@iterator方法。其实就是这个对象(或者它原型链上的某个对象)必须有一个名字
是 Symbol.iterator的属性。属性名称 描述 [Symbol.iterator] 返回一个对象的无参函数,被返回对象符合迭代器协议 当一个对象需要被迭代的时候,它的@@iterator方法被调用并且无参数,然后返回一个用于在迭代中获得值的迭代器。
-
迭代器协议
该迭代器协议定义了一种标准的方式来产生一个有限或无限序列的值。
当一个对象被认为是一个迭代器时,它实现了一个next()的方法并且拥有以下含义:迭代器协议 描述 next 返回一个对象的无参函数,被返回对象拥有两个属性: done (boolean)
* done(boolean)
* 如果迭代器已经经过了被迭代序列时为 true。这时 value 可能描述了该迭代器的返回值。
* 如果迭代器可以产生序列中的下一个值,则为false。这等效于连同done 属性也不指定。value:迭代器返回的任何 JavaScript 值。done 为 true 时可省略。
* value:迭代器返回的任何JavaScript值。done为true时可省略。
for…of语句的用法
ECMAScript 6引人了for …of语句用于遍历迭代器。一个数据结构只要部署了Symboliteratar属性,就被视为具有lterator接口,就可以用for…of循环遍历它的成员。
for (variable of iterable){
//statements
}
- variable:在每次迭代中,将不同属性的值分配给变量。
- iterable:被迭代枚举其属性的对象。
缺点:
只能从头遍历到尾,不能自定义。
// 遍历数组
let arr = [1,2,3,4,5];
for (let attr of arr) {
console.log(attr);
/*
1
2
3
4
5
*/
}
// 遍历Set集合
let set = new Set(arr);
for (let attr of set) {
console.log(attr);
/*
1
2
3
4
5
*/
}
// 遍历Map集合
let map = new Map();
let num = 100,str = 'dream',fun = function () {}, obj={}
map.set('num',num);
map.set('str',str);
map.set('fun',fun);
map.set('obj',obj);
for (let attr of map) {
// attr得到map集合的key/value对
console.log(attr);
/* [ 'num', 100 ]
[ 'str', 'dream' ]
[ 'fun', [Function: fun] ]
[ 'obj', {} ]
*/
}
// 遍历字符串
let string = "dream";
for (let attr of string) {
console.log(attr);
/*
d
r
e
a
m
*/
}
返回迭代器对象的方法
ECMAScript 6中的数组(Array )、Set和 Map都具有以下方法,该方法返回一个迭代器对象。
- entries()方法:返回一个新的迭代器对象,这个对象的元素是用来遍历[键名,键值]组成的数组。
- keys()方法:返回一个新的迭代器对象,用来遍历所有的键名。
- values()方法:返回一个新的迭代器对象,用来遍历所有的键值。
与forEach()方法的区别
ECMAScript 6 中的数组(Array )、Set 和 Map都具有forEach()方法用于遍历,
for…each()方法与for…of语句的区别如下:
- forEach()方法无法跳出循环。即,break语句和continue 语句无效。
- for …of语句可以使用break语句和、continue 语句和return语句。
与for…in语句的区别
for …in语句主要用于遍历对象。与for …of语句对比,for …in语句具有以下几个缺点:
- for…in不仅遍历自身,还会遍历手动添加的,甚至包括原型链的。
- 如果用于遍历数组的话,遍历得到的键名为字符串类型的数字值。
Object.prototype.objCustom = function () {};
Array.prototype.arrCustom = function () {};
let iterable = [3,5,7];
iterable.foo = "hello";
/*for (let i in iterable) {
console.log(i);
}*/
/*
// 数组的索引值
0
1
2
// iterable对象的属性
foo
// 原型上的方法
arrCustom
objCustom
*/
for (let i of iterable) {
console.log(i);
}
// 只能遍历数组中的值
/*
3
5
7
*/
生成器
Generator是什么
自定义的迭代器需要显式地维护其内部状态。
而生成器提提供了允许定义一个包含自有迭代算法的函数,同时可以自动维护其内部状态。
生成器就是ES6自带的迭代器
Generator可以作为生成一个迭代器的特殊函数,该函数被调用时返回一个Generator对象,该对象是符合可迭代协议和迭代器协议的。
在JS中,任何一个函数只要开始执行,就不会停下来直到执行完毕。
Generator函数与普通函数的区别在于:
- function*这种声明方式会定义一个生成器函数,它返回一个 Generator对象。
- yield关键字用来暂停和恢复一个生成器函数。
/*
定义生成器函数
function* functionName() {
yield 关键字
}
* 生成器函数在调用时,返回一个Generator对象
*/
function* fn() {}
let result = fn();
console.log(result); // // Object [Generator] {}
function*表达式
function*
这种声明方式( function关键字后跟一个星号)会定义一个生成器函数(Generatorfunction ),它返回一个Generator对象。
生成器函数在执行时能暂停,后面又能从暂停处继续执行。
function* name([param[, param[, ... param]]]) { statements }
- name:表示函数名称。
- param:表示传递给函数的一个参数的名称,一个函数最多可以有255个参数。
- staternents:表示 JavaScript语句。
yield表达式
yield关键字用来暂停和恢复一个生成器函数。
[rv] = yield [expression];
- expression: 定义通过迭代器协议从生成器函数返回的值。如果省略,则返回undefined.
- rv: 返回传递给生成器的next()方法的可选值,以恢复其执行。
// 定义一个生成器对象
function* fn() {
let arr = ['one','two','three','four'];
for (let i=0; i<arr.length; i++) {
yield arr[i]; // 暂停一个生成器函数
}
}
// 生成器调用返回生成器(即迭代器)对象
let generator = fn();
// 生成器对象就是ES6提供的迭代器(迭代器一定有next()函数,next()函数返回值是done和value)
console.log(generator.next()); // { value: 'one', done: false }
console.log(generator.next()); // { value: 'two', done: false }
console.log(generator.next()); // { value: 'three', done: false }
console.log(generator.next()); // { value: 'four', done: false }
在函数fn()中,碰到yield关键字,运行的程序就会停下来。只有调用next()方法,才会继续执行函数fn()中的代码。
所以函数fn()本身就有暂停状态。
上述代码运行的具体情况:
-
fn()不会立即执行,而是直接暂停,并返回Iterator对象(迭代器对象)
-
每次generator.next()都会打破暂停状态去执行,直到遇到下一个yield或return再次暂停
-
遇到yield时,会执行yield后面的表达式,并返回执行之后的值,然后再次进入暂停状态,此时:done:false
-
每次执行next()之后,返回的值永远都是{ value : … , done : … }的形式.
yield*表达式
yield *
表达式用于委托给另一个Generator 或可迭代对象。
yield *
表达式的作用是代理yield表达式,将需要函数本身产生(yield)的值委托出去。
yield *
后面跟一个生成器函数、或是其他可迭代的对象(如一个数组、字符串、arguments对象)。
yield *
表达式的返回值,就是其后面可迭代对象迭代完毕时的返回值
yield* [[expression]];
- expression:返回一个可迭代对象的表达式。
实例1:
// 定义一个生成器对象
function* fn() {
let arr = ['one','two','three','four'];
/!*for (let i=0; i<arr.length; i++) {
yield arr[i]; // 暂停和恢复一个生成器函数
}*!/
yield* arr; // 代理上面for循环中的yield表达式,将需要函数本身产生(yield)的值委托出去
}
// 生成器调用返回生成器对象
let generator = fn();
// 生成器对象就是ES6提供的迭代器(迭代器一定有next()函数,next()函数返回值是done和value)
console.log(generator.next()); // { value: 'one', done: false }
console.log(generator.next()); // { value: 'two', done: false }
console.log(generator.next()); // { value: 'three', done: false }
console.log(generator.next()); // { value: 'four', done: false }
console.log(generator.next()); // { value: undefined, done: true }
实例2:
function* g1() {
yield 2;
yield 3;
}
function* g2() {
yield 1;
yield* g1(); // 把g1()的执行委托给g2()
yield 4;
}
var iterator = g2();
console.log(iterator.next()); // { value: 1, done: false }
console.log(iterator.next()); // { value: 2, done: false }
console.log(iterator.next()); // { value: 3, done: false }
console.log(iterator.next()); // { value: 4, done: false }
console.log(iterator.next()); // { value: undefined, done: true }
Generator对象的方法
生成的Generator 对象具有以下几个原型方法:
方法名称 | 描述 |
---|---|
next()方法 | 返回一个包含属性done和value的对象。该方法也可以通过接受一个参数用以向生成器传值 |
return()方法 | 返回给定的值并结束生成器 |
throw()方法 | 用于向生成器抛出异常,井恢复生成器的执行,返回带有done 及 value两个属性的对象 |
next()、throw()、return()这三个方法本质上是同一件事,可以放在一起理解。
它们的作用都是让Generator函数恢复执行,并且使用不同的语句替换yield表达式。
end~