# 遍历器(迭代器)Iterator

# for遍历

首先从远古讲起,刚出js的时候如何遍历一个数组呢

var arr = [1, 2, 3, 4, 7, 8, 9]
for (let i = 0; i < arr.length; i++) {
    console.log(arr[i]);
}

# forEach遍历

看起来笨的一批,所以ES5给你研究了一个foreach方法,但是这个方法不能break,也不能改变数组自身的值,也不能返回任何值。

var arr = [1, 2, 3, 4, 7, 8, 9];
var arr2 = arr.forEach((element, index) => {
    console.log(`${index}个数值为${element}`);
    return element * 2;
})
console.log(arr2); // undefined
console.log(arr); // [1, 2,3,4, 8 , 9]

所以说foreach只给你最基本的操作,其他一概不管,如果你想要改变自身的值或者有break和countinue操作我们可以使用map操作,不展开说了。数组遍历方法总结 (opens new window)

# for-in遍历

那么ES6专门为遍历数组提供了一种方法,就是for-of。说到for-of,不得不提到for-in

那么关于他们两的区别,深入理解枚举属性与for-in和for-of (opens new window)

值得一提的是for-in是可以遍历数组的,但是不推荐用for-in遍历数组,为什么呢?因为for-in返回的可枚举属性是字符类型,不是数字类型,如果'0', '1'这样的属性和1,2数组发生相加,很可能不是直接相加,二十字符串的叠加。例如:

const items = [1,2, 3,4];
for (item in items) {
    let tempitem = item + 1;
    console.log(items[tempitem]); // undefined
    console.log(tempitem); // 01 21, 31 41 item与数字相加,会得到字符串相加的结果
}

所以为了便面歧义,还是不要用for-in遍历数组比较好

# for-of

接下来进入正题了,因为for-in在数组这边比较难用,所以ES6新添加的for-of来弥补for-in的不足。这个是正儿八经遍历数组的方法。与forEach()不同的是,它支持break、continue和return语句.而且他本身的语法非常简单

for (variable of iterable) {
    // statements
}
  • variable: 在每次迭代中,将不同属性的值分配给变量
  • iterable: 被迭代枚举其属性的对象。

而且关键的问题是for-of不仅可以遍历数组,他也可以遍历很多类似数组对象。

  • Array
  • Map
  • Set
  • String
  • TypedArray
  • 函数的arguments对象
  • NodeList对象

而他的原理在于这些类数组对象中都有一个属性,就是Symbol.iterator,也就是说,只要带Symbol.iterator他都能遍历,我们单独把这个属性拿出来,自己手动执行next()方法就会看到我们成功遍历了这个数组

const items = [1,2, 3,4];
const giao = items[Symbol.iterator]();
console.log(giao.next()); // {value: 1, done: false};
console.log(giao.next()); // {value: 2, done: false};
console.log(giao.next()); // {value: 3, done: false};
console.log(giao.next()); // {value: 4, done: false};
console.log(giao.next()); // {value: undefined, done: true};

同理,我们可以通过手动写一个iterator来更深入的了解他的原理:

Array.prototype.myiterator = function() {
    let i = 0;
    let items = this;
    return {
        next() {
            const done = i >= item.length;
            const value = done ? undefined : items[i++]
            return {
                value, 
                done
            }
        }
    }
}
const item = [1,2,3,4];
 
// 控制台
const giao = item.myiterator(); // 当我们获得遍历器时,我们只需要代替for-of执行myiterator即可遍历这个数组
giao.next(); // {value: 1, done: false}
giao.next(); // {value: 2, done: false}
giao.next(); // {value: 3, done: false}
giao.next(); // {value: 4, done: false}
giao.next(); // {value: undefined, done: true}

效果更for of 一样。另外值得注意的是,你可以在任意对象里添加这个属性,让他们可遍历。

const items = ['blue', 'yellow', 'white', 'black'];
for(item of items) {
    console.log(item);
}

# 总结

遍历器如果存在一个对象内,它就可以让这个对象可供for-of遍历,for-of的遍历方法就是不停的调用遍历器的next()方法,直到done属性变为true。

快速掌握ES6 iterator Generator和async 之间的关系及其用法 (opens new window)

# 生成器Generator

本质上,生成器函数返回的就是一个遍历器

生成器的语法很简单,就是在function后面加个*,然后用yield来返回对应的值。(其实也可以将yield看做return,只不过需要next()来进行外调用,还有一个函数只能由一个return,而yield可以有多个)

function* items() {
    yield '1';
    yield '2';
    yield '3'
}
const num = items();

// 控制台
num.next(); // {value: '1', done: false}
num.next(); // {value: '2', done: false}
num.next(); // {value: '3', done: false}
num.next(); // {value: undefined, done: true}
num.next(); // {value: undefined, done: true}

那么我们yield的之间同样也可以加入运算

function* items() {
    let i = 0;
    yield i; // 0
    i++;
    yield i // 1
    i++
    yield i // 2
    i++ // 这个就不运行了,因为他在yield之后
}
const num = items();
// 不用浏览器控制台,直接打印也行
console.log(nu.next()); // {value: 0, done: false}
console.log(nu.next()); // {value: 1, done: false}
console.log(nu.next()); // {value: 2, done: false}
console.log(nu.next()); // {value: undefined, done: true}

利用这样的特性,我们可以用Generator来进行ajax的等待操作

fuction ajax(url) {
    // 请求成功自动调用next()方法。然后返回数据结果
    axios.get(url).then(res => gen.next(res.data));
}
function* step() {
    const class = yield ajax.get('http://laotie.com/getclass');
    const score = yield ajax.get(`http://laotie.com/getscore?name=¥{class[0].name}`)
}
// 获得这个函数的遍历器
const gen = step();
// 启动'遍历器',不启动就不会东
gen.next(); // 获取class
gen.next(); // 获取到score

因为第二个get请求依赖第一个请求的结果,所以我们解决办法第一个运用Promise的回调来限制他们的先后顺序。但是在我们学习了生成器之后发现生成器很适合做这样的事,也就是只有当第一个请求执行完之后,才能顺序执行第二个请求。

另外还有一些小的特性

*可以添加到任意位置,都不会影响genterator。下面的下发都是可以的

function * foo(x, y) {...}
function *foo(x, y) {...}
function* foo(x, y) {...}
function*foo(x, y) {...}

关于Generator的Thunk或者co模块,因为ES8的async的加入,极大简化了Generator的操作

# async

# 语法

准确的说,async就是Generator的语法糖,首先看他的语法

async function laotie() {
    const data = await dosomething();
}

可以看到,

  • 原来的*由async代替
  • 原来的yield由await代替 这样做的直接好处就是更加语义化,可读性更强。但是其实async做到的远不止如此。
  1. 首先第一点,就是async不用不断执行next()了,async函数内置了执行器,使的我们在调用函数时,只需要直接调用就可以
// 接上一个代码块
laotie()
  1. 现在async的await还是保留这等待的功能,但是因为没有了next(),所以在调用await不会像yield那样返回值了。在async中,只有return返回,而且返回的是一个promise对象.

拿上面的代码直接改成async加await格式

async function items() {
    let i = 0; 
    await i;
    i++;
    await i;
    i++;
    await i;
    i++
}
console.log(items()) // Promise{<pending>}

直接调用方法我们能看到返回的是一个状态为resolved的Promise对象,而不是Iterator.

而这个对象,返回的值就是函数里return出来的值。我们可以用then()函数来接受这个值并答应它。

async function items() {
    let i = 3;
    return i ;
}
items().then(res => {
    console.log(res); // 3
})

当然这么举例子准定不是正经的用法,这些例子主要用于区分Generator和async函数之间的区别。

# 用法

正确的用法现在是在await之后加入一个异步函数, await相当于将这个异步函数转化为同步函数,等这个异步函数执行完毕返回resolved的时候才往下执行进一步的操作:例如:

async function asyncPrint(value, ms) {
    await new Promise(resolve => {
        setTimeout(resoove, ms);
    })
    console.log(value);
}
asyncPrint('hello world', 1000); // 疫苗打印除hello world

如果这个async的函数中间有多个await,那么就让多个await以排队的方式执行。

用法2

先让我们把之前generator的例子拿过来

function ajax(url) {
    // 请求成功自动调用next()方法。然后返回数据结果
    axios.get(url).then(res => gen.next(res.data))
}
function* step() {
    const class = yield ajax.get('http://laotie.com/getClass');
    const score = yield ajax.get(`http://laotie.com/getscore?name=${class[0].name}`);
}

// 获得这个函数的遍历器
const gen = step();
// 启动遍历器
gen.next();

写着挺累的,但是async可以快速的简化它。因为await接受的就是一个Promise函数,所以我们可以直接在await后面使用axios,然后直接使用对象解构赋值获取相应的值。

async function step() {
    const {data: {class}} = await axios.get(`http: //laotiecom/getclass`);
    cosnt {data: {core}} = await axios.get(`http://laotie.com/getscore?name=${class[0].name}`);
    return {class, sore}
}

对象可以设置Symbol.iterator

let iterable = {
    data: {a: 1, b: 2, c: 3}, 
    [Symbol.iterator]() {
        const self = this;
        let index = 0;
        return {
            next() {
                const arr = Object.keys(self.data);
                done = index >= arr.length;
                value = done ? undefined : self.data[arr[index++]];
                return {
                    value,
                    done,
                }
            }
        }
    }
}
for (let item of iterable) {
    console.log(item);
}

// 二
var obj = {
    a: 1, 
    b: 1,
    [Symbol.iterator]: function() {
        console.log(123);
        return {
            next() {
                return {
                    value: 1,
                    done: true
                }
            }
        }
    }
}

var obj = {
    a: 1, 
    b: 1,
    [Symbol.iterator]: function* () {
        yield '1';
        yield '2';
        yield '3'
    }
}