ES6 迭代器教程

301 阅读3分钟

原文:  A Simple Guide to ES6 Iterators in JavaScript with Examples

迭代器是ES6提供的对数据集进行遍历的一种新方式

简介

我们有如下的数组:

const myFavouriteAuthors = [
    'Neal Stephenson',  'Arthur Clarke',  'Isaac Asimov',   'Robert Heinlein'
];

要获取数组中的元素,有很多种方式,例如for循环、while判断、for-of迭代。

如果考虑一个对象,该对象包含一个新对象allAuthors,allAuthors包含上面提到的数组:

const myFavouriteAuthors = {
    allAuthors: {
        fiction: [ 'Agatha', 'Dr. Seuss' ],
        science: [ 'Neal Stephenson',  'Arthur Clarke',  'Isaac Asimov',   'Robert Heinlein' ]
    }
};

这个时候如何对该对象进行迭代?采用下面的方法会报错:

for (let author of myFavouriteAuthors) { 
    console.log(author)
}// TypeError: {} is not iterable

可迭代和迭代器

我们给该对象定义一个新方法,来获取所有allAuthors包含的数组:

const myFavouriteAuthors = {
    allAuthors: {
        fiction: [ 'Agatha', 'Dr. Seuss' ],
        science: [ 'Neal Stephenson',  'Arthur Clarke',  'Isaac Asimov',   'Robert Heinlein' ]
    },
    getAllAuthors() {
       const authors = []
    
       for(const author of this. allAuthors.fiction) {
          authors.push(author)
       }
    
       for(const author of this. allAuthors.science) {
          authors.push(author)
       }
    
       return authors
    }
};

这样通过getAllAuthors我们就可以获取所有authors了,但是这样做有几个问题:

  • getAllAuthors 这个函数名不具有代表性,我也可以命名为retriveAllAuthors
  • getAllAuthors 返回的数据格式也不具有代表性,在该例子中返回的是一个数组,当然我也可以修改代码返回以下格式: 

[ {name: 'Agatha Christie'}, {name: 'J. K. Rowling'}, ... ]

那么要如何设定一个规则,保证方法名称和返回的数据格式是确定且不可改变的?ES6新增了一种原始数据格式 Symbol ,通过 Symbol() 产生的值都是唯一的,可以作为对象的属性。同时,Symbol.iterator 会生成一个迭代器,该迭代器有 next 方法,next 方法会返回 value 和 done.


  • 可迭代的数据结构是指外部可以获取它内部元素的数据结构,实现的方法是通过提供 Symbol.iterator 方法,这个方法是迭代器的构造函数,可以生成迭代器。
  • 迭代器是一个可以遍历数据结构元素的指针。

让对象可迭代

为了让对象可迭代,我们需要实现 Symbol.iterator 这个方法,这里用到了对象的计算属性。

const iterable = {  [Symbol.iterator]() {    
    let step = 0    
    const iterator = {      
        next() {        
            step += 1                
            if(step === 1) {          
                return { value: "This", done: false }        
            } else if(step === 2){          
                return { value: "is", done: false }        
            } else if(step === 3) {          
                return { value: "iterable", done: false }        
            }        
            return { value: undefined, done: true }      
        }    
    }    
    return iterator  
}}

let iterator = iterable[Symbol.iterator]
iterator.next() // { value: "This", done: false }
iterator.next() // { value: "is", done: false }
iterator.next() // { value: "iterable", done: false }
iterator.next() // { value: undefined, done: true }

上面的代码大致就是 for-of 的内部实现原理,创建一个迭代器后循环调用 next 方法直到返回的 done 为 true.

JS中可迭代的数据结构

  • 数组和类数组
  • 字符串
  • Maps
  • Sets

实践出真知

数组解构

const array = ['a', 'b', 'c', 'd', 'e'];const [first, ,third, ,last] = array;

等同于

const array = ['a', 'b', 'c', 'd', 'e'];
const iterator = array[Symbol.iterator]();
const first = iterator.next().valueiterator.next().value // Since it was skipped, so it's not assigned
const third = iterator.next().valueiterator.next().value // Since it was skipped, so it's not assigned
const last = iterator.next().value
扩展运算符

const array = ['a', 'b', 'c', 'd', 'e'];
const newArray = [1, ...array, 2, 3];

下面代码同样可以实现:

const array = ['a', 'b', 'c', 'd', 'e'];
const iterator = array[Symbol.iterator]();
const newArray = [1];
for (let nextValue = iterator.next(); nextValue.done !== true; 
nextValue = iterator.next()) {  
    newArray.push(nextValue.value);
}
newArray.push(2)
newArray.push(3)

 myFavoriteAuthors 定义迭代函数

const myFavouriteAuthors = {  
    allAuthors: {    
        fiction: [      
            'Agatha Christie',       
            'J. K. Rowling',      
            'Dr. Seuss'    ],    
        scienceFiction: [      
            'Neal Stephenson',      
            'Arthur Clarke',      
            'Isaac Asimov',       
            'Robert Heinlein'    ],    
        fantasy: [      
            'J. R. R. Tolkien',      
            'J. K. Rowling',      
            'Terry Pratchett'    ],  
    },  
    [Symbol.iterator]() {    
        // 获取一个嵌套数组 [[],[]]   
        const genres = Object.values(this.allAuthors);  
      
        // 初始化嵌套数组下标和子数组下标  
        let currentAuthorIndex = 0;    
        let currentGenreIndex = 0;        

        return {      
            // 定义 next 方法    
            next() {        
                // 获取当前子数组      
                const authors = genres[currentGenreIndex];     
                   
                // 根据子数组长度判断该子数组是否完成迭代       
                const doNothaveMoreAuthors = !(currentAuthorIndex < authors.length);        
                
                if (doNothaveMoreAuthors) {          
                    // 数组下标+1        
                    currentGenreIndex++;          

                    // 子数组下标归零          
                    currentAuthorIndex = 0;        
                 }                
        
                // 根据数组长度判断是否迭代完成       
                const doNotHaveMoreGenres = !(currentGenreIndex < genres.length); 
       
                if (doNotHaveMoreGenres) {          
                    // 是则返回 done 为 true         
                    return { value: undefined, done: true };        
                }                
                       
                // 返回当前子数组下标对应的值       
                // 子数组下标+1       
                return {          
                    value: genres[currentGenreIndex][currentAuthorIndex++],          
                    done: false        
                }     
            }    
    };  
}};

for (const author of myFavouriteAuthors) {  console.log(author);}

console.log(...myFavouriteAuthors)

通过以上知识,你大致可以了解 ES6 新增的 iterator 是如何工作的,接下来就可以愉快地学习 generator 生成器了,这位博主同样有一篇讲述 generator 工作原理的,稍后我也会翻译并做整理,与大家共享~