本文是 重温基本 系列文章的第十三篇。
本日感觉:每次自我年终总结,都邑有种种心情和收成。
系列目次:
- 【温习材料】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);
})
参考材料
1.MDN 迭代器和天生器
2.ES6中的迭代器(Iterator)和天生器(Generator)
本部份内容到这完毕
Author | 王安然 |
---|---|
pingan8787@qq.com | |
博 客 | www.pingan8787.com |
微 信 | pingan8787 |
逐日文章引荐 | https://github.com/pingan8787… |
JS小册 | js.pingan8787.com |
迎接关注微信民众号【前端自习课】天天清晨,与您一同进修一篇优异的前端手艺博文 .