for...of 与 Iterator


不管你过去是如何顺序遍历字符串内容,在 ES6 看到for...of语法可以遍历字符串,应该会觉得很方便:

> let name = 'Justin';
undefined
> for(let ch of name) {
...     console.log(ch);
... }
J
u
s
t
i
n
undefined
>

如果你想要遍历数组,也可以使用for...of

> for(let v of [10, 20, 30]) {
...     console.log(v);
... }
10
20
30
undefined
>

实际上,for...of会尝试从字符串或数组上获取迭代器(Iterator),一个可以返回迭代器的函数,是使用Symbol.iterator作为特性存储着,试着获取该函数…

> let arr = [10, 20, 30];
undefined
> let it = arr[Symbol.iterator]();
undefined
> it.next();
{ value: 10, done: false }
> it.next();
{ value: 20, done: false }
> it.next();
{ value: 30, done: false }
> it.next();
{ value: undefined, done: true }
>

可以看到返回的迭代器具有next方法,每次调用都会返回一个迭代器结果(IteratorResult)对象,当中包含了valuedonevalue是当次迭代的结果,done表示迭代是否结束,当迭代结束时,value会是undefined

只要对象上具有可返回迭代器的函数,该对象就会是可迭代的(Iterable),也就可以使用for...of来进行迭代。例如,来创建一个 Python 风格的range函数:

function range(start, end) {
    let i = start;
    return {
        [Symbol.iterator]() { 
            return this; 
        },
        next() {
            return i < end ? 
                       {value: i++, done: false} :
                       {value: undefined, done: true}
        }
    };
}

for(let n of range(3, 8)) {
    console.log(n);
}

执行结果会显示 3 到 7 的数字,range函数返回的对象,具有[Symbol.iterator]特性,执行后可返回迭代器,由于该对象本身也实现了迭代器的next特性,因此直接返回自己(return this)就可以了。

next的返回对象在valueundefined,可以省略不写(你应该知道为什么吧!)。

对象必须有[Symbol.iterator]特性,执行后可返回迭代器,才能使用for...of,因此单纯的对象字面量,别寄望能使用for...of来迭代特性名称或特性值(没有迭代器,怎么知道你要迭代什么东西),这会引发TypeError

> var o = { x : 10 };
undefined
> for(let what of o) {
...     console.log(what);
... }
TypeError: o is not iterable
    at repl:1:14
    at ContextifyScript.Script.runInThisContext (vm.js:50:33)
    at REPLServer.defaultEval (repl.js:240:29)
    at bound (domain.js:301:14)
    at REPLServer.runBound [as eval] (domain.js:314:12)
    at REPLServer.onLine (repl.js:441:10)
    at emitOne (events.js:121:20)
    at REPLServer.emit (events.js:211:7)
    at REPLServer.Interface._onLine (readline.js:282:10)
    at REPLServer.Interface._line (readline.js:631:8)
>

ES6 中大多数内置的迭代器仅实现了next,除此之外,迭代器可以选择性地提供returnthrow方法。

return是提供迭代器的客户端调用,告知迭代器不需要再迭代了,因此迭代器此时可在return中实现回收资源之类的动作,然而返回{value: undefined, done: true}value的实际值,也可能是迭代器的客户端指定之值,在此之后,调用迭代器的next不应该有进一步的迭代结果。

例如,for...of会在被break或抛出一个错误时调用return,不会传入任何值。

function range(start, end) {
    let i = start;
    return {
        [Symbol.iterator]() { 
            return this; 
        },
        next() {
            return i < end ? 
                   {value: i++, done: false} :
                   {done: true}
        },
        return() {
            console.log('return');
            return {done : true};
        }
    };
}

let r = range(3, 8);
for(let n of r) {
    console.log(n);
    throw new Error('shit');
}

执行的结果中可以看到,迭代器的return被调用了:

3
return
C:\workspace\helloworld.js:19
        throw new Error('shit');
        ^

Error: shit
    at Object.<anonymous> (C:\workspace\helloworld.js:19:8)
    at Module._compile (module.js:635:30)
    at Object.Module._extensions..js (module.js:646:10)
    at Module.load (module.js:554:32)
    at tryModuleLoad (module.js:497:12)
    at Function.Module._load (module.js:489:3)
    at Function.Module.runMain (module.js:676:10)
    at startup (bootstrap_node.js:187:16)
    at bootstrap_node.js:608:3

迭代器的客户端可以透过迭代器throw方法,将指定的异常送入迭代器,这时就要看迭代器怎么处理了,可以是单纯结束迭代,然而忽略错误,例如,底下只会显示 3:

function range(start, end) {
    let i = start;
    return {
        [Symbol.iterator]() { 
            return this; 
        },
        next() {
            return i < end ? 
                       {value: i++, done: false} :
                       {done: true}
        },
        return() {
            console.log('return');
            return {done : true};
        },
        throw(e) {
            i = end;
            return {done: true};
        }
    };
}

let r = range(3, 8);
for(let n of r) {
    console.log(n);
    r.throw(new Error());
}

由于i被设为end了,在下一次调用next时,会返回{done: true}而中止迭代,照规范,throw要返回迭代器结果对象。当然,也许你的迭代器只是跳过下个迭代值也可以,例如,底下显示 3、5、7:

function range(start, end) {
    let i = start;
    return {
        [Symbol.iterator]() { 
            return this; 
        },
        next() {
            return i < end ? 
                           {value: i++, done: false} :
                           {done: true}
        },
        return() {
            console.log('return');
            return {done : true};
        },
        throw(e) {
            return {value: ++i, done: false};
        }
    };
}

let r = range(3, 8);
for(let n of r) {
    console.log(n);
    r.throw(new Error());
}

又或者直接抛出异常:

function range(start, end) {
    let i = start;
    return {
        [Symbol.iterator]() { 
            return this; 
        },
        next() {
            return i < end ? 
                           {value: i++, done: false} :
                           {done: true}
        },
        return() {
            console.log('return');
            return {done : true};
        },
        throw(e) {
            throw e;
        }
    };
}

let r = range(3, 8);
for(let n of r) {
    console.log(n);
    r.throw(new Error());
}

底下是执行结果:

3
return
C:\workspace\helloworld.js:15
                        throw e;
                        ^

Error
    at Object.<anonymous> (C:\workspace\helloworld.js:22:10)
    at Module._compile (module.js:635:30)
    at Object.Module._extensions..js (module.js:646:10)
    at Module.load (module.js:554:32)
    at tryModuleLoad (module.js:497:12)
    at Function.Module._load (module.js:489:3)
    at Function.Module.runMain (module.js:676:10)
    at startup (bootstrap_node.js:187:16)
    at bootstrap_node.js:608:3

可以看到,当你抛出异常时,此异常又传播至for...of,因此就触发了return方法的执行。

之后在看到生成器(Generator)时,可以看到生成器本质上也是个迭代器,生成器会实现nextreturnthrow三个方法。


展开阅读全文