likes
comments
collection
share

JavaScript | 迭代器

作者站长头像
站长
· 阅读数 12

JavaScript中的迭代器是一种常见的机制,它在数据遍历和一些常用的JavaScript操作中扮演着重要的角色。你可能已经熟悉一些使用迭代器的常见情况,比如数组的扩展运算符(spread operator)和数组的解构赋值,这些都是通过迭代器来实现的,那么接下来让我们来具体的了解一下JavaScript中的迭代器吧!

迭代器解决了什么问题?

也许你已经知道了遍历数组可以使用forEach循环,遍历对象可以使用for in循环。但是,从ES6开始添加了新的数据结构MapSet,那么对于这些不同的数据结构,就需要一个统一的方式(接口)去遍历这些数据,那么对于这些新数据的迭代方式,相应的解决方案就是Iterator(迭代器),具体的表现形式如下:

  • 迭代器为JavaScript提供了一个统一的遍历数据的方式,不论是数组、对象、Map以及Set
  • 带来了基于迭代器实现的for of循环命令
  • 使得数据结构的成员能够按某种次序排列,比如遍历对象的属性的时候按照特定的顺序遍历

在了解了迭代器解决了什么问题后,现在来具体了解一下迭代器到底是什么吧!

迭代器是什么?

首先来看下MDN官网对于迭代器的定义:

在 JavaScript 中,迭代器是一个对象,它定义一个序列,并在终止时可能返回一个返回值。更具体地说,迭代器是通过使用 next() 方法实现 Iterator protocol 的任何一个对象,该方法返回具有两个属性的对象: value,这是序列中的 next 值;和 done ,如果已经迭代到序列中的最后一个值,则它为 true 。如果 value 和 done 一起存在,则它是迭代器的返回值。——【MDN-迭代器】

看完上面这段话后是不是有点懵? 简单总结一下得到下面这些:

  • 在JavaScript中迭代器是一个特殊的对象,这个对象具有一些独特的特征
    • 拥有一个next()方法,每次调用都会返回一个对象
    • 调用next()方法返回的对象具有两个值value以及done
      • value返回迭代序列中下一次迭代返回的值,如果迭代结束返回undefined
      • done是一个布尔值,如果已经迭代到序列中的最后一个值,则为true,否则返回false
  • 在创建迭代器对象之后,可以通过调用next()方法对数据进行显示迭代。

可迭代协议

可迭代协议和迭代器是紧密相连的一个概念,那么下面就来看下可迭代协议的概念吧!

可迭代协议允许 JavaScript 对象定义或定制它们的迭代行为,例如,在一个 for..of 结构中,哪些值可以被遍历到。一些内置类型同时是内置的可迭代对象,并且有默认的迭代行为,比如 Array 或者 Map,而其他内置类型则不是(比如 Object)。 为了可迭代,对象必须实现 @@iterator 方法,这意味着该对象(或者它原型链上的某个对象)必须具有带有 @@iterator 键的属性,该属性可通过常量 Symbol.iterator 获得。

  • [Symbol.iterator]:一个无参数的函数,其返回值为一个符合迭代器协议的对象。

——【MDN-可迭代协议】

通过上文可以知道以下几点:

可迭代协议是什么?

  • 可迭代协议(Iterable)定义了一个对象如何能够被迭代,用于告诉JavaScript如何在循环中遍历该对象的元素。

可迭代协议的特征

  1. 在JavaScript中,要实现可迭代协议,需要提供一个特殊的属性作为“默认迭代器”,而这个属性就是Symbol.iterator
  2. Symbol.iterator属性是一个无参数的函数,其返回值为一个符合迭代器协议的对象(这个对象就是#2.迭代器是什么?中所描述的对象)。
  3. 通过调用Symbol.iterator属性(方法),可以获取到这个默认迭代器对象,通过这个对象就可以使用进行迭代的操作。
  4. 一个符合迭代器协议的对象需要实现迭代器接口,即具有next()方法。

原生可迭代与非原生可迭代

在了解完成可迭代协议后,我们就可以来看看原生可迭代与非原生可迭代的概念了。原生可迭代指的是天生具有Symbol.iterator方法的数据,比如:

  • 字符串
  • 数组
  • Set
  • Map
  • arguments对象
  • NodeList等DOM集合

JavaScript | 迭代器而天生没有Symbol.iterator方法的数据,就是非原生可迭代的,比如:

  • 对象
  • 数字

JavaScript | 迭代器

迭代器的基本使用

  1. 对数据调用[Symbol.iterator]()方法,得到可迭代的对象(#可迭代协议的特征-2
  2. 对这个对象调用next()方法,直到done的值为true
// 对数据调用[Symbol.iterator]()方法,得到可迭代的对象
const it = [1, 2, 3][Symbol.iterator]();
// 对这个对象调用next()方法,直到done的值为true
console.log(it.next()); // {value: 1, done: false}

console.log(it.next()); // {value: 2, done: false}

console.log(it.next()); // {value: 3, done: false}

console.log(it.next()); // {value: undefined, done: true}

实际写代码过程中,一般不会使用这种写法,通常情况下会使用for of循环来代替这种写法。

for of循环

  • 一种基于迭代器实现的遍历数据的方式,内部依靠迭代器实现数据的迭代
  • for of循环只会遍历出那些done为false时,对应的value值
let arr = [1,2,3];

for (const item of arr) {
  console.log(item);
}

// 相当于

// 1. 获取可迭代对象
let it1 = arr[Symbol.iterator]();
// 2. 手动调用next()方法
let next = it1.next();
while(!next.done) {
	console.log(next.value);
	next = it1.next();
	console.log(next);
}

JavaScript | 迭代器JavaScript | 迭代器

  • 同样for...of循环也支持breakcontinue关键字
// 1. 在 for...of 中使用 break 关键字 跳出整个for of循环
const arr = [1,2,3,4];
for (const item of arr) {
  if (item === 2) {
    break;
  }
  console.log(item);
}

// 2. 在 for...of 中使用 continue 关键字 跳出本次循环
const arr = [1,2,3,4];
for (const item of arr) {
  if (item === 2) {
    continue;
  }
  console.log(item);
}

JavaScript | 迭代器JavaScript | 迭代器

自定义遍历非原生可迭代的数据

比如现在想要遍历一个对象,那么就可以手动实现一下[Symbol.iterator]方法,通过这个方法就可以遍历对象了

  1. 遍历普通的对象
// 1. 准备一个测试对象
const obj = {
  a: 1,
  b: 2,
  c: 3
}
// 2. 定义迭代器方法 [Symbol.iterator](),让 obj 变为一个可迭代的对象
obj[Symbol.iterator] = () => {
  // 3. 定义一个计数标记 index
  let index = 0
  // 4. 返回一个对象,这个对象提供了next方法,切每次调用都会返回一个对象
  return {
    next() {
      index++;
      if(index === 1) {
        return {
          value: obj.a,
          done: false
        }
      } else if (index === 2) {
        return {
          value: obj.b,
          done: false
        }
      } else if (index === 3) {
        return {
          value: obj.c,
          done: false
        }
      } else {
        return {
          value: undefined,
          done: true
        }
      }
      
    }
  }
}

// 5. 测试
// 5.1 获取迭代器对象
const it = obj[Symbol.iterator]()
// 5.2 手动调用一次
let next = it.next()

while(!next.done) {
  console.log(next.value)
  next = it.next()
  console.log(next);
}

// 当然也可以直接用 for...of 循环

for(let item of obj) {
	console.log(item)
}

JavaScript | 迭代器

  1. 遍历有length和索引属性的对象
// 1. 准备一个测试对象
const obj = {
  0: "value1",
  1: "value2",
  2: "value3",
  length: 3,
};

// 2. 定义迭代器方法 [Symbol.iterator](),让 obj 变为一个可迭代的对象
obj[Symbol.iterator] = () => {
  // 3. 定义一个计数标记 index
  let index = 0;
  // 4. 返回一个对象,这个对象提供了next方法,切每次调用都会返回一个对象
  let value, done;
  return {
    next() {
      if(index < obj.length) {
        value = obj[index];
        done = false;
      } else {
        value = undefined;
        done = true;
      }
      index++;
      return {
        value,
        done
      }
    }
  }
}

// 5. 测试
// 5.1 获取迭代器对象
const it = obj[Symbol.iterator]()
// 5.2 手动调用一次
let next = it.next()

while(!next.done) {
  console.log(next.value)
  next = it.next()
  console.log(next);
}

// 当然也可以直接用 for...of 循环

for(let item of obj) {
	console.log(item)
}

JavaScript | 迭代器

  • 对于这种有length和索引属性的对象,如果需要迭代里面的数据,实际上可以直接借用数组的迭代器方法来帮助迭代
// 1. 准备一个测试对象
const obj = {
  0: "value1",
  1: "value2",
  2: "value3",
  length: 3,
};

// 2. 测试 这里直接接用数组的迭代器方法给这个测试对象
obj[Symbol.iterator] = Array.prototype[Symbol.iterator];

for (const item of obj) {
  console.log('item',item);
}

JavaScript | 迭代器

使用了Iterator的场景

打印FormData中的数据

使用FormData这个类实例化的对象无法正常打印JavaScript | 迭代器使用迭代器的方式才能查看里面的键值对

let form = new FormData();
form.append('username', 'DDD');
form.append('password', '987654321');
for (let item of form.entries()) {
  console.log(item[0] + ":" + item[1]);
}

JavaScript | 迭代器

The FormData.entries() 方法返回一个 iterator对象,此对象可以遍历访问 FormData 中的键值对。其中键值对的 key 是一个 USVString 对象;value 是一个 USVString , 或者 Blob对象。

数组的展开运算符内部使用了迭代器

这里要注意一点,对象的展开({...{a:'1', b:'2'}})不是通过Iterator实现的

console.log(...'string');
console.log(...[9,2,3]);
console.log(...new Set([1,8,9]));

JavaScript | 迭代器

数组的解构赋值内部使用了迭代器

const [a, b] = [1,2];
const [c, d] = [...[1,2]];
const [e, f] = "hi";
const [g, h] = [..."hi"];
const [i, j] = new Set([3, 4]);
const [k, l] = [...new Set([3, 4])];

JavaScript | 迭代器JavaScript | 迭代器

Set和Map的构造函数内部使用了迭代器

new Set([1,2,3,4])
new Map([[0, 1],[1, 2]])

JavaScript | 迭代器