ES6 学习13 简单归纳Iterator(遍历器) 和 for...of 循环

1.Iterator(遍历器)

1.1.概述

JavaScript原有的 用来表示集合的数据结构,

  • 数组(Array)
  • 对象(Object)

ES6又增加了两种

  • Set(类似数组)
  • Map(类似对象)

这样就有了 4 种表示集合的数据结构,开发者可以根据自己需求,组合它们,定义自己需要的数据结构。
然而这样就需要一种统一的接口机制,来处理所有不同的数据结构。

Iterator的作用有三个

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

Iterator 的遍历过程:

  1. 创建一个指针对象,指向当前数据结构最开始的位置,也就是说,遍历器对象本质上,就是一个指针对象。
  2. 第一次调用指针对象的 next 方法,指针指向数据结构的第一个成员
  3. 第二次调用指针对象的 next 方法,指针指向数据结构的第二个成员。
  4. 以此类推,直至最后一个成员

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

举一个模拟遍历器 next方法返回值的例子

var it = makeIterator(['a', 'b']);
it.next() // { value: "a", done: false }
it.next() // { value: "b", done: false }
it.next() // { value: undefined, done: true }

function makeIterator(array) {
  var nextIndex = 0;
  return {
    next: function() {
      return nextIndex < array.length ?
        {value: array[nextIndex++], done: false} :
        {value: undefined, done: true};
    }
  };
}
//对于遍历器对象来说,done: false和value: undefined属性都是可以省略的
//因此上面的makeIterator函数可以简写成下面的形式。
function makeIterator(array) {
  var nextIndex = 0;
  return {
    next: function() {
      return nextIndex < array.length ?
        {value: array[nextIndex++]} :
        {done: true};
    }
  };
}

上面代码,makeIterator函数,它是一个自定义的遍历器生成函数,返回一个遍历器对象。
总之,调用指针对象(遍历器对象)的 next 方法,就可以遍历事先给定的数据结构。

由于 Iterator 只是把接口规格加到数据结构之上,所以,遍历器与它所遍历的那个数据结构,实际上是分开的,完全可以写出没有对应数据结构的遍历器对象,或者说用遍历器对象模拟出数据结构。

1.2.默认的 Iterator 接口

设置默认 Iterator的目的

  • 为所有数据结构,提供了一种统一的访问机制,即 for…of 循环。
  • 当使用 for…of 循环遍历某种数据结构时,该循环会自动去寻找 Iterator 接口。

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

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

ES6 的有些数据结构原生具备 Iterator 接口,可以直接被 for…of 循环。
因为这些数据结构部署了 Symbol.iterator 属性(遍历器接口),调用这个接口,就会返回一个遍历器对象。
而有些数据结构则没有原生部署这个接口,无法直接调用 for…of。比如对象
严格地说,对象部署遍历器接口并不是很必要,因为这时对象实际上被当作 Map 结构使用,ES5 没有 Map 结构,而 ES6 原生提供了。

let obj = {
  data: [ 'hello', 'world' ],
  [Symbol.iterator]() {
    const self = this;
    let index = 0;
    return {
      next() {
        if (index < self.data.length) {
          return {
            value: self.data[index++],
            done: false
          };
        } else {
          return { value: undefined, done: true };
        }
      }
    };
  }
};
for(let i of obj){
	console.log(i)  //输出两次结果:'hello' 'world'
}

上面的代码,向对象部署了遍历器接口,使得 obj 可以被 for…of 遍历

对于类似数组的对象(存在数值键名和length属性),部署 Iterator 接口,有一个简便方法,就是Symbol.iterator方法直接引用数组的 Iterator 接口。

NodeList.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];
// 或者
NodeList.prototype[Symbol.iterator] = [][Symbol.iterator];
[...document.querySelectorAll('div')] // 可以执行了(扩展运算符内部默认会行使遍历操作)

另一个例子

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

上面的例子,iterable 中的键名对应数组,若是改变键名为字符串,当前部署遍历器对象的操作将无效。遍历结果会输出 undefined

1.3.使用 Iterator 的场合

有一些场合会默认调用 Iterator 接口(即Symbol.iterator方法)

  1. 解构赋值:对 Array 和 Set 进行结构赋值时,会默认的调用 Symbol.iterator 方法

  2. 扩展运算符:使用扩展运算符时(…),运算符内部也会调用

  3. yield*:yield后面跟的是一个可遍历的结构,它会调用该结构的遍历器接口。(以后的Generator函数中会学习到yield表达式)

注意

  • 字符串是一个类似于数组的对象,,也具有默认的 Iterator 接口。
  • 可以覆盖原生的 Symbol.iterator 方法,达到修改遍历器行为的目的。
    举个例子
var arr = [0,1,2]

arr[Symbol.iterator] = function(){
	console.log(arr)//(3) [0, 1, "2", Symbol(Symbol.iterator): ƒ]
	let _index = 0	
	return{
		next(){
			if ( _index++ <= arr.length ) {
				return { value:'x' ,done:false }
			}else{
				return{ value:undefined ,done:true }
			} 
			
		}
	}
}

for( let i of arr ){
	console.log(i) //输出4遍 'x'
}

数组本身是有 遍历器对象的 ,但是上面重新给数组定义了 Symbol.iterator 属性,部署了遍历的新方法。

1.4.遍历器对象的方法

遍历器对象一共有 3 种方法

  • next
  • return
  • throw

throw方法主要是配合 Generator 函数使用,一般的遍历器对象用不到这个方法。
next 如果你自己写遍历器对象生成函数,那么 next 方法是必须部署的。其他两种是可选的
return 如果 for…of 循环提前退出(通常是因为出错,或者有 break 语句),就会调用 return 方法。
举个例子

function readLinesSync(file) {
  return {
    [Symbol.iterator]() {
      return {
        next() {
          return { done: false };
        },
        return() {
          file.close();
          return { done: true };
        }
      };
    },
  };
}

上面的例子,返回了一个遍历器对象,定义了 next 方法和 return 方法

// 情况一
for (let line of readLinesSync(fileName)) {
  console.log(line);
  break;
}

// 情况二
for (let line of readLinesSync(fileName)) {
  console.log(line);
  throw new Error();
}

上面代码中,情况一输出文件的第一行以后,就会执行return方法,关闭这个文件;情况二会在执行return方法关闭文件之后,再抛出错误。

注意

  • return方法必须返回一个对象,这是 Generator 规格决定的。
  • throw方法主要是配合 Generator 函数使用,一般的遍历器对象用不到这个方法。

2.for…of 和 for…in循环

ES6 借鉴 C++、Java、C# 和 Python 语言,引入了 for...of 循环,作为遍历所有数据结构的统一的方法。
for…of循环可以使用的范围包括数组、Set 和 Map 结构、某些类似数组的对象(比如arguments对象、DOM NodeList 对象)、 Generator 对象,以及字符串。

for...in 循环用来遍历键名,有几个缺点

  • 数组的键名是数字,但是 for…in 循环是以字符串作为键名“0”、“1”、“2”等等。
  • for…in 循环不仅遍历数字键名,还会遍历手动添加的其他键,甚至包括原型链上的键。
  • 某些情况下,for…in循环会以任意顺序遍历键名。
  • 总之,for…in循环主要是为遍历对象而设计的,不适用于遍历数组。

for...of循环相比上面几种做法,有一些显著的优点。

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

3.总结归纳

  1. 至ES6,用来表示集合的数据结构有4种:数组、对象、Set、Map,他们有统一的接口机制(Iterator),来处理所有不同的数据结构。
  2. 上述的 4 中数据结构,除了对象,都有原生 Iterator 可以使用。若需要遍历 for... of 遍历对象,则可以讲对象转为 Map 结构,或者自己部署 遍历器
  3. ES6创造了 for...of(遍历键值) 循环,是遍历所有数据结构的统一操作接口。
  4. 当有需要时,可以通过改变 Symbol.iterator 属性来自定义遍历器的行为
  5. 遍历器对象有三种方法, next return throw,后两个方法可选。
  6. for...in 循环遍历键名(主要遍历对象),for...of 遍历键值 。对于 for…of 遍历Map结构,会将"键值对"(数组形式)都遍历出来
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值