JavaScript的迭代器与生成器
前沿:
可迭代对象及其相关的迭代器是是ES6的一个特性。数组是可迭代的,字符串、set对象和map对象也是。这意味着这些数据结构的内容可以通过for/of循环来迭代。
例:
let sum =0; for(let i of [1,2,3]{ sum+=i; } sum//6
迭代器让...操作符能够展开或“扩展”可迭代对象,
let chars = [..."abcd"]; let data = [1,2,3,4,5]; Math.max(...data)//5
迭代器也可以用于解构赋值:
let d = Uint8Array.of(255,0,1,199); let [r,g,b,a] = d; console.log(a)//199
迭代Map对象时,返回值是【key,vaule】对,在for/of循环中可以直接使用解构赋值:
let m =new Map([["one",1],["weo",2]]); for (let[k,V] of m) console.log(k,V)//one 1 weo 2
如果只想迭代键或值,而不是键/值对,可以使用keys()或者values()的方法;
let m =new Map([["one",1],["weo",2]]); console.log([...m]);//默认迭代//(2) ['one', 1] ['weo', 2] console.log([...m.entries()])//enteries方法相同(2) ['one', 1] ['weo', 2] console.log([...m.keys()])//方法只迭代键(2) ['one', 'weo'] console.log([...m.values()])//只迭代值(2) [1, 2]
最后,有些会接受Array对象的内置函数和构造函数(在ES6及之后的版本中)可以接收任意迭代器。,例如,set()构造函数就是这样一个API:
//字符串是可以迭代的,因此两个集合相同。 console.log( new Set("abc"))//Set(3) {'a', 'b', 'c'}
本章解释迭代器的原理,并展示如何创建可迭代的数据结构。理解了迭代器的基本概念后,我们在讲解再生器。生成器也是ES6的一个新的特性,主要用于简化迭代器的创建。
迭代器原理
摘要:
for/of循环和扩展操作符可以直接操作可迭代对象,但那有必要了解这种迭代是如何发生的。要理解JavaScript中的这种迭代,必须理解3个不同的类型。首先是可迭代对象,类似于Array、set、map都是可以迭代的。其次是迭代器对象,用于执行迭代。最后是迭代结果对象,保存每次迭代的结果。
可迭代对象指的是任何具有专用的迭代器方法,且该方法返回且代其对象的对象。迭代器对象指的是任何具有next()方法,且该方法返回迭代结果对象的对象。迭代结果对象是具有属性value和done的对象。要迭代一个可迭代对象,首先要调用其迭代器方法获得一个迭代器对象。然后,重复调用这个迭代器对象的next()方法,直至返回done属性为true的迭代结果对象。这里比较特别的是,可迭代器的迭代方法没有惯用名称,而是使用了符号Symbol.iterator作为名字。因此可迭代对象iterable的简单for/of循环也可以写成如下这种形式:
let iterable = [99]; let iterator = iterable[Symbol.iterator](); for(let result = iterator.next();!result.done;result = iterator.next()){ console.log(result.value)//99 }
内置可迭代数据类型的迭代器对象本身也是可以迭代的(也就是说,他们有一个名为symbol.iterator的方法,返回他们自己)。在下面的代码所示的需要迭代“部分使用”的迭代器时,这种设计是有用的:
let list = [1,2,3,4,5] let iter = list[Symbol.iterator](); console.log(iter) let head = iter.next(); console.log(head)//1 let tail = [...iter] console.log(tail)//[2,3,4,5]
实现可迭代对象
在ES6中。可迭代对象非常重要。因此,只要你的数据类型表示某种可迭代的结构,就应该考虑把他们实现为可迭代对象。Range类就是可迭代的。那些类使用生成器函数把自己转换成可迭代的类。
为了让类可迭代,必须实现一个名为Symbol.iterator的方法。这个方法必须返回一个迭代器对象,该对象有一个next()方法。而这个next()方法必须返回一个迭代结果对象,该对象有一个value属性和/或一个布尔值done属性。下例实现了一个可迭代的Range类,演示了如何创建可迭代对象、迭代器对象和迭代结果对象。
// Range对象表示一个数值范围{x:from<=x<=to} // Range定义了has()方法用于测试给定数值是不是该范围的成员 // Range是可迭代的,迭代器范围内所有的整数 class Range{ constructor (from,to){ this.from = from; this.to = to; } //让range对象数值集合一样 has(x){return typeof x ==="number"&&this.from<=x&&x<=this.to;} //使用几何表示法返回当前范围的字符串表示 toString(){return`{x|${this.from}}<=x<=${this.to}`} //通过返回一个迭代器对象,让Range对象可迭代 // 注意这个方法的名字是一个特殊符号,不是字符串 [Symbol.iterator](){ //每个迭代器实例必须相互独立、互不影响地迭代自己的范围 //因此需要一个状态变量跟踪迭代的位置。从第一个大于等于form整数开始 let next = Math.ceil(this.from);//这是下一个要返回的值 let last = this.to; //不会返回大于它的值 return{ //这个next()方法是迭代器的标志,他必须返回一迭代器结果对象 next(){ return(next <= last)//如果还没有返回last ?{value:next++}//则返回next并给它加1 :{done:true}//否则返回表示完成的对象 } //为了方便起见,迭代器本身也可迭代 } } } for (let x of new Range(1,10)) console.log(x);//打印数值1到10 console.log(...new Range(-2,2))//-2 -1 0 1 2
可迭代对象与迭代器有一个重要的特点,他们天性懒惰:如果计算下一个值需要一定的计算量,则相应的计算会推迟到实际需要下一个值的时候在发生。例如,假设假设有一个非常长的文本字符串,你想对他们进行分词,返回以空格分隔的单词。如果使用字符串的split()方法,哪怕是一个单词都还没用也要处理整个字符串,这样可能会占用很多的内存来保存反回的数组和其中的字符串。下面这个函数可以对字符串中的单词进行懒惰迭代,不必把它们全部保存在内存里:
function words(s){ var r = /\s+|$/g;//匹配一个或多个空格或末尾 r.lastIndex = s.match(/[^]/).index;//开始匹配第一个非空格 return{//返回一个可迭代的迭代器容器 [Symbol.iterator](){//这个方法是可迭代对象必须的 return this; }, next(){//这个方法是迭代器必须的 let start = r.lastIndex;//从上次匹配的地方恢复 if(start<s.length){//如果还没有处理完 let match = r.exec(s); if(match){ return {value:s.substring(start,match.index)}; } } return {done:true} } } } console.log([...words("abc def ghi!")])
定义生成器
在使用关键字 function 定义函数对象时,通过在关键字 function 后面添加星号 *
来定义生成器。
不能使用箭头函数定义生成器。
星号 *
不受其两侧的空格影响。
function *generator() {} function * generator01() {} function* generator02() {} let generator03 = function *() {} // 在对象字面量中简写函数时 let obj = { *generator04() {} }
创建生成器对象
调用生成器并不会执行生成器中的代码,而是会创建一个生成器对象。
function *generator() { return 'finished' } // 创建生成器对象 let generatorObj = generator()
生成器对象是迭代器。生成器对象实现了接口 Iterator ,拥有方法 next() 。但生成器对象的方法 next() 并不是用于迭代可迭代对象的。
生成器对象是可迭代对象。生成器对象也实现了接口 Iterable ,而且生成器对象的默认迭代器函数返回生成器对象自身。
中断生成器
在生成器中使用关键字 yield 来指定生成器中断执行的位置。
生成器执行的中断、恢复:
- 生成器在执行时遇到关键字 yield ,会停止执行,函数作用域的状态会被保留。
- 当生成器的生成器对象调用方法 next() 时,生成器会从停止执行的位置开始恢复执行,直到遇到关键字 yield 或 return ,生成器才会停止执行。
关键字 yield 像关键字 return 一样可以返回值。关键字 yield 只能在生成器的函数块中使用,不能在普通函数的函数块中使用。
例如:
function *generator() { yield '01' // 中断执行,并返回值 yield '02' return 'finished' } let generatorObj = generator() console.log(generatorObj.next()) console.log(generatorObj.next()) console.log(generatorObj.next()) // 输出: // { done: false, value: '01' } // { done: false, value: '02' } // { done: true, value: 'finished' }
提前终止生成器
通过调用生成器对象的方法 return() ,提前终止生成器。
生成器对象的方法 return() 会强制生成器进入关闭状态。
生成器对象的方法 return()
例:
function *generator() { yield '01' yield '02' return 'finished' } let generatorObj = generator() console.log(generatorObj.next()) console.log(generatorObj.return('Exiting early')) // 提前终止生成器 console.log(generatorObj.next()) // 输出: // { done: false, value: '01' } // { done: true, value: 'Exiting early' } // { done: true, value: undefined }
生成器迭代可迭代对象
生成器通过在关键字 yield 和可迭代对象之间添加星号 * ,来迭代一个可迭代对象。
星号 * 不受其两侧的空格影响。
生成器迭代可迭代对象的主要过程:
- 创建可迭代对象的迭代器。
- 调用迭代器的方法 next() 。
- 判断迭代器结果的属性 done 。
- 如果迭代器结果的属性 done为 false ,则使用关键字 yield 返回迭代器结果的属性 value 的值。并转到过程 2 。
- 如果迭代器结果的属性 done为 true,则获取迭代器结果的属性 value 的值。
- 结束。
因此生成器会将可迭代对象的迭代器最终返回的迭代器结果 { done: true, value: any } 的属性 value 的值作为 yield* 最终的值,但不会停止执行来返回 yield* 的值。
例:
function *generator() { yield* [1, 2] // 迭代可迭代对象 return 'finished' } let generatorObj = generator() console.log(generatorObj.next().value) console.log(generatorObj.next().value) console.log(generatorObj.next().value) // 输出: // 1 // 2 // finished function *generator01() { console.log('yield*: ', yield* [1, 2]) return 'finished' } generatorObj = generator01() console.log(generatorObj.next().value) console.log(generatorObj.next().value) console.log(generatorObj.next().value) // 输出: // 1 // 2 // yield*: undefined // finished
标签:console,log,迭代,对象,JavaScript,生成器,let From: https://www.cnblogs.com/user-zbb/p/17099538.html