【重温基础】13.迭代器和生成器
栏目: JavaScript · 发布时间: 7年前
内容简介:本文是今日感受:每次自我年终总结,都会有各种情绪和收获。系列目录:
本文是 重温基础 系列文章的第十三篇。
今日感受:每次自我年终总结,都会有各种情绪和收获。
系列目录:
- 【复习资料】ES6/ES7/ES8/ES9资料整理(个人整理)
- 【重温基础】1.语法和数据类型
- 【重温基础】2.流程控制和错误处理
- 【重温基础】3.循环和迭代
- 【重温基础】4.函数
- 【重温基础】5.表达式和运算符
- 【重温基础】6.数字
- 【重温基础】7.时间对象
- 【重温基础】8.字符串
- 【重温基础】9.正则表达式
- 【重温基础】10.数组
- 【重温基础】11.Map和Set对象
- 【重温基础】12.使用对象
本章节复习的是JS中的迭代器和生成器,常常用来处理集合。
前置知识:
JavaScrip已经提供多个迭代集合的方法,从简单的 for
循环到 map()
和 filter()
。
迭代器和生成器将迭代的概念直接带入核心语言,并提供一种机制来自定义 for...of
循环的行为。
本文会将知识点分为两大部分,简单介绍和详细介绍:
简单介绍,适合基础入门会使用的目标;
详细介绍,会更加深入的做介绍,适合理解原理;
1. 概述
当我们使用循环语句迭代数据时,需初始化一个变量来记录每一次迭代在数据集合中的位置:
let a = ["aaa","bbb","ccc"];
for (let i = 0; i< a.length; i++){
console.log(a[i]);
}
复制代码
这边的 i
就是我们用来记录迭代位置的变量,但是在 ES6
开始,JavaScrip引入了迭代器这个特性,并且 新的数组方法
和 新的集合类型
(如 Set集合
与 Map集合
)都依赖迭代器的实现,这个新特性对于 高效的数据处理
而言是不可或缺的,在语言的其他特性中也都有迭代器的身影:新的 for-of循环
、展开运算符( ...
),甚至连 异步编程
都可以使用迭代器。
本文主要会介绍ES6中新增的迭代器(Iterator)和生成器(Generator)。
2. 迭代器(简单介绍)
迭代器是一种特殊对象,它具有一些专门为迭代过程设计的专有接口,所有的迭代器对象都有一个 next()
方法,每次调用都会返回一个结果对象。
这个结果对象,有两个属性:
-
value: 表示下一个将要返回的值。 -
done: 一个布尔值,若没有更多可返回的数据时,值为true,否则false。
如果最后一个值返回后,再调用 next()
,则返回的对象的 done
值为 true
,而 value
值如果没有值的话,返回的为 undefined
。
ES5实现一个迭代器:
function myIterator(list){
var i = 0;
return {
next: function(){
var done = i >= list.length;
var value = !done ? list[i++] : undefined;
return {
done : done,
value : value
}
}
}
}
var iterator = myIterator([1,2,3]);
iterator.next(); // "{done: false, value: 1}"
iterator.next(); // "{done: false, value: 2}"
iterator.next(); // "{done: false, value: 3}"
iterator.next(); // "{done: true, value: undefined}"
// 以后的调用都一样
iterator.next(); // "{done: true, value: undefined}"
复制代码
从上面代码可以看出,ES5的实现还是比较麻烦,而ES6新增的生成器,可以使得创建迭代器对象的过程更加简单。
3. 生成器(简单介绍)
生成器是一种返回迭代器的函数,通过 function
关键字后的星号( *
)来表示,函数中会用到新的关键字 yield
。星号可以紧挨着 function
关键字,也可以在中间添加一个空格。
function *myIterator(){
yield 1;
yield 2;
yield 3;
}
let iterator = myIterator();
iterator.next(); // "{done: false, value: 1}"
iterator.next(); // "{done: false, value: 2}"
iterator.next(); // "{done: false, value: 3}"
iterator.next(); // "{done: true, value: undefined}"
// 以后的调用都一样
iterator.next(); // "{done: true, value: undefined}"
复制代码
生成器函数最有趣的部分是,每当执行完一条 yield
语句后函数就会自动停止执行,比如上面代码,当 yield 1;
执行完后,便不会执行任何语句,而是等到再调用迭代器的 next()
方法才会执行下一个语句,即 yield 2;
.
使用 yield
关键字可以返回任何值和表达式,因为可以通过生成器函数批量给迭代器添加元素:
function *myIterator(list){
for(let i = 0; i< list.length ; i ++){
yield list[i];
}
}
var iterator = myIterator([1,2,3]);
iterator.next(); // "{done: false, value: 1}"
iterator.next(); // "{done: false, value: 2}"
iterator.next(); // "{done: false, value: 3}"
iterator.next(); // "{done: true, value: undefined}"
// 以后的调用都一样
iterator.next(); // "{done: true, value: undefined}"
复制代码
生成器的适用返回很广,可以将它用于所有支持函数使用的地方。
4. 迭代器(详细介绍)
4.1 Iterator迭代器概念
Iterator是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成迭代操作(即依次处理该数据结构的所有成员)。
Iterator三个作用:
- 为各种数据结构,提供一个 统一 的、 简便 的访问接口;
- 使得数据结构的成员能够按某种次序排列;
-
Iterator
接口主要供ES6新增的
for...of消费;
4.2 Iterator迭代过程
next next next
每一次调用 next
方法,都会返回数据结构的当前成员的信息。具体来说,就是返回一个包含 value
和 done
两个属性的对象。
value done
模拟 next
方法返回值:
let f = function (arr){
var nextIndex = 0;
return {
next:function(){
return nextIndex < arr.length ?
{value: arr[nextIndex++], done: false}:
{value: undefined, done: true}
}
}
}
let a = f(['a', 'b']);
a.next(); // { value: "a", done: false }
a.next(); // { value: "b", done: false }
a.next(); // { value: undefined, done: true }
复制代码
4.3 默认Iterator接口
若数据 可迭代 ,即一种数据部署了Iterator接口。
ES6中默认的Iterator接口部署在数据结构的 Symbol.iterator
属性,即如果一个数据结构具有 Symbol.iterator
属性,就可以认为是 可迭代
。
Symbol.iterator
属性本身是函数,是当前数据结构默认的迭代器生成函数。执行这个函数,就会返回一个迭代器。至于属性名 Symbol.iterator
,它是一个表达式,返回 Symbol
对象的 iterator
属性,这是一个预定义好的、类型为 Symbol 的特殊值,所以要放在方括号内(参见《Symbol》一章)。
原生具有Iterator接口的数据结构有:
- Array
- Map
- Set
- String
- TypedArray
- 函数的 arguments 对象
- NodeList 对象
4.4 Iterator使用场景
-
(1)解构赋值
对数组和
Set结构进行解构赋值时,会默认调用Symbol.iterator方法。
let a = new Set().add('a').add('b').add('c');
let [x, y] = a; // x = 'a' y = 'b'
let [a1, ...a2] = a; // a1 = 'a' a2 = ['b','c']
复制代码
-
(2)扩展运算符
扩展运算符(
...)也会调用默认的 Iterator 接口。
let a = 'hello'; [...a]; // ['h','e','l','l','o'] let a = ['b', 'c']; ['a', ...a, 'd']; // ['a', 'b', 'c', 'd'] 复制代码
-
(2)yield*
yield*后面跟的是一个可迭代的结构,它会调用该结构的迭代器接口。
let a = function*(){
yield 1;
yield* [2,3,4];
yield 5;
}
let b = a();
b.next() // { value: 1, done: false }
b.next() // { value: 2, done: false }
b.next() // { value: 3, done: false }
b.next() // { value: 4, done: false }
b.next() // { value: 5, done: false }
b.next() // { value: undefined, done: true }
复制代码
-
(4)其他场合
由于数组的迭代会调用迭代器接口,所以任何接受数组作为参数的场合,其实都调用了迭代器接口。下面是一些例子。
-
for...of
-
Array.from()
-
Map(), Set(), WeakMap(), WeakSet()(比如
new Map([['a',1],['b',2]])) -
Promise.all()
-
Promise.race()
4.5 for...of循环
只要数据结构部署了 Symbol.iterator
属性,即具有 iterator 接口,可以用 for...of
循环迭代它的成员。也就是说, for...of
循环内部调用的是数据结构的 Symbol.iterato
方法。
使用场景:
for...of
可以使用在 数组
,
Set
和 Map
结构
, 类数组对象
, Genetator对象
和 字符串
。
-
数组
for...of循环可以代替数组实例的forEach方法。
let a = ['a', 'b', 'c'];
for (let k of a){console.log(k)}; // a b c
a.forEach((ele, index)=>{
console.log(ele); // a b c
console.log(index); // 0 1 2
})
复制代码
与 for...in
对比, for...in
只能获取对象键名,不能直接获取键值,而 for...of
允许直接获取键值。
let a = ['a', 'b', 'c'];
for (let k of a){console.log(k)}; // a b c
for (let k in a){console.log(k)}; // 0 1 2
复制代码
-
Set和Map
可以使用数组作为变量,如
for (let [k,v] of b){...}。
let a = new Set(['a', 'b', 'c']);
for (let k of a){console.log(k)}; // a b c
let b = new Map();
b.set('name','leo');
b.set('age', 18);
b.set('aaa','bbb');
for (let [k,v] of b){console.log(k + ":" + v)};
// name:leo
// age:18
// aaa:bbb
复制代码
- 类数组对象
// 字符串
let a = 'hello';
for (let k of a ){console.log(k)}; // h e l l o
// DOM NodeList对象
let b = document.querySelectorAll('p');
for (let k of b ){
k.classList.add('test');
}
// arguments对象
function f(){
for (let k of arguments){
console.log(k);
}
}
f('a','b'); // a b
复制代码
-
对象
普通对象不能直接使用
for...of会报错,要部署Iterator才能使用。
let a = {a:'aa',b:'bb',c:'cc'};
for (let k in a){console.log(k)}; // a b c
for (let k of a){console>log(k)}; // TypeError
复制代码
4.6 跳出for...of
使用 break
来实现。
for (let k of a){
if(k>100)
break;
console.log(k);
}
复制代码
5. 生成器(详细介绍)
5.1 基本概念
Generator
生成器函数是一种异步编程解决方案。
原理:
执行 Genenrator
函数会返回一个遍历器对象,依次遍历 Generator
函数内部的每一个状态。
Generator
函数是一个普通函数,有以下两个特征:
function yield
通过调用 next
方法,将指针移向下一个状态,直到遇到下一个 yield
表达式(或 return
语句)为止。简单理解, Generator
函数分段执行, yield
表达式是暂停执行的标记,而 next
恢复执行。
function * f (){
yield 'hi';
yield 'leo';
return 'ending';
}
let a = f();
a.next(); // {value: 'hi', done : false}
a.next(); // {value: 'leo', done : false}
a.next(); // {value: 'ending', done : true}
a.next(); // {value: undefined, done : false}
复制代码
5.2 yield表达式
yield
表达式是暂停标志,遍历器对象的 next
方法的运行逻辑如下:
-
遇到
yield就暂停执行,将这个yield后的表达式的值,作为返回对象的value属性值。 -
下次调用
next往下执行,直到遇到下一个yield。 -
直到函数结束或者
return为止,并返回return语句后面表达式的值,作为返回对象的value属性值。 -
如果该函数没有
return语句,则返回对象的value为undefined。
注意:
-
yield只能用在Generator函数里使用,其他地方使用会报错。
// 错误1
(function(){
yiled 1; // SyntaxError: Unexpected number
})()
// 错误2 forEach参数是个普通函数
let a = [1, [[2, 3], 4], [5, 6]];
let f = function * (i){
i.forEach(function(m){
if(typeof m !== 'number'){
yield * f (m);
}else{
yield m;
}
})
}
for (let k of f(a)){
console.log(k)
}
复制代码
-
yield表达式如果用于另一个表达式之中,必须放在 圆括号 内。
function * a (){
console.log('a' + yield); // SyntaxErro
console.log('a' + yield 123); // SyntaxErro
console.log('a' + (yield)); // ok
console.log('a' + (yield 123)); // ok
}
复制代码
-
yield表达式用做函数参数或放在表达式右边,可以 不加括号 。
function * a (){
f(yield 'a', yield 'b'); // ok
lei i = yield; // ok
}
复制代码
5.3 next方法
yield
本身没有返回值,或者是总返回 undefined
, next
方法可带一个参数,作为上一个 yield
表达式的返回值。
function * f (){
for (let k = 0; true; k++){
let a = yield k;
if(a){k = -1};
}
}
let g =f();
g.next(); // {value: 0, done: false}
g.next(); // {value: 1, done: false}
g.next(true); // {value: 0, done: false}
复制代码
这一特点,可以让 Generator
函数开始执行之后,可以从外部向内部注入不同值,从而调整函数行为。
function * f(x){
let y = 2 * (yield (x+1));
let z = yield (y/3);
return (x + y + z);
}
let a = f(5);
a.next(); // {value : 6 ,done : false}
a.next(); // {value : NaN ,done : false}
a.next(); // {value : NaN ,done : true}
// NaN因为yeild返回的是对象 和数字计算会NaN
let b = f(5);
b.next(); // {value : 6 ,done : false}
b.next(12); // {value : 8 ,done : false}
b.next(13); // {value : 42 ,done : false}
// x 5 y 24 z 13
复制代码
5.4 for...of循环
for...of
循环会自动遍历,不用调用 next
方法,需要注意的是, for...of
遇到 next
返回值的 done
属性为 true
就会终止, return
返回的不包括在 for...of
循环中。
function * f(){
yield 1;
yield 2;
yield 3;
yield 4;
return 5;
}
for (let k of f()){
console.log(k);
}
// 1 2 3 4 没有 5
复制代码
5.5 Generator.prototype.throw()
throw
方法用来向函数外抛出错误,并且在Generator函数体内捕获。
let f = function * (){
try { yield }
catch (e) { console.log('内部捕获', e) }
}
let a = f();
a.next();
try{
a.throw('a');
a.throw('b');
}catch(e){
console.log('外部捕获',e);
}
// 内部捕获 a
// 外部捕获 b
复制代码
5.6 Generator.prototype.return()
return
方法用来返回给定的值,并结束遍历Generator函数,如果 return
方法没有参数,则返回值的 value
属性为 undefined
。
function * f(){
yield 1;
yield 2;
yield 3;
}
let g = f();
g.next(); // {value : 1, done : false}
g.return('leo'); // {value : 'leo', done " true}
g.next(); // {value : undefined, done : true}
复制代码
5.7 next()/throw()/return()共同点
相同点就是都是用来恢复Generator函数的执行,并且使用不同语句替换 yield
表达式。
-
next()将yield表达式替换成一个值。
let f = function * (x,y){
let r = yield x + y;
return r;
}
let g = f(1, 2);
g.next(); // {value : 3, done : false}
g.next(1); // {value : 1, done : true}
// 相当于把 let r = yield x + y;
// 替换成 let r = 1;
复制代码
-
throw()将yield表达式替换成一个throw语句。
g.throw(new Error('报错')); // Uncaught Error:报错
// 相当于将 let r = yield x + y
// 替换成 let r = throw(new Error('报错'));
复制代码
-
next()将yield表达式替换成一个return语句。
g.return(2); // {value: 2, done: true}
// 相当于将 let r = yield x + y
// 替换成 let r = return 2;
复制代码
5.8 yield* 表达式
用于在一个Generator中执行另一个Generator函数,如果没有使用 yield*
会没有效果。
function * a(){
yield 1;
yield 2;
}
function * b(){
yield 3;
yield * a();
yield 4;
}
// 等同于
function * b(){
yield 3;
yield 1;
yield 2;
yield 4;
}
for(let k of b()){console.log(k)}
// 3
// 1
// 2
// 4
复制代码
5.9 应用场景
-
控制流管理
解决回调地狱:
// 使用前
f1(function(v1){
f2(function(v2){
f3(function(v3){
// ... more and more
})
})
})
// 使用Promise
Promise.resolve(f1)
.then(f2)
.then(f3)
.then(function(v4){
// ...
},function (err){
// ...
}).done();
// 使用Generator
function * f (v1){
try{
let v2 = yield f1(v1);
let v3 = yield f1(v2);
let v4 = yield f1(v3);
// ...
}catch(err){
// console.log(err)
}
}
function g (task){
let obj = task.next(task.value);
// 如果Generator函数未结束,就继续调用
if(!obj.done){
task.value = obj.value;
g(task);
}
}
g( f(initValue) );
复制代码
- 异步编程的使用 在真实的异步任务封装的情况:
let fetch = require('node-fetch');
function * f(){
let url = 'http://www.baidu.com';
let res = yield fetch(url);
console.log(res.bio);
}
// 执行该函数
let g = f();
let result = g.next();
// 由于fetch返回的是Promise对象,所以用then
result.value.then(function(data){
return data.json();
}).then(function(data){
g.next(data);
})
复制代码
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 可迭代对象,迭代器(对象),生成器(对象)
- 搞清楚 Python 的迭代器、可迭代对象、生成器
- 深入理解python的迭代器,生成器,可迭代对象区别
- 生成器与迭代器
- 迭代器和生成器
- Python 迭代器、生成器和列表解析
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Java Servlet & JSP Cookbook
Bruce W. Perry / O'Reilly Media / 2003-12-1 / USD 49.99
With literally hundreds of examples and thousands of lines of code, the Java Servlet and JSP Cookbook yields tips and techniques that any Java web developer who uses JavaServer Pages or servlets will ......一起来看看 《Java Servlet & JSP Cookbook》 这本书的介绍吧!