步遥——Iterator对象和for...of循环

1:Iterator(遍历器)概念
集合数据结构:Array,Object,Map,Set
用户可以组合使用,定义自己的数据结构。比如:数组中有Map,Map中有对象。这样就需要一种统一的接口机制。遍历器就是这样一种机制,来处理所有不同的数据结构。

遍历器是一种接口。
遍历器为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署Iterator接口,就可以完成遍历操作: for ...of循环

作用:
1:为各种数据结构,提供统一的,简便的访问接口
2:使得数据结构的成员能够按某种次序排列
3:ES6创造了一种新的遍历命令for...of循环,Iterator接口主要提供for...of消费

遍历过程:
1:创建一个指针对象,指向当前数据结构的起始位置。遍历器对象本质上是一个指针对象。{next:function(){} }
2:第一次调用指针对象的next方法,可以将指针指向数据结构的第一个成员。
3:第二次调用指针对象的next方法,指针就指向数据结构的第二个成员。
4:不断调用指针对象的next方法,直到它指向数据结构的结束位置。

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

{value:'someValue',done:false}

指针对象的next方法,用来移动指针。开始时指针指向数组的开始位置,然后每次调用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} : //i++来移动指针
        {value: undefined, done: true}; //最后以为done为true
    }
  };}

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

for...of循环会自动寻找Iterator接口

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

interface Iterable {
  [Symbol.iterator]() : Iterator, // 部署了iterator接口,Iterator是一个函数,就是当前数据结构默认的遍历器生成函数。
}

遍历器接口其实就是一个方法,方法返回一个指针对象,指针对像中有next方法,next方法返回一个含有value,done属性的对象

const obj = {
[Symbol.iterator]:function(){
    return { //指针对象
        next:function(){
            return {
            value:i++,
            done:false
            }
        }
    }
}
}

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

原生具有Iterator接口的数据结构如下:Array,Map,Set,String,TypedArray,arguments对象,NodeList对象

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

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

如果Symbol.iterator方法对应的不是遍历器生成函数(即会返回一个遍历器对象),解释引擎将会报错。

2:调用Iterator接口的场合:
2.1:解构赋值
对数组和Set结构进行解构赋值时,会默认调用Symbol.iterator方法
2.2:扩展运算符
只要某个数据结构部署了 Iterator 接口,就可以对它使用扩展运算符,将其转为数组。
2.3:yield*
yield*后面跟的是一个可遍历的结构,它会调用该结构的遍历器接口。
2.4:其他
由于数组的遍历会调用遍历器接口,所以任何接受数组作为参数的场合,其实都调用了遍历器接口。
* for...of
* Array.from()
* Map(), Set(), WeakMap(), WeakSet()(比如new Map([['a',1],['b',2]]))
* Promise.all()
* Promise.race()

3:字符串也有Iterator接口
可以覆盖原生的Symbol.iterator方法,达到修改遍历器行为的目的。(可以偷偷改掉遍历输出的内容)
对于字符串来说,for...of循环还有一个特点,就是会正确识别 32 位 UTF-16 字符。

4:Iterator接口和Generator函数
Symbol.iterator()方法几乎不用部署任何代码,只要用 yield 命令给出每一步的返回值即可

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

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

return()方法必须返回一个对象,这是 Generator 语法决定的。

throw()方法的使用场合是:
主要是配合 Generator 函数使用,一般的遍历器对象用不到这个方法。请参阅《Generator 函数》一章

6:for...of循环
引入了for...of循环,作为遍历所有数据结构的统一的方法。

let arr = new Array(5).fill({a:1})
for(i of arr){console.log(i)} // 获得键值
for(i in arr){console.log(i)}  //获取键名

for...in循环读取键名,for...of循环读取键值。如果要通过for...of循环,获取数组的索引,可以借助数组实例的entries方法和keys方法(参见《数组的扩展》一章)。

7: Set 和 Map 结构也原生具有 Iterator 接口,可以直接使用for...of循环。
值得注意的地方有两个,首先,遍历的顺序是按照各个成员被添加进数据结构的顺序。其次,Set 结构遍历时,返回的是一个值,而 Map 结构遍历时,返回的是一个数组,该数组的两个成员分别为当前 Map 成员的键名和键值。

并不是所有类似数组的对象都具有 Iterator 接口,一个简便的解决方法,就是使用Array.from方法将其转为数组。

8:对象:
对于普通的对象,for...of结构不能直接使用,会报错,必须部署了 Iterator 接口后才能使用。但是,这样情况下,for...in循环依然可以用来遍历键名。
一种解决方法是,使用Object.keys方法将对象的键名生成一个数组,然后遍历这个数组。
另一个方法是使用 Generator 函数将对象重新包装一下。

9:与其他遍历语法的比较
for : 比较麻烦
forEach: 无法中途跳出forEach循环,break命令或return命令都不能奏效
for...in 循环可以遍历数组的键名 缺点:
* 数组的键名是数字,但是for...in循环是以字符串作为键名“0”、“1”、“2”等等。
* for...in循环不仅遍历数字键名,还会遍历手动添加的其他键,甚至包括原型链上的键。
* 某些情况下,for...in循环会以任意顺序遍历键名。

总之: for...in循环主要是为遍历对象而设计的,不适用于遍历数组。
for...of:优点:
* 有着同for...in一样的简洁语法,但是没有for...in那些缺点。
* 不同于forEach方法,它可以与break、continue和return配合使用。
* 提供了遍历所有数据结构的统一操作接口。

for (var n of fibonacci) {
  if (n > 1000)
    break;
  console.log(n);
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,277评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,689评论 3 393
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,624评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,356评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,402评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,292评论 1 301
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,135评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,992评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,429评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,636评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,785评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,492评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,092评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,723评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,858评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,891评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,713评论 2 354

推荐阅读更多精彩内容