es6学习笔记--Interator和Generator(以及for-of的用法)

这几天学习了遍历器和生成器,看着资料学,有点雾里缭绕的感受,让人忍不住放弃,还好多看了好几遍,怼着资料里的例子让本身学会了Interator和Generator。
 
Interator,中文简称:遍历器,是一种接口,为具备遍历结构的或者说有length长度的集合提供一个接口,从而进行遍历操做。
Generator,中文简称:生成器,从语法上讲是一种状态机,经过遍历操做,展现不一样的状态状况。
 

Interator(遍历器)git

Iterator 接口的目的,就是为全部数据结构(集合),提供了一种统一的访问机制,为for-of这个遍历方法提供接口,任何数据结构只要部署 Iterator 接口,就能够完成遍历操做(即依次处理该数据结构的全部成员)。
ps: 数据结构=集合

在 JavaScript 中 迭代器是一个对象,它提供了一个next() 方法,(除了next()方法还有return和throw方法),用来返回序列中的下一项。这个方法返回包含两个属性:done和 value。done属性是个布尔值,表明遍历是否结束,便是否还有必要再一次调用next方法。value属性表明当前成员的值。es6

迭代器对象一旦被建立,就能够反复调用next()。github

如下代码是模拟迭代器:
function makeIterator(array){
    var nextIndex = 0;
    return {
    next: function(){
        return nextIndex < array.length ?
            {value: array[nextIndex++], done: false} :
            {done: true};
    }
    };
}
let a = makeIterator(['apple','pear','orange'])
console.log(a.next())   // {value: "apple", done: false}
console.log(a.next())   // {value: "pear", done: false}
console.log(a.next())   // {value: "orange", done: false}
console.log(a.next())   // {done: true}
由上述例子可知:next方法返回的是一个对象,有value和done属性,若是还有下一个next()可遍历,那么done为false,若是done为true,说明不可再次遍历。
ps:遍历器建立以后不会自动触发,而是由next()触发。
 
一种数据结构只要部署了 Iterator 接口,咱们就称这种数据结构是“可遍历的”(iterable),

ES6 规定,默认的 Iterator 接口部署在数据结构的Symbol.iterator属性, Symbol.iterator属性自己是一个函数,就是当前数据结构默认的遍历器生成函数。执行这个函数,就会返回一个遍历器。编程

常见的具备Iterator接口的遍历器:
  Array
  Map
  Set
  String
  TypedArray
  函数的 arguments 对象
  NodeList 对象
  生成器
ps:普通的对象不具备Iterator接口,缘由是对象的遍历是没有顺序的,没有相应的索引值可言,因此想要使普通的对象要有Iterator接口,须要给它加上Object方法变成有顺序的对象便可。
Iterator遍历器简单来讲,就是在具备Iterator的对象(Array,Map,Set,String,TypedArray,函数的 arguments 对象,NodeList 对象,生成器)上进行for-of的循环,而且用next()方法获取遍历的值.
 

学习一下新型的for-of循环。可在具备Iterator 接口的元素进行遍历。数组

for-of循环和之前的之前的for循环和es5的forEach循环同样,遍历操做。
for...of语句在可迭代对象(包括 Array,Map,Set,String,TypedArray,arguments 对象等等)上建立一个迭代循环,调用自定义迭代钩子,并为每一个不一样属性的值执行语句。
let arr = [1,2,3];
for(let v of arr){
    v +=1;
    console.log(v)
} // 2  3  4
for-of替代了之前的全部的循环遍历,融合了其优势,摒弃了其缺点。
 
for-of与for,forEach,for-in相比的优缺点:
1 forEach循环不能用 break 中断循环,不然会报错.也不能使用 return 语句返回到外层函数
[1,2,3,4,5].forEach((i,v) => {
    console.log(v)
    if(i > 3){
        break;
    }
})  // Uncaught SyntaxError: Illegal break statement
2 for-in 遍历,只获取键名,获取不到键值。以任意顺序遍历键名,只能遍历带有字符串的key。一般不推荐循环数组。
for(let v in ['a','b','c']){
    console.log(v)
}  // 0   1   2
3 for循环书写太复杂,emmmm,只能这么说。
 
for-of的优势:
1 最简洁、最直接的遍历数组元素的语法
let arr = [1,2,3,4];
for(let v of arr){
    console.log(v)
}  // 1    2    3    4
上个方法避开了for-in循环的全部缺陷。
let a = ['a','b','c','d']
for(let v of a){
    console.log(v)
}   // a   b   c    d
上述代码for-of能够直接获取键值,若是想要获取其索引值能够采用Object扩展方法keys()和entires()
2 与forEach()不一样的是,它能够正确响应break、continue和return语句
for(let v of [1,2,3,4,5]){
    console.log(v)
    if(v > 3){
        break
    }
} //  1   2   3    4     5

3 能够遍历其余的全部集合(Nodelist,Set,Map),还有生成器数据结构

<div>1</div>
<div>2</div>
<div>3</div>
<div>4</div>


let doms = document.querySelectorAll('div')
for(let v of doms){
    console.log(v.innerHTML)
}    // 1   2   3   4
for-of能够遍历dom元素,而且进行相应的操做
let set = new Set([1,2,3,4,5])
for(let v of set){
    console.log(v)
}   //  1   2  3  4  5
let map = new Map().set('a',1).set('b',2)
for(let v of map){
    console.log(v)
}   
// ["a", 1]
// ["b", 2] 
Set和Map自己具备 Iterator 接口,因此用for-of循环,set返回的是一个值,而map返回的是一个数组。
ps:for-of不能遍历普通的对象,会报xxx is not iterable,须要把普通对象转化成具备Interator接口的便可。Object.keys(),Object.values()和Obejct.entries()也能够获取
let obj = {name:'peter',age:25}
for(let v of obj){
    console.log(v)
} // Uncaught TypeError: obj is not iterable
let obj = {name:'peter',age:25}
for(let v of Object.keys(obj)){
    console.log(v)
} // name  age

4 另外,for-of能够适用于字符串app

let str = 'hello';
for(let v of str) {
    console.log(v)
}   // h  e  l  l  o

Interator不是很难懂,只要明白了哪些是数据集合,就说明具备Interator接口,天然就能够看成遍历器,从而使用for-of循环和使用next方法获取想要的数据。dom

 

Generator(生成器)异步

经过对Interator的理解,生成器也是具备Interator接口的对象,它自己带有遍历特性,返回一个遍历器对象。async

从写法上看,它和普通函数差异不大,就多了两个特性:
  1 在函数声明前加上星号(*),
  2 函数内部多了一个关键字yield。

既然生成器是遍历器,那么可使用遍历器的方法(自己函数不会实行,必须经过next()方法才能调用或者使用for-of返回)

function *fn(){
    yield 'peter';
    yield 1;
    yield {name:'peter'};
    yield [1,2,3,4];
    yield function *foo(){
        yield 123;
    }
}
let a = fn()
console.log(a.next())
console.log(a.next())
console.log(a.next())
console.log(a.next())
console.log(a.next())
console.log(a.next())
{value: "peter", done: false}
{value: 1, done: false}
{value: {…}, done: false}
{value: Array(4), done: false}
{value: ƒ, done: false}
{value: undefined, done: true}
从上述例子可知:每个next()方法,就返回一个数据,这说明yield表达式表明一个进程,返回其后面的表达式。

学习一下yield:

1 因为 Generator 函数返回的遍历器对象,只有调用next方法才会遍历下一个内部状态,因此其实提供了一种能够暂停执行的函数。因此yield表达式就是暂停标志。
2 yield 是关键字,其后面能够跟变量,常量,表达式,可是必须有next方法才能调用或者for-of返回,自己不是返回值
function *fn(){
    yield 'a';
    let b = yield 'b' + 'c';
    yield 'd'
}
let foo = fn()
for(let v of foo){
    console.log(v)
}  //  a   bc    d
由上述例子可知:yield返回的是后面的表达式,不影响前面的声明
3 遇到yield表达式时,进程暂停,next()方法才会调用yield后面的表达式。
function *fn(){
    yield '1'
    console.log('start')
    yield '2'
}
let a = fn()
console.log(a.next())   
{value: "1", done: false}
4 做用和return差很少,但也有区别,return只能执行一回,yield能执行屡次,每次遇到yield时就先暂停,而后下一次运行从暂停的位置开始。
5 yield只能在Genterator函数里才能运用,在其余地方运用会报错。
function fn(){
    yield 'a'
}
fn()   // Uncaught SyntaxError: Unexpected string
6 yield和return同时在一个函数里时,按照代码同步顺序执行的结果,到了return就直接返回,不继续执行下一步。
function *fn(){
    yield '1';
    yield '2';
    return '3';
    yield '4'
}
let a = fn()
console.log(a.next());  // {value: "1", done: false}
console.log(a.next());  // {value: "2", done: false}
console.log(a.next());  // {value: "3", done: true}
console.log(a.next());  // {value: undefined, done: true}
console.log(a.next());  // {value: undefined, done: true}
7 yield 也能够跟星号,表明在一个 Generator 函数里面执行另外一个 Generator 函数。
在生成器函数里是没法进行另外一个Generator函数的,没有效果,若单单使用yield,返回的是另外一个生成器的对象
function *foo(){
    yield 'f'
}
function *fn(){
    yield 'a';
    yield foo()
    yield 'b'
}
for( let v of fn()){
    console.log(v)   
}   // a   foo {<suspended>}     b
因此在yield* 能够用来进行另外一个Generator函数(只要在一个Generator函数运行另外一个Generator函数就能够直接yield*)
function *foo(){
    yield 1;
    yield 2
}
function *fn(){
    yield 'a';
    yield *foo()
    yield 'b'
}
for( let v of fn()){
    console.log(v)    // a   1  2    b
}

上面已经说到生成器也是具备Interator接口的对象,不可置否的,生成器自己带有Symbol.iterator,能够说生成器是遍历器的一种,因此可遍历,可使用for-of来循环数据。

for...of循环能够自动遍历 Generator 函数时生成的Iterator对象,且此时再也不须要调用next方法。

简单来讲,for-of里返回的数据和next()中value同样.区别在于for-of循环完后不循环return后面的表达式。而next()则会。
function *fn(){
    yield 'a';
    yield 'b';
    yield 'c';
    yield 'd';
    return 'end'
}
let a = fn()
for(let v of a){
    console.log(v)  // a   b   c    d   
}
function *fn(){
    yield 'a';
    yield 'b';
    yield 'c';
    yield 'd';
    return 'end'
}
let a = fn()
console.log(a.next());   // {value: "a", done: false}
console.log(a.next());   // {value: "b", done: false}
console.log(a.next());   // {value: "c", done: false}
console.log(a.next());   // {value: "d", done: false}
console.log(a.next());   // {value: "end", done: true}
console.log(a.next());   // {value: undefined, done: true}
扩展运算符也支持生成器的遍历
function *fn(){
    yield 'a';
    yield 'b';
    yield 'c';
    yield 'd';
    return 'end'
}
let a = fn()
console.log([...a])   //  ["a", "b", "c", "d"]
因而可知,遍历出来的依然不包含return的表达式

概要总结;只要具备Symbol.iterator属性的,就能够遍历yield表达式

Generator 函数也不能跟new命令一块儿用,会报错
function* f() {
    yield 2;
    yield 3;
}
new f()  // TypeError: F is not a constructor

Generator 函数的方法:

next() 返回 Generator 函数对象中yield后面的表达式,上面已经用到了next方法。yield表达式自己没有返回值,老是返回undefined

当next()有参数时,该参数就会被看成上一个yield表达式的返回值。
function *fn(x){
    let a = yield x;
    let b = yield 2 + a;
}
let a = fn(2);
console.log(a.next(5));   // {value: 2, done: false}
console.log(a.next(10));  // {value: 12, done: false}
console.log(a.next(20));  // {value: undefined, done: true}
因为next方法的参数表示上一个yield表达式的返回值,因此在第一次使用next方法时,传递参数是无效的。
因此第一个next方法用来启动遍历器对象,因此不用带有参数。
      第二个next()方法把第二个yield后面的2替换成了10,因此10+2=12
      第三个next()方法由于没有yield,返回undefined,参数无效
ps:若是一开始传了参数,第二个next没有传参数,则是undefined
function *fn(x){
    let a = yield x;
    let b = yield 2 + a;
}
let a = fn(2);
console.log(a.next());   // {value: 2, done: false}
console.log(a.next());  // {value: NaN, done: false}
console.log(a.next());  // {value: undefined, done: true}
第二个传参为空致使undefined+2等于NaN
next()方法的意义:Generator 函数从暂停状态到恢复运行,它的上下文状态(context)是不变的。经过next方法的参数,就有办法在 Generator 函数开始运行以后,继续向函数体内部注入值。也就是说,能够在 Generator 函数运行的不一样阶段,从外部向内部注入不一样的值,从而调整函数行为。

throw() 在函数体外抛出错误,而后在 Generator 函数体内捕获。

let a = function* () {
    try {
        yield ;
    } catch ( e ){
        console.log(e);
    }
};
var i = a();
console.log(i.throw())  // Uncaught undefined
生成器的声明方式和普通的同样。    yield后面没有表达式,为undefined。
throw方法能够接受一个参数,该参数会被catch语句接收,建议抛出Error对象的实例。
let g = function* () {
    try {
        yield 1;
    } catch (e) {
        console.log(e);
    }
    yield 'a';
    yield 'b'
};

let i = g();
console.log(i.next())  // 1
i.throw(new Error('出错了!'));  // Error: 出错了!(…)   附带执行了一次yield ‘a’
console.log(i.next())  // b

throw()方法的做用就是捕获异常,而且继续执行下去,不由于异常而中断。throw方法被捕获之后,会附带执行下一条yield表达式。也就是说,会附带执行一次next方法。

ps:不要混淆遍历器对象的throw方法和全局的throw命令。上面的异常是用遍历器对象的throw方法抛出的,而不是用throw命令抛出的。后者只能被函数体外的catch语句捕获。

throw()的意义:大大方便了对错误的处理。多个yield表达式,能够只用一个try...catch代码块来捕获错误。若是使用回调函数的写法,想要捕获多个错误,就不得不为每一个函数内部写一个错误处理语句,如今只在 Generator 函数内部写一次catch语句就能够了。

return() 返回给定的值,而且终结遍历 Generator 函数。

当return()不传参数时,默认是undefined,就至关于最后一步,done即为true时的操做,value值为undefined
function *fn(){
    yield 1;
    yield 2;
    return 3;
}
let a = fn();
console.log(a.next())   // {value: 1, done: false}
console.log(a.return());   // {value: undefined, done: true}
console.log(a.next())   // {value: undefined, done: true}
当return()传参数时,value值为传的参数
function *fn(){
    yield 1;
    yield 2;
    return 3;
}
let a = fn();
console.log(a.next())   // {value: 1, done: false}
console.log(a.return(100));   // {value: 100, done: true}
console.log(a.next())   // {value: undefined, done: true}

return()的意义:一般在生成器异步操做时须要在某个时段跳出来。

Generator生成器是异步编程提供了方便。

 

对于Interator和Generator,在平时使用时不多用到,只有那个for-of能够替代for循环使用,主要用于异步编程async当中。学习这个感受没学全,事后我会再仔细学一遍。知识点我放到github里了,有须要能够去下载一块儿学习。

仍是那句话。有什么问题或错误请私信或者下方评论,一块儿讨论进步。

 

参考资料:

阮一峰es6入门 http://es6.ruanyifeng.com/

相关文章
相关标签/搜索