ES6学习9(Iterator&for...of)

Iterator

JavaScript原有的表示“集合”的数据结构:
Array、Object、Map、Set
用户还可以组合他们,定义自己的数据结构。这样就需要一种统一的接口机制,来处理所有不同的数据结构。
遍历器(Iterator)就是这样一种机制。它是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署Iterator接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)。
Iterator的作用有三个:一是为各种数据结构,提供一个统一的、简便的访问接口;二是使得数据结构的成员能够按某种次序排列;三是ES6创造了一种新的遍历命令for…of循环,Iterator接口主要供for…of消费。

遍历过程

  • 创建一个指针对象,指向当前数据结构的起始位置。也就是说,遍历器对象本质上,就是一个指针对象。
  • 第一次调用指针对象的next方法,可以将指针指向数据结构的第一个成员。
  • 第二次调用指针对象的next方法,指针就指向数据结构的第二个成员。
  • 不断调用指针对象的next方法,直到它指向数据结构的结束位置。

每一次调用next都会返回value和done,value是当前遍历到的成员的值,done是是否遍历完成的布尔值。

数据结构的默认Iterator接口

一种数据结构只要部署了Iterator接口,我们就称这种数据结构是”可遍历的“(iterable)。
ES6规定,默认的Iterator接口部署在数据结构的Symbol.iterator属性
在ES6中,有三类数据结构原生具备Iterator接口:数组、某些类似数组的对象(字符串,NodeList)、Set和Map结构,
我们来看一下数组的:

let arr = ['a', 'b', 'c'];
let iter = arr[Symbol.iterator]();

console.log(iter.next()); // { value: 'a', done: false }
console.log(iter.next()); // { value: 'b', done: false }
console.log(iter.next()); // { value: 'c', done: false }
console.log(iter.next()); // { value: undefined, done: true }

由此看来,这个接口最重要的就是这个next方法,这个方法应该可以根据遍历的情况返回当前的遍历值value和是否遍历完成的标志done。
那么只要我在一个对象的Symbol.iterator上部署了一个合乎规定的next方法,那这个对象就可以使用for…of遍历了。
我们来看一个例子,使用遍历器实现单链表结构的遍历:

//链表节点本身
function Node(value) {
  this.value = value;
  this.next = null;
}
//链表节点的遍历方法,定义在Symbol.iterator上
Node.prototype[Symbol.iterator] = function() {
    //遍历方法必须返回一个遍历器对象
    //这个对象唯一强制的条件就是具有一个符合要求的next方法
  var iterator = {
    next: next
  };
  var current = this;
  //对于这个next方法,必须返回一个具有done属性和value属性的对象
  function next() {
    if (current) {
      var value = current.value;
      current = current.next;
      return {
        done: false,
        value: value
      };
    } else {
      return {
        done: true
      };
    }
  }
  return iterator;
}
var one = new Node(1);
var two = new Node(2);
var three = new Node(3);

one.next = two;
two.next = three;

for (var i of one){
  console.log(i);
}
// 1
// 2
// 3

对于类似数组的对象,也就是存在以0,1,2……为键,并有length属性的对象,直接使用数组的遍历器就行:

NodeList.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];
[...document.querySelectorAll('div')] // 可以执行了

调用Iterator接口的场合

有一些场合会默认调用Iterator接口

解构赋值

let set = new Set().add('a').add('b').add('c');
let [first, ...rest] = set;
// first='a'; rest=['b','c'];

扩展运算符

只要某个数据结构部署了Iterator接口,就可以对它使用扩展运算符,将其转为数组。

let arr = [...iterable];

yield*

yield*后面跟的是一个可遍历的结构,它会调用该结构的遍历器接口。

let generator = function* () {
  yield 1;
  yield* [2,3,4];
  yield 5;
};

var iterator = generator();
console.log(iterator.next()); // { value: 1, done: false }
console.log(iterator.next()); // { value: 2, done: false }
console.log(iterator.next()); // { value: 3, done: false }
console.log(iterator.next()); // { value: 4, done: false }
console.log(iterator.next()); // { value: 5, done: false }
console.log(iterator.next()); // { value: undefined, done: true }
for (var i of generator()) {
    console.log(i);
}

任何接受数组为参数的地方

由于数组的遍历会调用遍历器接口,所以任何接受数组作为参数的场合,其实都调用了遍历器接口。

Iterator接口与Generator函数

Symbol.iterator方法的最简单实现,还是使用Generator函数。

var myIterable = {};
myIterable[Symbol.iterator] = function* () {
  yield 1;
  yield 2;
  yield 3;
};
[...myIterable] // [1, 2, 3]

遍历器对象的return(),throw()

遍历器对象除了具有next方法,还可以具有return方法和throw方法。如果你自己写遍历器对象生成函数,那么next方法是必须部署的,return方法和throw方法是否部署是可选的。

return方法的使用场合是,如果for…of循环提前退出(通常是因为出错,或者有break语句或continue语句),就会调用return方法。如果一个对象在完成遍历前,需要清理或释放资源,就可以部署return方法。

var str = new String('abcdefghijklmn');
str[Symbol.iterator] = function() {
    var index = 0;
    var _length = this.length;
    return {
        next: function(){
            if (index<_length)
                return {value:str[index++]+'1',done:false};
            else 
                return {done:true};
        },
        return: function() {
            console.log('somthing wrong');
            return { done: true };
        }
    }
}
for (var i of str) {
    console.log(i);
    if (i==='l1')
        break;
}

这里FF测试不成功。
throw方法主要是配合Generator函数使用

for…of循环

一个数据结构只要部署了Symbol.iterator属性,就被视为具有iterator接口,就可以用for…of循环遍历它的成员。for…of循环内部调用的是数据结构的Symbol.iterator方法。
for…of循环可以使用的范围包括数组、Set和Map结构、某些类似数组的对象(比如arguments对象、DOM NodeList对象)、Generator对象,以及字符串。

遍历Set

var engines = new Set(["Gecko", "Trident", "Webkit", "Webkit"]);
for (var e of engines) {
  console.log(e);
}
// Gecko
// Trident
// Webkit

遍历Map

var es6 = new Map();
es6.set("edition", 6);
es6.set("committee", "TC39");
es6.set("standard", "ECMA-262");
for (var [name, value] of es6) {
  console.log(name + ": " + value);
}
// edition: 6
// committee: TC39
// standard: ECMA-262

计算生成的数据结构

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

  • entries() 返回一个遍历器对象,用来遍历[键名, 键值]组成的数组。对于数组,键名就是索引值;对于Set,键名与键值相同。Map结构的iterator接口,默认就是调用entries方法。
  • keys() 返回一个遍历器对象,用来遍历所有的键名。
  • values() 返回一个遍历器对象,用来遍历所有的键值。

这三个方法调用后生成的遍历器对象,所遍历的都是计算生成的数据结构。

类似数组的对象

字符串、DOM NodeList对象、arguments对象
对于字符串来说,for…of循环还有一个特点,就是会正确识别32位UTF-16字符。
并不是所有类似数组的对象都具有iterator接口。你可以像上面说的把Array的Symbol.iterator赋值过来。还有一个更简便的解决方法,就是使用Array.from方法将其转为数组。

let arrayLike = { length: 2, 0: 'a', 1: 'b' };
// 正确
for (let x of Array.from(arrayLike)) {
  console.log(x);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值