JS高阶(六)Iterator迭代器/Generator和Await原理

119 阅读5分钟

Iterator迭代器

ES6新增的内容

let/const/class、ES6Module模块规范(import export)、Symbol/BigInt、箭头函数、解构赋值、...、数组和对象以及正则新增了一些方法、模板字符串、Set/Map...

Promise、Proxy、Reflect、Iterator、Generator...

Iterator迭代器

只是一种机制,可以为各种不同的数据结构提供统一的循环和迭代规范,而for of循环本身就是基于这种进制进行迭代的!

  • 拥有 Iterator 机制的对象,必须具备 next 方法:执行这个方法可以依次访问对象中的每个成员
  • 执行next返回值是一个对象:
    • value:当前迭代的这一项
    • done:记录是否迭代完成
class Iterator {
    constructor(assemble) {
        let self = this;
        self.assemble = assemble;
        self.index = 0;
    }
    next() {
        let self = this,
            assemble = self.assemble;
        if (self.index > assemble.length - 1) {
            return {
                value: undefined,
                done: true
            };
        }
        return {
            value: assemble[self.index++],
            done: false
        };
    }
}
let itor = new Iterator([10, 20, 30, 40]);
console.log(itor.next()); //->{value:10,done:false}
console.log(itor.next()); //->{value:20,done:false}
console.log(itor.next()); //->{value:30,done:false}
console.log(itor.next()); //->{value:40,done:false}
console.log(itor.next()); //->{value:undefined,done:true} 

虽然不具备 Iterator 内置类,但是对于某些数据结构,它提供了 Symbol.iterator 属性方法,这个方法具备迭代器规范,基于这个方法可以依次迭代数据中的每一项

  • 数组 Array.prototype[Symbol.iterator]=function...
  • Set.prototype / Map.prototype
  • String.prototype
  • NodeList.prototype
  • arguments[Symbol.iterator]
  • generator object
  • ...

把Symbol.iterator方法执行,会返回一个具备迭代器规范的itor对象,基于itor.next()依次执行,就可以获取到数据集合中的每一项!!

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

ES6中新提供的“for...of”循环,就是按照这套机制去迭代的!!

  • 1.先去找 arr[Symbol.iterator] 执行(如果数据集合不具备这个属性,就会报错「Uncaught TypeError: xxx is not iterable」),返回一个迭代器对象 itor

  • 2.每一轮循环都会执行一次 itor.next() 方法,把结果中的value赋值给循环中的value;当done为true时,则结束整个循环!!

let arr = [10, 20, 30];
arr[Symbol.iterator] = function () {
    let self = this,
        index = 0;
    return {
        next() {
            if (index > self.length - 1) {
                return {
                    value: undefined,
                    done: true
                };
            }
            let result = {
                value: self[index],
                done: false
            };
            index += 2;
            return result;
        }
    };
};
for (let value of arr) {
    console.log(value);
}

Object.prototype上不具备Symbol.iterator,一个普通对象默认不能基于迭代器规范循环,也就是不能使用for/of;但是可通过在原型上添加Symbol.iterator来实现迭代器规范

Object.prototype[Symbol.iterator] = function () {
    let self = this,
        keys = Reflect.ownKeys(self),
        index = 0;
    return {
        next() {
            if (index >= keys.length) {
                return {
                    value: undefined,
                    done: true
                };
            }
            return {
                value: self[keys[index++]],
                done: false
            };
        }
    };
};
let obj = { name: 'zhufeng', age: 13, lx: 0 };
for (let value of obj) {
    console.log(value);
} 

类数组可直接使用Array.prototype[Symbol.iterator],不需再进行重写

let obj = {
    0: 10,
    1: 20,
    2: 30,
    length: 3
};
obj[Symbol.iterator] = Array.prototype[Symbol.iterator];
for (let value of obj) {
    console.log(value);
} 

JS中常用的循环

  • for循环、while循环、Array.prototype.forEach可以迭代数组
  • for of循环 「扩充:for await of」
  • for in循环 [for in循环是无法迭代symbol类型的私有属性]

性能从高到低:

for循环(i是私有的) -> for循环(i是全局的) && while循环」-> forEach循环(内部封装的方法,肯定做了一些逻辑判断处理,所以要稍微比命令式编程慢一些) -> for/of循环 -> for/in循环(它会依次查找私有和原型链上所有可枚举的属性,所以比其他循环慢很多,项目中不建议使用for/in)

console.time('FOR');
for (var i = 0; i < arr.length; i++) { }
console.timeEnd('FOR');

console.time('FOR2');
let m = 0;
for (; m < arr.length; m++) { }
console.timeEnd('FOR2');

console.time('WHILE');
let k = 0;
while (k < arr.length) { k++; }
console.timeEnd('WHILE');

console.time('FOREACH');
arr.forEach(() => { });
console.timeEnd('FOREACH');

console.time('FOROF');
for (let value of arr) { }
console.timeEnd('FOROF');

console.time('FORIN');
for (let key in arr) { }
console.timeEnd('FORIN');

密集数组 [10,20,30,40]

稀疏数组 {1:20,2:30,length:6}

forEach迭代的时候 对于稀疏数组,只迭代有内容的,例如:第二个数组只迭代两次


Generator和Await原理

Generator生成器函数 function* 函数名(){}

fn(10,20)

  • 函数体中的代码并没有执行「实参值已经预先传递进去了」
  • 返回一个具备迭代器规范的对象itor:next/return/throw

itor -> fn.prototype -> GeneratorFunction.prototype(next/return/throw) -> 原型对象(Symbol.iterator) -> Object.prototype

执行itor.next()才会执行函数体中的代码

  • 把之前预先传递的实参赋值给形参变量
  • this->window
  • 遇到 yeild 或者 return 结束
  • 返回的结果是一个对象 {value:yeild或者return后的值,done:false/true}
function* fn(x, y) {
    console.log(x, y);
    console.log(this);
    return 10;
}
fn.prototype.x = 100;
let itor = fn(10, 20);
console.log(itor.next()); //{value:10,done:true}
function* generator() {
    console.log('A');
    yield 10;
    console.log('B');
    yield 20;
    console.log('D');
    return 100;
}
let itor = generator();
console.log(itor.next()); //输出A {value:10,done:false}
console.log(itor.next()); //输出B {value:20,done:false}
console.log(itor.next()); //输出D {value:100,done:true}
console.log(itor.next()); // {value:undefined,done:true} 

next/throw/return

function* generator() {
    yield 10;
    yield 20;
    yield 30;
}
let itor = generator();
console.log(itor.next()); //{value:10,done:false}
// console.log(itor.throw('异常原因')); //手动抛出异常,结束函数中的代码执行
// console.log(itor.return(100)); //{value:100,done:true} 遇到return则结束函数执行,done设置为true
console.log(itor.next()); //{value:undefined,done:true}

next传值

function* generator() {
    let res = yield 10;
    console.log(res);

    res = yield 20;
    console.log(res);

    res = yield 30;
    console.log(res);
}
let itor = generator();
console.log(itor.next(100)); //{value:10,done:false}  第一次执行next传递的值没有用
console.log(itor.next(200)); //把200赋值给第一个res 200 {value:20,done:false}  每一次next传递的值,会做为上一次yeild执行的返回结果
console.log(itor.next(300)); //把300赋值给第二个res 300 {value:30,done:false}
console.log(itor.next(400)); //把400赋值给第三个res 400 {value:undefined,done:true}

yield 后面为 Generator生成器函数

function* generator1() {
    yield 10;
    yield 20;
}
function* generator2() {
    yield 30;
    yield* generator1(); //yeild* 可以保证代码执行到这的时候,进入到新的generator函数中去执行
    yield 40;
}
let itor = generator2();
console.log(itor.next()); //value:30  done:false
console.log(itor.next()); //value:10  done:false
console.log(itor.next()); //value:20  done:false
console.log(itor.next()); //value:40  done:false
console.log(itor.next()); //value:undefined  done:true

需求:串行发送三个请求,请求时间分别是 1000/2000/3000

const query = interval => {
    return new Promise(resolve => {
        setTimeout(() => {
            resolve(interval);
        }, interval);
    });
};

query(1000).then(value => {
    console.log('第一个成功:', value);
    return query(2000);
}).then(value => {
    console.log('第二个成功:', value);
    return query(3000);
}).then(value => {
    console.log('第三个成功:', value);
});
function* generator() {
    let value = yield query(1000);
    console.log('第一个成功:', value);

    value = yield Promise.reject('xxx');
    console.log('第二个成功:', value);

    value = yield query(3000);
    console.log('第三个成功:', value);
})

let itor = generator();
// itor.next(); //value:第一次请求的promise实例,接下来我们需要等待本次请求成功,获取到结果后,在去执行下一次的next方法
itor.next().value.then(val => {
    // val:第一次请求成功的结果
    itor.next(val).value.then(val => {
        // val:第二次请求成功的结果
        itor.next(val).value.then(val => {
            // val:第三次请求成功的结果
            itor.next(val);
        });
    });
});
const isPromise = function isPromise(x) {
    if (x !== null && /^(object|function)$/i.test(typeof x)) {
        var then;
        try {
            then = x.then;
        } catch (_) {
            return false;
        }
        if (typeof then === "function") return true;
    }
    return false;
};
// co.js
function AsyncFunction(generator, ...params) {
    return new Promise((resolve, reject) => {
        let itor = generator(...params);
        const next = val => {
            let { value, done } = itor.next(val);
            if (done) {
                resolve(value);
                return;
            }
            if (!isPromise(value)) value = Promise.resolve(value);
            value.then(val => next(val))
                .catch(reason => {
                    reject(reason);
                    itor.throw(reason);
                });
        };
        next();
    });
}
AsyncFunction(function* () {
    let value = yield query(1000);
    console.log('第一个成功:', value);

    value = yield Promise.reject('xxx');
    console.log('第二个成功:', value);

    value = yield query(3000);
    console.log('第三个成功:', value);
}).then(value => {
    console.log('请求都成功:', value);
}).catch(reason => {
    console.log('某次请求失败:', reason);
});
// async await 是 generator+promise 的“语法糖”
(async () => {
    let result = await query(1000);
    console.log(`第一个请求成功,结果是:${result}`);

    result = await query(2000);
    console.log(`第二个请求成功,结果是:${result}`);

    result = await query(3000);
    console.log(`第三个请求成功,结果是:${result}`);
})();