第九章 迭代器和生成器

96 阅读8分钟

迭代器

迭代器(iterator):一个具有next方法的对象,且next方法也会返回一个对象,该返回的对象能够反映下一个数据以及指示是否迭代完成

迭代器创建函数(iterator creator):一个返回迭代器的函数

const arr = [1, 2, 3, 4, 5, 6];

function createIterator(arr) {
    let i = 0;
    return {
        next() {
            const res = {
                value: arr[i],
                done: i >= arr.length
            }
            i++;
            return res;
        }
    }
}

const iterator = createIterator(arr);

let data = iterator.next();
while (!data.done) {
    console.log(data.value);
    data = iterator.next();
}

注意:迭代器不是只能迭代数组

可迭代协议和for of循环

ES6规定,如果一个对象具有知名符号属性Symbol.iterator(对象的原型链上有也算),并且该属性的值是一个迭代器创建函数,则该对象就是可迭代的(iterable)(即该对象就是可迭代对象)

数组就是一个可迭代对象:

const arr = [1, 2, 3, 4, 5];

const iterator = arr[Symbol.iterator]();

let data = iterator.next();
while (!data.done) {
    console.log(data.value);
    data = iterator.next();
}

很多类数组也是可迭代对象,比如:

const divs = document.querySelectorAll("div");

const iterator = divs[Symbol.iterator]();

let data = iterator.next();
while (!data.done) {
    console.log(data.value);
    data = iterator.next();
}

也可以自己创建可迭代对象

const obj = {
    a: 1,
    b: 2,
    c: 3,
    [Symbol.iterator](){
        const keys = Object.keys(this);
        const that = this;
        let i = 0;
        return {
			next(){
                const propName = keys[i];
                const propValue = that[propName];
				const res = {
                    value: {
                        propName,
                        propValue
                    },
                    done: i >= keys.length
                }
                i++;
                return res;
            }
    	}
    }
};

const iterator = obj[Symbol.iterator]();

let data = iterator.next();
while (!data.done) {
    console.log(data.value);
    data = iterator.next();
}

for of循环

for of循环专门用于遍历可迭代对象,格式如下:

for(const item of obj){
    console.log(item);
}

其实for of循环就是下面写法的语法糖

const iterator = obj[Symbol.iterator]();

let data = iterator.next();
while (!data.done) {
    const item = data.value;
    console.log(item);
    data = iterator.next();
}

注意:for of循环只能遍历可迭代对象,若提供的不是可迭代对象,则会报错

const obj = {
    0: "a",
    1: "b",
    2: "c",
    length: 3
};

for(const item of obj){		// TypeError: obj is not iterable
    console.log(item);
}

展开运算符与可迭代对象

当展开运算符使用在对象身上,但展开后却不是成为对象的一部分,会自动使用迭代模式对对象进行展开

比如对象展开为数组

const obj = {
    a: 1,
    b: 2,
    c: 3,
    [Symbol.iterator](){
        const keys = Object.keys(this);
        const that = this;
        let i = 0;
        return {
			next(){
                const propName = keys[i];
                const propValue = that[propName];
				const res = {
                    value: {
                        propName,
                        propValue
                    },
                    done: i >= keys.length
                }
                i++;
                return res;
            }
    	}
    }
};

const arr = [...obj];

console.log(arr);

/**
	[
		{propName: 'a', propValue: 1},
		{propName: 'b', propValue: 2},
		{propName: 'c', propValue: 3}
	]
*/

又或者对象展开成为函数的参数

const obj = {
    a: 1,
    b: 2,
    c: 3,
    [Symbol.iterator](){
        const keys = Object.keys(this);
        const that = this;
        let i = 0;
        return {
			next(){
                const propName = keys[i];
                const propValue = that[propName];
				const res = {
                    value: {
                        propName,
                        propValue
                    },
                    done: i >= keys.length
                }
                i++;
                return res;
            }
    	}
    }
};

function test(a, b, c){
    console.log(a);
    console.log(b);
    console.log(c);
}

test(...obj);
/**
	{propName: 'a', propValue: 1}
	{propName: 'b', propValue: 2}
	{propName: 'c', propValue: 3}
*/

注意:

  • 如果上面两种情况,所展开的对象是不可迭代的,则只能对它们展开成为另一个对象

    const obj1 = {
        a: 1,
        b: 2
    };
    
    const obj2 = {
        ...obj1
    };
    
    console.log(obj2);			// { a: 1, b: 2 }
    
    const arr = [...obj1];		// TypeError: obj1 is not iterable
    
  • 如果将可迭代对象展开为另一个对象,可迭代对象的符号属性也会参与“展开”

    const obj1 = {
        a: 1,
        b: 2,
        c: 3,
        [Symbol(1)]: 4
    };
    
    
    const obj2 = {
        ...obj1
    };
    
    console.log(obj2);
    
    /**
    	obj2 = {
    		a: 1,
    		b: 2,
    		c: 3,
    		[Symbol(1)]: 4
    	}
    */
    
  • 展开运算符只能使用在引用值身上,如果将展开运算符应用在原始值身上,则原始值将先会被包装成为引用值,然后变为对该引用值的展开

    只要原始值被当做引用值使用,就JS就会为原始值临时生成一个引用值来代替原始值接受操作(包装类)

解构可迭代对象

  • 解构运算符只能使用在引用值身上,如果将解构运算符应用在原始值身上,则原始值将先会被包装成为引用值,然后变为对该引用值的解构

    const str = "123";
    
    const [ a, b ] = str;
    
    console.log(a, b);				// "1" "2"
    
  • 当使用数组解构方式解构可迭代的非数组对象时,会使用迭代模式对对象进行解构

    const obj = {
        a: 1,
        b: 2,
    	[Symbol.iterator](){
            const keys = Object.keys(this);
            let index = 0;
            const that = this;
            return {
                next(){
                    const res = {
                        value: undefined,
                        done : index >= keys.length
                    };
                    if(index < keys.length){
                        res.value = {
                            key: keys[index],
                            value: that[keys[index]]
                        };
                        index++;
                    }
                    return res;
                }
            }
        }
    }
    
    console.log(obj[0]);			// undefined
    
    const [ ele1, ele2 ] = obj;
    
    console.log(ele1, ele2);		// { key: "a", value: 1 } { key: "b", value: 2 }
    

生成器

生成器是一个通过构造函数Generator创建的对象,但该构造函数只能在JS内部使用,我们无法使用该构造函数

生成器既是一个迭代器,又是一个可迭代对象

生成器的创建,必须依托于生成器函数(Generator Function)

注意:

  • 生成器函数不是指Generator构造函数
  • 调用生成器函数时,函数内部会自动调用构造函数Generator创建生成器,并将生成器返回

生成器函数

书写函数时,在function关键字后面或在函数名前面加上*,则该函数就是生成器函数

生成器函数返回的一定是生成器(就像async修饰的函数返回的一定是Promise一样)

function *method1(){}

const method2 = function*(){}

const obj = {
    *method3(){}
}

生成器的函数内部就是给该函数返回的生成器提供迭代的数据的

生成器函数内部可以使用yield关键字,该关键字之后的数据,将会作为之后迭代所得到对象的value属性

function *method(){
    yield 1;
}

const g = method();

console.log(g.next());		// { value: 1, done: false }

每执行到一个yield表达式,生成器函数的函数体就会”暂停执行“,直到下一次调用生成器的next方法

function *method(){
    console.log("a");
    yield 1;
    console.log("b");
    yield 2;
    console.log("c");
}

const g = method();

console.log(g.next());
// "a"
// { value: 1, done: false }
console.log(g.next());
// "b"
// { value: 2, done: false }
console.log(g.next());
// "c"
// { value: undefined, done: true }

若生成器对应的生成器函数执行完,就意味着结束了,不会反过头来重新执行

function *method(){
    yield 1;
}

const g = method();

console.log(g.next());		// { value: 1, done: false }
console.log(g.next());		// { value: undefined, done: true }
console.log(g.next());		// { value: undefined, done: true }
console.log(g.next());		// { value: undefined, done: true }
...							// { value: undefined, done: true }...

注意:

  • 一个函数,它不允许同时被async和*修饰
  • 单单调用生成器函数,只会得到一个生成器结果,并不会实际执行函数的函数体,要想执行生成器函数的函数体,需要调用返回的生成器的next方法
  • 生成器本身就是迭代器,因此生成器函数就是迭代器创建函数

细节

  1. 生成器函数的返回值,会出现在第一次done为true时的value属性中

    function *method(){
        yield 1;
        return 10;
    }
    
    const g = method();
    
    console.log(g.next());			// { value: 1, done: false }
    console.log(g.next());			// { value: 10, done: true }
    console.log(g.next());			// { value: undefined, done: true }
    console.log(g.next());			// { value: undefined, done: true }
    
  2. 调用生成器的next方法时,可以传递参数,传递的参数会成为上一次调用next时的yield表达式的返回值

    function* method() {
        let info = yield 1;			// 运行到yield表达式时就卡住了,此时赋值表达式还没有执行
        console.log(info);
        info = yield 2;
        console.log(info);
    }
    
    const g = method();
    
    g.next();		// 第一次调用生成器的next方法时传递参数无任何效果
    g.next(10);		// 控制台输出【10】,即yield 1表达式的返回值为10
    g.next(20);		// 控制台输出【20】,即yield 2表达式的返回值为10
    

    不传递实参,相当于对应的形参为undefined,因此默认情况下,yield表达式的返回值为undefined

    function* method() {
        let info = yield 1;
        console.log(info);
        info = yield 2;
        console.log(info);
    }
    
    const g = method();
    
    g.next();		// 第一次调用生成器的next方法时传递参数无任何效果
    g.next();		// 控制台输出【undefined】
    g.next();		// 控制台输出【undefined】
    
  3. 在生成器函数内部,可以调用其他生成器函数,但调用时要加上yield *

    调用其他生成器函数时,只会得到其他生成器函数的next方法返回的done为false的对象

    function *test1(){
        yield "a";
        yield "b";
        return "c";
    }
    
    function *test2(){
        yield 1;
        yield *test1();
        yield 2;
        return 3;
    }
    
    const g = test2();
    
    console.log(g.next());		// { value: 1, done: false }
    console.log(g.next());		// { value: "a", done: false }
    console.log(g.next());		// { value: "b", done: false }
    console.log(g.next());		// { value: 2, done: false }
    console.log(g.next());		// { value: 3, done: true }
    

生成器的其他API

  1. return方法

    调用该方法,可以提前结束生成器函数

    function* method(){
        yield 1;
        yield 2;
        yield 3;
        return 4;
    }
    
    const g = method();
    
    console.log(g.next());			// { value: 1, done: false }
    console.log(g.return());		// { value: undefined, done: true }
    console.log(g.next());			// { value: undefined, done: true }
    console.log(g.next());			// { value: undefined, done: true }
    

    注意:return()是直接让函数结束,并不是让函数执行return语句

  2. throw方法

    调用该方法,可以在生成器函数中产生一个错误

异步任务控制

Promise刚发布时,async和await还没有出现,所以当时人们使用生成器函数来模拟如今的async和await的功能

而async和await也是基于此才诞生出来的

function* task() {
    const resp = yield fetch(...);
    const body = yield resp.json();
    console.log(body);
}

function run(generatorFunc) {
    const generator = generatorFunc();
    let result = generator.next();
    handleResult();
    function handleResult() {
        if (result.done) {
            return;
        }
        if (isPromise(result.value)) {
            result.value.then((data) => {
                result = generator.next(data);
                handleResult();
            });
        } else {
            result = generator.next(result.value);
            handleResult();
        }
    }
}

run(task);

扩展

babel对async和await的转换:

原始内容:

function A(val){
    return new Promise((resolve)=>{
        setTimeout(()=>{
            resolve(val);
        }, 1000);
    });
}

async function B(){
    var b = await A(3);
    var c = await A(4);
    return b + c;
}

B().then((res)=>{
	console.log(res);
});

转换后的结果(简化版):

function A(val){
    return new Promise((resolve)=>{
        setTimeout(()=>{
            resolve(val);
        }, 1000);
    });
}

function B() {
    function* _B() {
        var b = yield A(3);
        var c = yield A(4);
        return b + c;
    }

    function asyncToGenerator(gen, resolve, reject, num) {
        try {
            var info = gen.next(num);
            var val = info.value;
        } catch (err) {
            reject(err);
            return;
        }
        if (info.done) {
            resolve(val);
            return;
        } else {
            Promise.resolve(val).then((data) => {
                asyncToGenerator(gen, resolve, reject, data);
            });
        }
    }

    var gen = _B();

    return new Promise((resolve, reject) => {
        asyncToGenerator(gen, resolve, reject, undefined);
    });
}

B().then((res) => {
    console.log(res);
});