function *iteratorMother() {
yield'we';
yield'are';
yield'the BlackGold team!';
}
let iterator = iteratorMother();
for (let element of iterator) {
console.log(element);
}
// we// are// the BlackGold team!for (let element of iterator) {
console.log(element);
}
// nothing to be printed// 这个时候迭代器iterator已经完成他的使命,如果想要再次迭代,应该生成另一个迭代器对象以进行遍历操作复制代码
function *iteratorMother() {
let a = yield'we';
let b = yield a + ' ' + 'are';
yield b + ' ' + 'the BlackGold team!';
}
let iterator = iteratorMother();
for (let element of iterator) {
console.log(element);
}
// we// undefined are// undefined the BlackGold team!复制代码
function *iteratorMother() {
let a = yield'we';
let b = yield a + ' ' + 'are';
yield b + ' ' + 'the BlackGold team!';
}
let iterator = iteratorMother(),
first, second, third;
// 第一次调用next方法时,传入的参数将不起任何作用
first = iterator.next('anything,even an Error instance');
console.log(first.value); // we
second = iterator.next(first.value);
console.log(second.value); // we are
third = iterator.next(second.value);
console.log(third.value); // we are the BlackGold team!复制代码
let asyncFuncWrapper = (asyncFunction, param) => {
returnnewPromise((resolve, reject) => {
asyncFunction(param, data => {
resolve(data);
});
});
},
asyncFunc = (param, callback) => setTimeout(() => callback(param), 1000);
asyncfunctionasyncFuncRunner() {
let a = await asyncFuncWrapper(asyncFunc, 1);
a += 1;
let b = await asyncFuncWrapper(asyncFunc, a);
b += 1;
let c = await asyncFuncWrapper(asyncFunc, b);
let d = await c + 1;
return d;
}
asyncFuncRunner().then(data =>console.log(data)); // 三秒后输出 4复制代码
委托生成器
在这个讲求DRY(Don't Repeat Yourself)的时代,生成器也可以进行复用。
function *iteratorMother() {
yield'we';
yield'are';
}
function *anotherIteratorMother() {
yield'the BlackGold team!';
yield'get off work now!!!!!!';
}
function *theLastIteratorMother() {
yield *iteratorMother();
yield *anotherIteratorMother();
}
let iterator = theLastIteratorMother();
for (let key of iterator) {
console.log(key);
}
// we// are// the BlackGold team!// get off work now!!!!!!复制代码
let arr = newArray(10 * 1000 * 1000).fill({ test: 1 });
console.time();
for (let i = 0, len = arr.length; i < len; i++) {}
console.timeEnd();
console.time();
for (let i in arr) {}
console.timeEnd();
console.time();
for (let i of arr) {}
console.timeEnd();
console.time();
arr.forEach(() => {});
console.timeEnd();
复制代码
JavaScript 遍历、枚举与迭代的骚操作
前言
[JavaScript 遍历、枚举与迭代的骚操作(上篇)](JavaScript 遍历、枚举与迭代的骚操作(上篇))总结了一些常用对象的遍历方法,大部分情况下是可以满足工作需求的。但下篇介绍的内容,在工作中95%的情况下是用不到的,仅限装逼。俗话说:装得逼多必翻车!若本文有翻车现场,请轻喷。
ES6 迭代器(iterator)、生成器(generator)
上一篇提到,for of循环是依靠对象的迭代器工作的,如果用for of循环遍历一个非可迭代对象(即无默认迭代器的对象),for of循环就会报错。那迭代器到底是何方神圣?
迭代器是一种特殊的对象,其有一个next方法,每一次枚举(for of每循环一次)都会调用此方法一次,且返回一个对象,此对象包含两个值:
生成器,顾名思义,就是迭代器他妈;生成器是返回迭代器的特殊函数,迭代器由生成器生成。
生成器声明方式跟普通函数相似,仅在函数名前面加一个号(号左右有空格也是可以正确运行的,但为了代码可读性,建议左边留空格,右边不留);函数内部使用yield关键字指定每次迭代返回值。
上面的例子展示声明了一个生成器函数iteratorMother的方式,调用此函数返回一个迭代器iterator。
yield是ES6中的关键字,它指定了iterator对象每一次调用next方法时返回的值。如第一个yield关键字后面的字符串"we"即为iterator对象第一次调用next方法返回的值,以此类推,直到所有的yield语句执行完毕。
注意:当yield语句执行完毕后,调用iterator.next()会一直返回{ value: undefined, done: true },so,别用for of循环遍历同一个迭代器两次
注意:可以指定生成器的返回值,当运行到return语句时,无论后面的代码是否有yield关键字都不会再执行;且返回值只返回一次,再次调用next方法也只是返回{ value: undefined, done: true }
注意third time:yield关键字仅可在生成器函数内部使用,一旦在生成器外使用(包括在生成器内部的函数例使用)就会报错,so,使用时注意别跨越函数边界
上面的例子,在JavaScript引擎进行函数声明提升的时候就报错了,而非在实例化一个迭代器实例的时候才报错。
注意fourth time:别尝试在生成器内部获取yield指定的返回值,否则会得到一个undefined
为对象添加生成器
使用for of循环去遍历一个对象的时候,会先去寻找此对象有没有生成器,若有则使用其默认的生成器生成一个迭代器,然后遍历此迭代器;若无,报错!
上篇也提到,像Set、Map、Array等特殊的对象类型,都有多个生成器,但是自定义的对象是没有内置生成器的,不知道为啥;就跟别人有女朋友而我没有女朋友一样,不知道为啥。没关系,自己动手,丰衣足食;我们为自定义对象添加一个生成器(至于怎么解决女朋友的问题,别问我)
好吧,我承认上面的例子有点脱了裤子放P的味道,当然不是说这个例子臭,而是有点多余;毕竟我们希望遍历的是对象的属性,那就换个方式搞一下吧
通过上面例子的展示的方式包装对象,确实可以使用for of来遍历对象的属性,但是使用起来还是有点点的麻烦,目前没有较好的解决办法。我们在创建自定义的类(构造器)的时候,可以加上Symbol.iterator生成器,那么类的实例就可以使用for of循环遍历了。
迭代器传值
上面提到过,如果在迭代器内部获取yield指定的返回值,将会得到一个undefined,但代码逻辑如果依赖前面的返回值的话,就需要通过给迭代器的next方法传参达到此目的
往next方法传的参数,将会成为上一次调用next对应的yield关键字的返回值,在生成器内部可以获得此值。所以调用next方法时,会执行对应yield关键字右侧至上一个yield关键字左侧的代码块;生成器内部变量a的声明和赋值是在第二次调用next方法的时候进行的。
解决回调地狱
每当面试时问到如何解决回调地狱问题时,我们的第一反应应该是使用Promise对象;如果你是大牛,可以随手甩面试官Promise的实现原理;但是万一不了解Promise原理,又想装个逼,可以试试使用迭代器解决回调地狱问题
上面的例子中,使用setTimeout来模拟一个异步函数asyncFunc,此异步函数接受两个参数:param和回调函数callback;在生成器内部,每一个yield关键字返回的值都为一个包装了异步函数的函数,用于往异步函数传入参数;执行迭代器的函数iteratorRunner,用于循环执行迭代器,并运行迭代器返回的函数。最后,我们可以在匿名生成器里面以同步的方式处理我们的代码逻辑。
以上的方式虽然解决了回调地狱的问题,但本质上依然是使用回调的方式调用代码,只是换了代码的组织方式。生成器内部的代码组织方式,有点类似ES7的async、await语法;所不同的是,async函数可以返回一个promise对象,搬砖工作者可以继续使用此promise对象以同步方式调用异步函数。
委托生成器
在这个讲求DRY(Don't Repeat Yourself)的时代,生成器也可以进行复用。
上面的例子中,生成器theLastIteratorMother定义里面,复用了生成器iteratorMother、anotherIteratorMother两个生成器,相当于在生成器theLastIteratorMother内部声明了两个相关的迭代器,然后进行迭代。需要注意的是,复用生成器是,yield关键字后面有星号。
几个循环语句性能
上一篇有小伙伴提到对比一下遍历方法的性能,我这边简单对比一下各个循环遍历数组的性能,测试数组长度为1000万,测试代码如下:
结果如下图(单位为ms,不考虑IE): 以上的结果可能在不同的环境下略有差异,但是基本可以说明,原生的循环速度最快,forEach次之,for of循环再次之,forin循环又次之。其实,如果数据量不大,遍历的方法基本不会成为性能的瓶颈,考虑如何减少循环遍历或许更实际一点。
总结
含泪写完这一篇,我要下班了,再见各位。