不算原创,更像是翻译,按照自己的理解组织了一下,内容主要来自Mozilla Iterators and Generators 以及页面里的相关扩展链接。
迭代器
定义
迭代器iterator是一个object,这个object有一个next函数,该函数返回一个有value和done属性的object,其中value指向迭代序列中的下个值。这样看来迭代器定义简直简单的感人T_T.
例如下列code
function makeIterator(array) {
console.log("Enter this function");
var nextIndex = 0;
return {
next: function() {
return nextIndex < array.length ?
{value: array[nextIndex++], done: false} :
{done: true};
}
};
}
var itt = makeIterator([1,3, 5]);
console.log(itt);
console.log("before calling next");
console.log(itt.next().value);
console.log(itt.next().value);
console.log(itt.next().value);
console.log(itt.next().done);
通过运行以上代码,我们可以看到打印结果是:
Enter this function
Object..
before calling next
1
3
5
true
其中打印Object内容展开是
Object
next:function ()
arguments:null
caller:null
length:0
name:”next”
prototype:Object
proto:function ()
[[FunctionLocation]]:testJS.js:31
[[Scopes]]:Scopes[2]
0:Closure (makeIterator)
array:Array(3)
nextIndex:3
1:Global
由此可以理解nextIndex和Array的值原来是存在闭包的scope里面。这个我之前一直不太理解的说,需要补一下闭包和scope相关知识。
迭代协议 Iteration Protocol
对迭代器有了初步概念后,该是时候稍作深入一下了,也就是迭代协议。迭代协议分为两部分:
- 可迭代协议
- 迭代器协议
可迭代协议(Iterable protocol)
What for?
可以认为,一旦支持可迭代协议,意味着该对象可以用for-of来遍历,可以用来定义或者定制JS 对象的迭代行为。常见的内建类型比如Array & Map都是支持可迭代协议的。
How?
对象必须实现@@interator方法,意味着对象必须有一个带有@@interator key的可以通过常量Symbol.iterator访问到的属性。[Symbol.iterator]是一个返回对象类型的零参数的函数。
可以通过运行下面代码来理解string对象的可迭代协议:
var someString = 'hi';
console.log(someString[Symbol.iterator]);
迭代器协议(iterator protocol)
迭代器协议就是已经在上面的迭代器一节所讲到的那样,iterator协议定义了产生value序列的一种标准方法。只要实现符合要求的next函数,该对象就是一个迭代器。
迭代协议例子
例如我们可以自定义string的迭代行为:
// need to construct a String object explicitly to avoid auto-boxing
var someString = new String('hi');
someString[Symbol.iterator] = function() {
return { // this is the iterator object, returning a single element, the string "bye"
next: function() {
if (this._first) {
this._first = false;
return { value: 'bye', done: false };
} else {
return { done: true };
}
},
_first: true
};
};
生成器(generator)
了解了什么是生成器之后,我们已经打好了基础,可以进一步理解生成器。
Why need it?
尽管自定义的迭代器很有用,不过创建迭代器需要仔细地编程,因为这里面需要明确地维护迭代中的内部状态。生成器可以允许你通过写一个可以维护自身状态数据的函数来定义一个可迭代算法,是迭代器的一个有力替代方案。
What is?
GeneratorFunction 是一种特殊类型的函数,该函数是作为iterator的工厂来工作的。当它得到执行的时候,会返回一个新的Generator object。
How to?
只要定义函数的时候使用 function * 语法就可以了。听起来生成器的写法也是简单的感人,那么事实是这样么……
code sample
function * makeGenerator(array){
var index = 0;
while (index <array.length)
yield array[index++];
}
var ge = makeGenerator(["a","b","c"]);
console.log(ge);
console.log(ge.next().value);
console.log(ge.next().value);
console.log(ge.next().value);
打印ge对象可以看到其proto, GeneratorFunction 和闭包scope的内容。
makeGenerator {[[GeneratorStatus]]: "suspended"} __proto__:Generator [[GeneratorStatus]]:"suspended" [[GeneratorFunction]]:function* makeGenerator(array) [[GeneratorReceiver]]:Window [[GeneratorLocation]]:testJS.js:53 [[Scopes]]:Scopes[2] 0:Closure (makeGenerator) array:Array(3) 0:"a" 1:"b" 2:"c" length:3 __proto__:Array(0) index:3 1:Global
那么理解了生成器的概念之后,查看打印的ge对象的内容,请问generator到底是迭代器还是可迭代呢?答案就是both。 那么为什么呢?可以在展开的ge对象里面 去找迭代协议的两种体现:next函数 和 Symbol.iterator属性。
可迭代
用户自定义可迭代User-defined Iterables
理解了上述概念后,我们就可以试着自己定义可迭代的对象啦。
例如用户定义一个可迭代的对象
var myIterable = {};
myIterable[Symbol.iterator] = function* () {
yield 1;
yield 2;
yield 3;
};
for (let value of myIterable) {
console.log(value);
}
内建可迭代(built-in iterables)
String, Array, TypedArray, Map and Set 对象都是内建的可迭代对象。
语法期待可迭代(Syntaxes Expecting Iterables)
不是很明白这个什么叫syntaxes expecting,不过好像不是很耽误。某些表达式或者语句是expecting iterables,例如下面四种
for-of 循环
用法如下,乍看好像跟for-in很像。
let list = [4, 5, 6];
for (let i of list) {
console.log(i);
}
那么果真如此么?
例子如下:
let list = [4, 5, 6];
for (let i in list) {
console.log(i); // "0", "1", "2",
}
for (let i of list) {
console.log(i); // "4", "5", "6"
}
哈,蛮神奇的,果然不一样,这是为啥呢?
与for-in的区别: 在于for-in是遍历对象的所有枚举属性名字, for of 遍历对象的迭代器的values。
喜欢用python的同学不要跟我一样容易用混了哦~
spread操作符
多元素或者变量展开。详见spread operator
也可以用作array的copy,但要注意copy是浅层copy哦。
yield *
yield * 被用于委派(delegate to)另外一个生成器或者可迭代对象。说起来感觉还是不如代码理解的直白,用代码理解如下:
function * g1(){
yield 2;
yield * 'xyz';
console.log("after g1 y 2");
return "this is a return of g1";
}
function *g2(){
yield 1;
var ret= yield * g1();
console.log("yield * g1 = "+ret);
yield 3;
}
var ii = g2();
console.log(ii.next().value);
console.log(ii.next().value);
console.log(ii.next().value);
console.log(ii.next().value);
console.log(ii.next().value);
console.log(ii.next().value);
结果输出是:
1
2
x
y
z
after g1 y 2
yield * g1 = this is a return of g1
3
yield * g1( ),表示当前迭代被委派到g1()这个生成器上,继续执行运行 yield 2,然后 遇到yield * ‘xyz’,因为 ‘xyz’可迭代,然后迭代继续被委派,于是得到x, y , z, 执行完后返回g1这个生成器向后执行,输出g1( ) 返回值后,yield 3.
如果把上面代码的yield * ‘xyz’ 改成yield ‘xyz’,就可以感受下yield vs. yield * 了。
1
2
xyz
after g1 y 2
yield * g1 = this is a return of g1
3
析构赋值(Destructuring assignment, available in ES6)
用于将数组的多个元素或者对象的属性解包(unpack)到不同变量。例如:
var a, b, rest;
[a, b] = [10, 20];
var o = {p: 42, q: true};
var {p, q} = o;
除此之外还有其他用途,例如变量交换:
var a = 1;
var b = 3;
[a, b] = [b, a];
用这个做交换,无需额外创建临时变量,那么到底它是如何实现的呢(不是XOR trick)?
有了解的可以分析一下不?
高级生成器
Generator.prototype.next()
生成器是按需来计算yielded的值的。next()方法也可以接受一个value,这个value可以用来修改生成器的中间状态。传递给next() 的value就会被当作是上一个yield 表达式返回的结果。
举斐波那契生成器的例子,使用next(x) 来重启这个序列。
function* fibonacci() {
var fn1 = 0;
var fn2 = 1;
while (true) {
var current = fn1;
fn1 = fn2;
fn2 = current + fn1;
var reset = yield current;
if (reset) {
fn1 = 0;
fn2 = 1;
}
}
}
var sequence = fibonacci();
console.log(sequence.next().value); // 0
console.log(sequence.next().value); // 1
console.log(sequence.next().value); // 1
console.log(sequence.next().value); // 2
console.log(sequence.next().value); // 3
console.log(sequence.next().value); // 5
console.log(sequence.next().value); // 8
console.log(sequence.next(true).value); // 0
console.log(sequence.next().value); // 1
console.log(sequence.next().value); // 1
console.log(sequence.next().value); // 2
Generator.prototype.return()
返回给定的值并且终结生成器。
function* gen() {
yield 1;
yield 2;
yield 3;
}
var g = gen();
g.next(); // { value: 1, done: false }
g.return('foo'); // { value: "foo", done: true }
g.next(); // { value: undefined, done: true }
Generator.prototype.throw()
The throw() method resumes the execution of a generator by throwing an error into it and returns an object with two properties done and value.
我理解是跟next(value)有点像,只不过是向里面传递了一个exception,然后使得该生成器里面触发异常并返回一个带有done和value属性的object。
返回的done值根据运行时迭代器是否done而取true或者false。
例子如下:
function* gen() {
while(true) {
try {
yield 42;
} catch(e) {
console.log('Error caught!');
}
}
}
var g = gen();
console.log(g.next());
console.log(g.throw(new Error('Something went wrong')));
console.log(g.next());
打印结果是:
Object {value: 42, done: false}
Error caught!
Object {value: 42, done: false}
Object {value: 42, done: false}