JS中的迭代器

85 阅读2分钟

什么是迭代器

迭代器(iterator),是确使用户可在容器对象(container,例如链表或数组)上遍访的对象,使用该接口无需关心对象的内部实现细节。

从迭代器的定义我们可以看出来,迭代器是帮助我们对某个数据结构进行遍历的对象。

迭代器协议

在JavaScript中,迭代器也是一个具体的对象,这个对象需要符合迭代器协议(iterator protocol)。

迭代器协议定义了产生一系列值(无论是有限还是无限个)的标准方式。

那么在js中这个标准就是一个特定的next方法。

next方法的要求

一个无参数或者一个参数的函数,返回一个应当拥有以下两个属性的对象。

  • done(boolean)

    • 如果迭代器可以产生序列中的下一个值,则为 false。(这等价于没有指定 done 这个属性。)
    • 如果迭代器已将序列迭代完毕,则为 true。这种情况下,value 是可选的,如果它依然存在,即为迭代结束之后的默认返回值。
  • value

    • 迭代器返回的任何 JavaScript 值。done 为 true 时可省略。

迭代器代码演示

一个简单的迭代器对象

const iterator = {
  next(){
    return {
      done:true,
      value:'123'
    }
  }
}

我们可以创建一个迭代器对象来访问数组。我们可以通过next()方法来访问迭代器。直到数组中所有的元素全部被迭代完了,调用next()方法会返回{ done: true, value: undefined }

const names = ["abc","cba","ccc"]
let index = 0
const namesIterator = {
  next(){
    if(index < names.length){
      return {done:false,value:names[index++]}
    }else{
      return {done:true, value:undefined}
    }
  }
}

console.log(namesIterator.next());//{ done: false, value: 'abc' }
console.log(namesIterator.next());//{ done: false, value: 'cba' }
console.log(namesIterator.next());//{ done: false, value: 'ccc' }
console.log(namesIterator.next());//{ done: true, value: undefined }

我们可以使用生成迭代器的函数来优化一下上面的代码。

function createArrayIterator(arr){
  let index = 0;
  return { //返回一个迭代器对象
    next(){
      if(index < arr.length){
        return {done:false, value : arr[index++]}
      }else{
        return {done:true, value : undefined}
      }
    }
  }
}

const names = ["abc","cba","ccc"]
let namesiterator = createArrayIterator(names)
console.log(namesiterator.next());
console.log(namesiterator.next());
console.log(namesiterator.next());
console.log(namesiterator.next());

可迭代对象

上面的代码我们整体上来看是十分奇怪的,比如我们获取一个数组的时候,需要自己创建一个index变量,再创建一个所谓的迭代器对象,事实上我们可以对上面的代码进行进一步的封装,让其变成一个可迭代对象。

什么是可迭代对象呢?

它和迭代器是不同的概念,当一个对象实现了iterable protocol协议时,它就是一个可迭代对象,这个对象的要求是必须实现 @@iterator 方法,在代码中我们使用 Symbol.iterator 访问该属性。

当我们要问一个问题,我们转成这样的一个东西有什么好处呢?

当一个对象变成一个可迭代对象的时候,进行某些迭代操作,比如 for...of 操作时,其实就会调用它的 @@iterator 方法。

区分迭代器和迭代器对象

迭代器是一个对象,符合迭代器协议iterable protocol

const iterator = {next(){ return {}}}

可迭代对象,当一个对象实现了iterable protocol协议,它就是一个可迭代对象。要求实现[Symboliterator]方法(函数)

const iterableObj = {[Symbol.iterator]}(){return 迭代器}

那么这个iterableObj就是一个可迭代对象了

迭代器对象代码演示

const iterableObj = {
  names : ["abc","cba","ccc"],
  [Symbol.iterator](){
    let index = 0;
    return {
      next:() =>{ //一定要是箭头函数
        if(index < this.names.length){ //iterator.next() 这样子这个对象里没有的,所以next得写箭头函数,这样它就会去上层作用域找
          //如果不写箭头函数,调用的时候就是返回的对象.next(),不是iterableObj了
          return {done:false,value:this.names[index++]}
        }else{
          return {done:true,value:undefined}
        }
      }
    }
  }
}

console.log(iterableObj[Symbol.iterator]); //[Function: [Symbol.iterator]]

//1.第一次调用
const iterator = iterableObj[Symbol.iterator]()
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());

// //2.第二次调用(不会有第一次调用的值)
// const iterator1 = iterableObj[Symbol.iterator]()
// console.log(iterator1.next());
// console.log(iterator1.next());
// console.log(iterator1.next());
// console.log(iterator1.next());

迭代器对象有什么用呢?

比如for of 它可以遍历的东西必须是一个可迭代对象。(for of本质就是用了迭代器的next())

//比如普通对象你就无法for of,它不是迭代对象
const obj = {
  name : 'harry',
  age:21
}
for(const item of obj){
  console.log(item);  //obj is not iterable
}

如果这个对象是可迭代的对象,那就没问题。

const iterableObj = {
  names : ["abc","cba","ccc"],
  [Symbol.iterator](){
    let index = 0 
    return {
      next:()=> {
        if(index < this.names.length){ //iterator.next() 这样子这个对象里没有的,所以next得写箭头函数,这样它就会去上层作用域找
          //如果不写箭头函数,调用的时候就是返回的对象.next(),不是iterableObj了
          return {done:false,value:this.names[index++]}
        }else{
          return {done:true,value:undefined}
        }
      }
    } 
  }
}

for(const item of iterableObj){ //for of其实是一个语法糖,本质上的来源就是可迭代对象。什么时候done为true,就自动结束了。
  console.log(item); 
}

内置的可迭代对象

事实上我们平时创建的很多原生对象已经实现了可迭代协议,会生成一个迭代器对象的。

比如说:StringArrayMapSetarguments对象NodeList集合

拿数组来举例:

const names = ["abc","cba","ccc"] //数组对象本身就是一个可迭代对象
console.log(names[Symbol.iterator]); //[Function: values]

//代码1
const iterator1 = names[Symbol.iterator]()
console.log(iterator1.next());
console.log(iterator1.next());
console.log(iterator1.next());
console.log(iterator1.next());

//代码2
for(const item of names){
  console.log(item); //代码2就是代码1的语法糖
}

其他的演示

//Map/Set
const set = new Set()
set.add(10)
set.add(20)

for(item of set){
  console.log(item);
}

//函数中的arguments
function foo(x,y,z){
  for(const arg of arguments){
    console.log(arg);
  }
}

foo(10,20,30)

可迭代对象的应用场景

展开运算符就是可迭代对象的应用场景。

const iterableObj = {
  names : ["abc","cba","ccc"],
  [Symbol.iterator](){
    let index = 0 
    return {
      next:()=> {
        if(index < this.names.length){ //iterator.next() 这样子这个对象里没有的,所以next得写箭头函数,这样它就会去上层找
          return {done:false,value:this.names[index++]}
        }else{
          return {done:true,value:undefined}
        }
      }
    } 
  }
}

//1.展开运算符

const names = ["abc","cba","nba"]
const newNames = [...names] //迭代器的用法
console.log(newNames);

//2.关于对象的展开,这是ES9新增的特性,使用的不是迭代器
// const newObj = {...obj}
// console.log(newObj);

//3.解构语法
const [name1,name2] = names
// console.log(name1);
//这也是es9新增的,不是迭代器
const {name,age} = obj

//4.创建一些其他对象的时候
const set1 = new Set(iterableObj)
const set2 = new Set(names)

const arr1 = Array.from(iterableObj)

//5.Promise.all
Promise.all(iterableObj)l.then(res => {
  console.log(res);
})

自定义类的可迭代性

class Classroom{
  constructor(address,name,students){
    this.address = address
    this.name = name
    this.students = students
  }

  entry(newStudent){
    this.students.push(newStudent)
  }

  [Symbol.iterator](){
    let index = 0
    return {
      next:() => {
        if(index < this.students.length){
          return {done:false,value:this.students[index++]}
        }else{
          return {done:true,value:undefined}
        }
      },
      return: ()=>{
        console.log('迭代器终止了');
        return {done:true,value:undefined}
      }
    }
  }
}

const classroom = new Classroom("3撞5楼200","计算机教室",["james","curry","lebro"])
classroom.entry("lilei")

for(const item of classroom){
  console.log(item); //不加迭代器会报错 
}