【重温基础】13.迭代器和生成器
栏目: JavaScript · 发布时间: 5年前
内容简介:本文是今日感受:每次自我年终总结,都会有各种情绪和收获。系列目录:
本文是 重温基础 系列文章的第十三篇。
今日感受:每次自我年终总结,都会有各种情绪和收获。
系列目录:
- 【复习资料】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 迭代器、生成器和列表解析
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
全景探秘游戏设计艺术
Jesse Schell / 吕阳、蒋韬、唐文 / 电子工业出版社 / 2010-6 / 69.00元
撬开你脑子里的那些困惑,让你重新认识游戏设计的真谛,人人都可以成为成功的游戏设计者!从更多的角度去审视你的游戏,从不完美的想法中跳脱出来,从枯燥的游戏设计理论中发现理论也可以这样好玩。本书主要内容包括:游戏的体验、构成游戏的元素、元素支撑的主题、游戏的改进、游戏机制、游戏中的角色、游戏设计团队、如何开发好的游戏、如何推销游戏、设计者的责任等。 本书适合任何游戏设计平台的游戏设计从业人员或即将......一起来看看 《全景探秘游戏设计艺术》 这本书的介绍吧!