概述
前言:
半年前快速过了一遍ES6语法,掌握并熟练了一些常用的ES6语法,比如:class、继承、模板字符串、解构赋值、导入导出、Promise等等,然而对于一些ES6的其他新特性,并没有认真研究,最近学习ES8时,学习了Object.entries方法,然后遇到了一个问题,这个问题是:为什么对象不可以用for of进行遍历?这才来研读了一下Iterator,很庆幸我在这里找到了答案,本文参考了阮一峰老师的ES6标准入门,为此买了一本纸质版的ES6标准入门以示感谢!
1、Iterator是什么?
我的理解就是为不同的数据结构统一遍历接口,以便按顺序进行遍历,通俗一点讲就是可以共用ES6的for of遍历机制
2、一起来看看ES6标准入门中关于Iterator的简单模拟实现
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};
}
};
}
首先调用makeIterator方法得到一个遍历器对象,该对象里面有一个next属性对应一个方法,该方法用于进行遍历,该方法共享闭包作用域里面的nextIndex指针来进行遍历,每次执行next方法都会将nextIndex加1,指向数组的下一个成员,当指针nextIndex大于数组长度时,将返回{value: undefined, done: true},即完成状态。此处抛一个问题:for of遍历数据结构时是依据什么跳出遍历过程的?
3、Iterator接口部署在哪里
并不是所有的数据结构都部署了Iterator接口,只有部署了Iterator的数据结构才可以使用for of进行遍历,默认的 Iterator 接口部署在数据结构的Symbol.iterator属性
console.log(Object.prototype[Symbol.iterator])
//undefined
console.log(Array.prototype[Symbol.iterator])
//function values() { [native code] }
console.log(String.prototype[Symbol.iterator])
//function [Symbol.iterator]() { [native code] }
这说明对象是没有部署Iterator接口的,而数组和字符串是部署了遍历借口的,下面我们来验证一下:
const obj={
"name":"luochao",
"age" : "age"
}
for (const x of obj) {
console.log(x); //obj[Symbol.iterator] is not a function
}
for (const y of ["1","2","3"]) {
console.log(y); //1,2,3
}
for (const z of "lc") {
console.log(z); //l,c
}
当然你也可以这样遍历
let obj4= [6,7,8]
let xx=obj4[Symbol.iterator](); //拿到遍历器对象
for(let i=0;i<obj4.length;i++){
console.log(xx.next())
}
//Object {value: 6, done: false}
//Object {value: 7, done: false}
//Object {value: 8, done: false}
console.log(xx.next())// {value: undefined,done: true}
此处抛出一个问题?你是否发现这两种遍历数组的方式差别在哪里了呢?你是否可以模拟的写出Array.prototype[Symbol.iterator]遍历接口的实现逻辑呢?聪明的你可能已经想出来了
原生部署了遍历接口的数据结构有:Array、Map、Set、String、TypedArray、函数的 arguments 对象
如果你没有写出来Array.prototype[Symbol.iterator]遍历接口的实现,也不要灰心,跟着我接着往下看
4、一个对象如果要具备可被for…of循环调用的 Iterator 接口,就必须在Symbol.iterator的属性上部署遍历器生成方法(原型链上的对象具有该方法也可),不理解不要紧,看一下ES6标准人门中的代码
class RangeIterator {
constructor(start, stop) {
this.value = start;
this.stop = stop;
}
[Symbol.iterator]() { return this; }
next() {
var value = this.value;
if (value < this.stop) {
this.value++;
return {done: false, value: value};
}
return {done: true, value: undefined};
}
}
function range(start, stop) {
return new RangeIterator(start, stop);
}
for (var value of range(0, 3)) {
console.log(value); // 0, 1, 2
}
这里我们看下使用typescript编译后的代码
var RangeIterator = (function () {
function RangeIterator(start, stop) {
this.value = start;
this.stop = stop;
}
RangeIterator.prototype[Symbol.iterator] = function () { return this; };
RangeIterator.prototype.next = function () {
var value = this.value;
if (value < this.stop) {
this.value++;
return { done: false, value: value };
}
return { done: true, value: undefined };
};
return RangeIterator;
}());
通过编译后的代码可以发现遍历器接口被加在了RangeIterator.prototype[Symbol.iterator]上,发现了什么?for of遍历range(0, 3)生成的对象,直接拿到值,是不是可以看成range(0,3)生成的对象直接调用了原型RangeIterator.prototype上的[Symbol.iterator]方法拿到了遍历器对象,然后再调用了原型上的next方法拿到{ done: false, value: value }对象,然后在取对象的value属性拿到实际的value值:0,1,2,for of是不是真的做了这些事呢?
5、我们来重写一下Array.prototype[Symbol.iterator]函数(这只是我的假设,有不对的地方欢迎指正)
Array.prototype[Symbol.iterator]=function(){
this.nextIndex=0;
this.next=function(){
return this.nextIndex < this.length ?
{value: this[this.nextIndex++], done: false} :
{value: 1, done: true};
}
return this;
}
let arr3=[9,8,7];
for(let attr of arr3){
console.log(attr) //9,8,7
}
let xx=arr3[Symbol.iterator](); //拿到遍历器对象
for(let i=0;i<arr3.length;i++){
console.log(xx.next())
}
//Object {value: 9, done: false}
//Object {value: 8, done: false}
//Object {value: 7, done: false}
console.log(xx.next())
//Object {value: undefined, done: true}
应该是眼前一亮吧?我的设想是for of遍历具有iterator接口的数据结构,会先执行[Symbol.iterator]方法,然后返回遍历器对象(这里以arr3为例就是返回arr3),然后调用遍历器对象(arr3)的next方法拿到Object {value: 9, done: false},然后调用返回对象的value属性拿到具体的值
6、我们再来看一段代码验证我们的构想,并回答开始的一个问题:for of遍历数据结构时是依据什么跳出遍历过程的?
function Obj(value) {
this.value = value;
this.next = null;
}
Obj.prototype[Symbol.iterator] = function() {
var iterator = { next: next };
var current = this;
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 Obj(1);
var two = new Obj(2);
var three = new Obj(3);
one.next = two;
two.next = three;
for (var i of one){
console.log(i); // 1, 2, 3
}
同样的我们来分析这段代码,for of循环执行,调用one对象(其实引用js高程上的话说应该叫指针)的[Symbol.iterator]方法拿到遍历器对象iterator,然后调用遍历器对象iterator的next方法,第一次执行current等于one指针指向的对象,所以会返回{ done: false, value: 1 },并且执行 current = current.next
后this就变成了two指针指向的对象,然后接下来遍历就应该遍历two指针指向的对象了,什么时候跳出,相信你也看出来了当current取three的next属性时,current为undefined,则返回{ done: true },当for of拿到{ done: true }将会跳出遍历,为了验证我的猜想,我把代码做了如下修改
function Obj(value) {
this.value = value;
this.next = null;
}
Obj.prototype[Symbol.iterator] = function() {
var iterator = { next: next };
var current = this;
function next() {
if (current) {
var value = current.value;
current = current.next;
return { done: false, value: value };
} else {
return { done: false,value:"无限遍历"};
}
}
return iterator;
}
var one = new Obj(1);
var two = new Obj(2);
var three = new Obj(3);
one.next = two;
two.next = three;
for (var i of one){
console.log(i); // 1, 2, 3
}
当我把{done:true}修改为{ done: false,value:”无限遍历”}时,你讲会看到这个for of循环根本停不下来了,接下来控制台就是满屏的”无限循环”字眼,所以for of跳出循环是依据{done:true}进行跳出的
7、回到最开始的问题,为什么对象不可以用for of进行遍历?
最直接的回到应该是对象没有部署[Symbol.iterator]遍历接口,那它为什么不部署遍历接口呢?再次回到定义:部署遍历接口是为了数据结构的成员能够按某种次序排列,当你看了Array.prototype[Symbol.iterator]的实现
Array.prototype[Symbol.iterator]=function(){
this.nextIndex=0;
this.next=function(){
return this.nextIndex < this.length ?
{value: this[this.nextIndex++], done: false} :
{value: 1, done: true};
}
return this;
}
你可能会注意到了this.nextIndex,value:this[this.nextIndex++],这充分表明部署遍历接口的数据接口都是有线性规律的,而对象的键值都是任意的,杂乱无章的,这就失去了部署遍历接口的意义
8、如何查看数据结构的遍历接口
数据结构之所以可以用for of进行遍历,是因为数据结构部署了遍历接口,而用for of进行循环遍历时,会默认调用待遍历数据的遍历接口,遍历接口一般是数据结构原型的某一个方法(比如:values,entries方法),下面我来教你如何查看数据结构的遍历接口:查看对象原型上的 Symbol.iterator
属性对应的方法
const set = new Set([
['lc', 22],
['yx', 21]
]);
console.log(set);
set结构的 Symbol.iterator
属性对应的方法是values方法,这说明我们在调用for of对set结构进行遍历的时候,他相当于会调用values()方法拿到遍历器对象(其实 set[Symbol.iterator]
=== set.values
),也就是说你直接用for of遍历 set.values()
方法或者 set
是没有区别的
for (const v of set){
console.log(v); //["lc", 1],['yx', 21]
}
for (let v of set.values()){
console.log(v)//["lc", 1],['yx', 21]
}
console.log(set[Symbol.iterator]===set.values) //true
9、我有话说
let arr3=[9,8,7];
let xx=arr3[Symbol.iterator](); //拿到遍历器对象
console.log(xx) //[9, 8, 7, nextIndex: 0]
let arr4=[9,8,7];
let xxx=arr4[Symbol.iterator];
console.log(xxx()) //为什么这里就不能拿到上面带nextIndex的数组了呢
欢迎大佬留言指正,万分感谢!!!
最后
以上就是爱笑黑裤为你收集整理的探索ES6 Iterator(遍历器)的全部内容,希望文章能够帮你解决探索ES6 Iterator(遍历器)所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复