当前位置:   article > 正文

扩展1:Iterator(遍历器)_iterator()

iterator()

1、迭代器模式:

迭代器模式是指提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示。在使用迭代器模式之后,即使不关心对象的内部构造,也可以按顺序访问其中的每个元素。

2、Iterator的概念

表示“集合”的数据结构有很多,比如 Array、Map、Set、String、arguments、NodeList等。这样就需要一种统一的接口机制,来处理遍历所有不同的数据结构。

遍历器(Iterator)是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)

以上数据类型,都有 Symbol.iterator 属性,属性值是一个函数,执行这个函数就会返回一个迭代器。这个迭代器就有 next 方法可顺序迭代子元素。

作用:

  • 为各种数据结构,提供一个统一的、简便的访问接口;
  • 数据结构的成员能够按某种次序排列;
  • ES6 创造了一种新的遍历命令 for...of 循环,Iterator 接口主要供 for…of 消费

3、Iterator 的遍历过程

(1)创建一个指针对象,指向起始位置。遍历器对象本质上,就是一个指针对象

(2)第一次调用指针对象的 next 方法,指向第一个成员。

(3)第二次调用指针对象的 next 方法,指向第二个成员。

(4)不断调用指针对象的 next 方法,直到它指向数据结构的结束位置。

每一次调用next方法,都会返回数据结构的当前成员的信息。具体来说,就是返回一个包含 value 和done 两个属性的对象。其中,value 属性是当前成员的值,done 属性是一个布尔值,表示遍历是否结束。
es6语法模拟:

  1. class MakeIterator {
  2. constructor(list) {
  3. this.list = list;
  4. this.index = 0;
  5. }
  6. next() {
  7. if (this.index < this.list.length) {
  8. return {
  9. value: this.list[this.index++],
  10. done: false
  11. }
  12. } else {
  13. return {
  14. value: undefined,
  15. done: true
  16. }
  17. }
  18. }
  19. }
  20. // 测试
  21. let iterator = new MakeIterator([1,2,3]);
  22. console.log(iterator.next());
  23. console.log(iterator.next());
  24. console.log(iterator.next());
  25. console.log(iterator.next());

4、默认 Iterator 接口

数据结构只要部署了 Iterator 接口,我们就称这种数据结构是“可遍历的”(iterable)。

默认的 Iterator 接口部署在数据结构的 Symbol.iterator 属性,或者说,一个数据结构只要具有 Symbol.iterator 属性,就可以认为是“可遍历的”(iterable)。

Symbol.iterator 属性本身是一个函数,这个函数是当前数据结构默认的遍历器生成函数。执行这个函数,就会返回一个遍历器。 因为属性名 Symbol.iterator 是一个表达式,这是一个预定义好的、类型为 Symbol 的特殊值,所以要放在方括号内。

可以运行 Array.prototype[Symbol.iterator] 来测试,如图所示

可以进行自定义:

  1. const obj = {
  2. [Symbol.iterator]: function () {
  3. return {
  4. next: function () {
  5. return {value: 0, done: false}
  6. }
  7. }
  8. }
  9. }
  10. // 测试
  11. let iterator = obj[Symbol.iterator]();
  12. console.log(iterator.next());
  13. console.log(iterator.next());
  14. console.log(iterator.next());

结果如下:

对象 obj 是可遍历的(iterable),因为具有 Symbol.iterator 属性。执行这个属性,会返回一个遍历器对象,对象中有next方法

ES6 的有些数据结构原生具备 Iterator 接口(比如数组),不用任何处理。原因在于,这些数据结构原生部署了Symbol.iterator属性,另外一些数据结构没有(比如对象)

凡是部署了Symbol.iterator属性的数据结构,就称为部署了遍历器接口。调用这个接口,就会返回一个遍历器对象。

原生具备 Iterator 接口的数据结构如下

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


下面的例子是数组的 Symbol.iterator 属性(在原型链 Array.prototype 上),调用这个属性,就得到遍历器对象

  1. let arr = ['a', 'b', 'c'];
  2. let iter = arr[Symbol.iterator]();
  3. iter.next() // { value: 'a', done: false }
  4. iter.next() // { value: 'b', done: false }
  5. iter.next() // { value: 'c', done: false }
  6. iter.next() // { value: undefined, done: true }

但是如果 Symbol.iterator 方法对应的不是遍历器生成函数(即会返回一个遍历器对象),解释引擎将会报错遍历器对象的根本特征就是具有 next 方法。 每次调用 next 方法,都会返回一个代表当前成员的信息对象,具有 value 和 done 两个属性。当我们使用 for of 循环遍历的时候,打印的就是 value 属性

  1. // 测试 5。正常运行------------------------------------------
  2. var obj5 = {};
  3. obj5[Symbol.iterator] = function () {
  4. let index = 0;
  5. return {
  6. next: function () {
  7. if (index < 4) {
  8. return {value: index++, done: false}
  9. }
  10. return {value: undefined,done: true}
  11. }
  12. }
  13. }
  14. for (let item of obj5) {
  15. console.log(item);
  16. }

4、for…of循环

来源:可以利用 Symbol.iterator 自己封装一个遍历方法:

function each(data) {
  let iterator = data[Symbol.iterator]();

  let item = iterator.next();
  while (!item.done) {
    console.log(item.value);
    item = iterator.next();
  }
}

介绍: 一个数据结构只要部署了 Symbol.iterator 属性,就被视为具有 iterator 接口,就可以用 for...of 循环遍历它的成员

for…of 循环可以使用的范围包括数组、Set 和 Map 结构、某些类似数组的对象(比如arguments对象、DOM NodeList 对象)、Generator 对象,以及字符串

对于原生部署 Iterator 接口的数据结构,不用自己写遍历器生成函数,for...of循环会自动遍历它们。除此之外,其他数据结构(主要是对象)的 Iterator 接口,都需要自己在Symbol.iterator属性上面部署,这样才会被for…of循环遍历。
 

1、数组

数组原生具备 iterator 接口(即默认部署了 Symbol.iterator 属性),for...of 循环本质上就是调用这个接口产生的遍历器

  1. let arr = ['red','green','blue'];
  2. for (let item of arr) {
  3. console.log(item); // red green blue
  4. }
  5. 空对象obj部署了数组arr的Symbol.iterator属性,结果obj的forof循环产生了与arr完全一样的结果
  6. let obj = {};
  7. obj[Symbol.iterator] = arr[Symbol.iterator].bind(arr);
  8. for (let item of obj) {
  9. console.log(item); // red green blue
  10. }

另外,for...of 循环调用遍历器接口,数组的遍历器接口只返回具有数字索引的属性。这一点跟 for...in 循环不一样。 

  1. let arr = [1,2,3];
  2. arr.foo = 'webchang';
  3. for (let i in arr) {
  4. console.log(i); // "0" "1" "2" "foo"
  5. }
  6. for (let i of arr) {
  7. console.log(i); // 1 2 3
  8. }

2、Set 和 Map 结构

Set 和 Map 结构也原生具有 Iterator 接口,可以直接使用 for…of 循环

Set:

  1. // 遍历 Set 结构
  2. let names = new Set(['小明','webchang','小红','webchang']);
  3. for (let item of names) {
  4. console.log(item); // 小明 webchang 小红
  5. }

Map: 

  1. // 遍历 Map 结构
  2. let students = new Map();
  3. students.set('001','小明');
  4. students.set('002','webchang');
  5. students.set('003','小红');
  6. for (let item of students) {
  7. console.log(item);
  8. }
  9. for (let [num,name] of students) {
  10. console.log(num,name);
  11. }

 结果如下

 注意:

1、遍历的顺序是按照各个成员被添加进数据结构的顺序

2、Set 结构遍历时,返回的是一个值

 Map 结构遍历时,返回的是一个数组,该数组的两个成员分别为当前 Map 成员的键名和键值

3、计算生成的数据结构 

有些数据结构是在现有数据结构的基础上,计算生成的。比如,ES6 的数组、Set、Map 都部署了以下三个方法,调用后都返回遍历器对象。

1、entries() 返回一个遍历器对象,用来遍历 [键名, 键值] 组成的数组。对于数组,键名就是索引值;对于 Set,键名与键值相同。Map 结构的 Iterator 接口,默认就是调用 entries 方法。
2、keys() 返回一个遍历器对象,用来遍历所有的键名。
3、values() 返回一个遍历器对象,用来遍历所有的键值。
这三个方法调用后生成的遍历器对象,所遍历的都是计算生成的数据结构

  1. let arr = ['a', 'b', 'c'];
  2. for (let pair of arr.entries()) {
  3. console.log(pair);
  4. }
  5. // [0, 'a']
  6. // [1, 'b']
  7. // [2, 'c']

Object.entries()

方法返回一个给定对象自身可枚举属性的键值对数组,其排列与使用 for...in 循环遍历该对象时返回的顺序一致(区别在于 for-in 循环还会枚举原型链中的属性)。

  1. const object1 = {
  2. a: 'somestring',
  3. b: 42
  4. };
  5. for (const [key, value] of Object.entries(object1)) {
  6. console.log(`${key}: ${value}`);
  7. }
  8. // "a: somestring"
  9. // "b: 42"
  10. for (const item of Object.entries(object1)) {
  11. console.log(item);
  12. }
  13. > Array ["a", "somestring"]
  14. > Array ["b", 42]
  15. const obj = { foo: 'bar', baz: 42 };
  16. console.log(Object.entries(obj)); // [ ['foo', 'bar'], ['baz', 42] ]

 

4、类似数组的对象

类似数组的对象包括好几类。for...of 循环用于字符串、DOM NodeList 对象、arguments 对象

  1. // 字符串
  2. let str = "hello";
  3. for (let s of str) {
  4. console.log(s); // h e l l o
  5. }
  6. // DOM NodeList对象
  7. let paras = document.querySelectorAll("p");
  8. for (let p of paras) {
  9. p.classList.add("test");
  10. }
  11. // arguments对象
  12. function printArgs() {
  13. for (let x of arguments) {
  14. console.log(x);
  15. }
  16. }
  17. printArgs('a', 'b');
  18. // 'a'
  19. // 'b'

 注意:并不是所有类似数组的对象(存在数值键名和length属性)都具有 Iterator 接口

  1. // 这是一个类似数组的对象,但是它没有 Iterator 接口
  2. let arrayLike = { length: 2, 0: 'a', 1: 'b' };

但是可以对这些类似数组的对象部署 Iterator 接口,有一个简便方法,就是 Symbol.iterator 方法直接引用数组的 Iterator 接口

  1. NodeList.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];
  2. // 或者
  3. NodeList.prototype[Symbol.iterator] = [][Symbol.iterator];

NodeList 对象是类似数组的对象,本来就具有遍历接口,可以直接遍历。上面代码中,我们将它的遍历接口改成数组的Symbol.iterator属性,没有任何影响。 

类似数组的对象调用数组的 Symbol.iterator 方法

  1. let iterable = {
  2. 0: 'a',
  3. 1: 'b',
  4. 2: 'c',
  5. length: 3,
  6. [Symbol.iterator]: Array.prototype[Symbol.iterator]
  7. };
  8. for (let item of iterable) {
  9. console.log(item); // 'a', 'b', 'c'
  10. }

注意,普通对象部署数组的 Symbol.iterator 方法,无效

  1. let iterable = {
  2. a: 'a',
  3. b: 'b',
  4. c: 'c',
  5. length: 3,
  6. [Symbol.iterator]: Array.prototype[Symbol.iterator]
  7. };
  8. for (let item of iterable) {
  9. console.log(item); // undefined, undefined, undefined
  10. }

5、对于普通的对象,for…of 结构不能直接使用


对于普通的对象,for…of 结构不能直接使用,会报错,必须部署了 Iterator 接口后才能使用。

对象(Object)之所以没有默认部署 Iterator 接口,是因为对象的哪个属性先遍历,哪个属性后遍历是不确定的,需要开发者手动指定。本质上,遍历器是一种线性处理。

  1. let obj = {
  2. name: 'webchang',
  3. age: 18
  4. }
  5. // Uncaught TypeError: obj is not iterable
  6. for (let item of obj) {
  7. console.log(item);
  8. }

 一个对象如果要具备可被 for…of循环调用的 Iterator 接口,就必须在Symbol.iterator 的属性上部署遍历器生成方法(原型链上的对象具有该方法也可)。

  1. class RangeIterator {
  2. constructor(start, end) {
  3. this.value = start;
  4. this.stop = end;
  5. }
  6. [Symbol.iterator]() {
  7. return this;
  8. }
  9. next() {
  10. let value = this.value;
  11. if (value < this.stop) {
  12. this.value++;
  13. return {value: this.value, done: false};
  14. }
  15. return {value: undefined, done: true}
  16. }
  17. }
  18. let range = new RangeIterator(0,3);
  19. for (let item of range) {
  20. console.log(item);
  21. }
  22. console.log(range);

为对象添加 Iterator 接口

  1. let obj = {
  2. data: ['web', 'chang'],
  3. [Symbol.iterator]: function () {
  4. let self = this;
  5. let index = 0;
  6. return {
  7. next: function () {
  8. if (index < self.data.length) {
  9. return {value: self.data[index++], done: false}
  10. }
  11. return {value: undefined, done: true}
  12. }
  13. }
  14. }
  15. }
  16. for (let item of obj) {
  17. console.log(item); // "web" "chang"
  18. }

 案例:

  1. // 想要通过 for of 循环直接遍历出 price 对象中 a、b、c 三个属性对应数组的最小值
  2. let price = {
  3. a: [1, 2, 3],
  4. b: [6, 5, 4],
  5. c: [8, 7, 9],
  6. [Symbol.iterator]() {
  7. let arr = []
  8. for (let [key, value] of Object.entries(this)) {
  9. arr.push(Math.min(...value));
  10. }
  11. let index = 0;
  12. return {
  13. next() {
  14. if (index < arr.length) {
  15. return {value: arr[index++], done: false}
  16. }
  17. return {value: undefined, done: true}
  18. }
  19. }
  20. }
  21. }
  22. for (let item of price) {
  23. console.log(item); // 1 4 7
  24. }

6、与其他遍历语法的比较 

(1)以数组为例,最原始的写法就是for循环。

  1. for (var index = 0; index < myArray.length; index++) {
  2. console.log(myArray[index]);
  3. }

(2)数组提供内置的forEach方法 

无法中途跳出forEach循环,break命令或return命令都不能奏效

  1. myArray.forEach(function (value) {
  2. console.log(value);
  3. });

(3)for...in 循环可以遍历数组的键名 

  1. for (var index in myArray) {
  2. console.log(myArray[index]);
  3. }

for...in 缺点:

数组的键名是数字,但是 for...in 循环是以字符串作为键名的,“0”、“1”、“2”等等
for...in 循环不仅遍历数字键名,还会遍历手动添加的其他键,甚至包括原型链上的键。

for...in 循环主要是为遍历对象而设计的,不适用于遍历数组。

for...of优点:

  • 有着同for…in一样的简洁语法,但是没有for…in那些缺点。
  • 不同于forEach方法,它可以与break、continue和return配合使用。
  • 提供了遍历所有数据结构的统一操作接口。

for  ....in 和for  ....of  的区别

for…in:是为遍历对象属性而构建的,它以任意顺序遍历一个对象的除Symbol以外的可枚举属性,可用break或者throw跳出

  1. let obj = {
  2. name: '张三',
  3. age: 18
  4. }
  5. for(let item in obj) {
  6. console.log(item)
  7. }
  8. // 输出 name age

数组也是对象的一种,所以数组也是可以使用for...in遍历

  1. let arr = ['a', 'b', 'c']
  2. for(let item in arr) {
  3. console.log(item)
  4. }
  5. > "0"
  6. > "1"
  7. > "2"

for…of 在可迭代对象上创建一个迭代循环,调用自定义迭代钩子,并为每个不同属性的值执行语句(包括ArrayMapSetStringTypedArrayarguments等等,不包括Object),可用break或者throw跳出。

  1. let arr = ['a', 'b', 'c']
  2. let obj = {
  3. name: '张三',
  4. age: 18,
  5. sex: '男'
  6. }
  7. for (let i of arr) {
  8. console.log(i)
  9. }// 输出 a b c
  10. for (let i of obj) {
  11. console.log(i)
  12. }// 报错 obj is not iterable (obj不是可迭代的)

区别: 

都是迭代一些东西,主要区别在迭代方式

  • for...in语句以任意顺序迭代对象的可枚举属性

  • for...of语句遍历可迭代对象定义要迭代的数据

    1. let arr = ['a', 'b', 'c']
    2. Array.prototype.ufo = '张三'
    3. for(let item in arr) {
    4. console.log(item)
    5. }// 输出 0 1 2 ufo
    6. for(let item of arr) {
    7. console.log(item)
    8. }// 输出 a b c

    Array.prototype添加了ufo属性,由于继承和原型链,所以arr也继承了ufo属性,属于可枚举属性,所以for...in会遍历出来,而for...of则不会

 

https://blog.csdn.net/weixin_43974265/article/details/113483241   https://blog.csdn.net/pdd11997110103/article/details/119676477

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/盐析白兔/article/detail/179663
推荐阅读
相关标签
  

闽ICP备14008679号