JavaScript可迭代对象详细介绍

目录
  • 1、迭代器
  • 2、迭代器接口与可迭代对象
  • 3、自定义可迭代对象
    • 3.1、可迭代的Range对象
    • 3.2、使用Generator函数作为迭代器接口
    • 3.3、可迭代的List
    • 3.3、可迭代的迭代器
  • 4、可迭代对象的意义
  • 5、使用可迭代对象
  • 6、后记

1、迭代器

迭代器是借鉴C++等语言的概念,迭代器的原理就像指针一样,它指向数据集合中的某个元素,你可以获取它指向的元素,也可以移动它以获取其它元素。迭代器类似于数组中下标的拓展,各种数据结构,如链表(List)、集合(Set)、映射(Map)都有与之对应的迭代器。

JS中的迭代器是专门为了遍历这一操作设计的。每次获取到的迭代器总是初始指向第一个元素,并且迭代器只有next()一种行为,直到获取到数据集的最后一个元素。我们无法灵活移动迭代器的位置,所以,迭代器的任务,是按某种次序遍历数据集中的元素。

JS规定,迭代器必须实现next()接口,它应该返回当前元素并将迭代器指向下一个元素,返回的对象格式为{value:元素值, done:是否遍历结束},其中,done是一个布尔值。done属性为true的时候,我们默认不会去读取value, 所以最后返回的经常是{value: undifined, done: true},注意,返回类似{value: 2, done: true} 不会导致错误,但是因为done设置为true,在for...of等操作中都会忽略value的值。因此,done:falsevalue:undifined可以省略。一个简单的JS迭代器像这样:

let iter = {
    i: 0,
    next() {
        if (this.i > 10) return { done: true };
        return { value: this.i++ };
    }
}
//手动使用迭代器
console.log(iter.next());  //{ value: 0 }
console.log(iter.next());   //{ value: 1 }
while (true) {
    let item = iter.next();
    if (!item.done) {
        console.log(item.value);     //打印从2到10
    } else {
        break;
    }
}

可以看到,迭代器与普通的JS对象没有区别,它就是一个用于实现迭代的对象。手动操作迭代器并没有什么实用性,迭代器的作用是附着在对象上,让一个对象,或者数据结构成为可迭代对象。

2、迭代器接口与可迭代对象

迭代器接口是我们获取对象迭代器时默认调用的接口,一个实现了迭代接口的对象即是可迭代对象。JS的默认迭代接口是[Symbol.iterator], 一个对象实现了[Symbol.iterator]接口就成为了可迭代对象。

[Symbol.iterator]是一个特殊的Symbol属性,它用于JS内部检测一个对象是否为可迭代对象。接口一词的含义代表它是一个函数,其结果应该放回一个迭代器。结合上面迭代器必须要有next()操作,所以,对可迭代对象,调用链iterableObj[Symbol.iterator]().next()应该是可行的。数组是最具代表性的可迭代对象,让我们拿数组测试一下:

arr = [1, '2', {a: 3}];
let arrIt = arr[Symbol.iterator]();    //获取数组迭代器
console.log(arrIt.next());   //{ value: 1, done: false }
console.log(arrIt.next());  //{ value: '2', done: false }
console.log(arrIt.next());   //{ value: { a: 3 }, done: false }
console.log(arrIt.next());  //{ value: undefined, done: true }

可以看到,迭代器的next()接口确实如愿工作,并且返回上述的结构。

3、自定义可迭代对象

现在,让我们来实现几个可迭代对象,这十分简单,只要:

  • 实现对象的迭代器接口[Symbol.iterator](),注意它是一个方法,
  • 在迭代器接口中返回一个迭代器对象,
  • 确保迭代器对象具有next()接口,并且返回{value: v, done: bool}的结构。

3.1、可迭代的Range对象

作为第一个可迭代对象,我们来实现类似python中的range(from, to),不过这里使用Range对象来封装一个左闭右开的范围[from, to)

function Range(from, to) {
    this.from = from;
    this.to = to;
}
Range.prototype[Symbol.iterator] = function () {
    //返回一个迭代器对象
    return {
        cur: this.from,
        to: this.to, //保证next()中可以获取
        next() {
            return (this.cur < this.to) ? {
                value: this.cur++,
                done: false
            } : {
                value: undefined,
                done: true
            };
        }
    }
}
let range = new Range(5, 11);  //创建一个range对象
//使用for...of循环
for (const num of range) {
    console.log(num);    //依次打印5,6,7,8,9,10
}
//使用
let arrFromRange = Array.from(range);
console.log(arrFromRange);   //[5,6,7,8,9,10]

3.2、使用Generator函数作为迭代器接口

因为Generator函数产生的generator对象是一种特殊的迭代器,所以我们可以很方法地使用Generator函数作为对象的迭代器接口。使用Generator函数改写上面的迭代器接口:

Range.prototype[Symbol.iterator] = function* () {
    for (let i = this.from; i < this.to; i++) {
        yield i;
    }
}

这种写法更加简洁易懂,是最为推荐的写法,Generator函数中产生的值就是遍历过程中得到的值。

3.3、可迭代的List

接下来,我们自定义一个链表节点List,在此我们省去不必要的接口。

function ListNode(value) {
    this.value = value;
    this.nextNode = null;
}

function List(root) {
    this.cur = this.root = root;
}
//List的next接口
List.prototype.next = function () {
    if (this.cur) { //非尾哨兵节点
        let curNode = {
            value: this.cur.value
        };
        this.cur = this.cur.nextNode;
        return curNode;
    } else {
        return {
            done: true
        };
    }
}

List.next()实现了将链表指针后移的操纵,并且返回了移动前节点的值,你可能注意到,我特意让返回的对象格式与迭代器返回结果一致,下面你将看到这么做的原因。现在我们让List变成可迭代,按照之前的做法,使得List[Symbol.iterator]().next()能够返回正确的{value: v, done: true}格式。是的,我们已经画好龙了,就差一个点睛之笔:

List.prototype[Symbol.iterator] = function () {
    return this;
}

随手写一个测试:

let a = new ListNode('a'),
    b = new ListNode('b'),
    c = new ListNode('c');
a.nextNode = b, b.nextNode = c;
let list = new List(a);
for (let i of list) {
    console.log(i);  //a,b,c
}

Perfect! List的迭代器接口返回了它自己,利用了自身的next()接口完成迭代操作,也就是说List的迭代器是List本身,我都为自己构思的例子觉得巧妙。

3.3、可迭代的迭代器

上面的List例子会让人觉得有点牵强,list.next()的返回值为了迎合迭代器的要求,让平时不得不使用let curValue = list.next().value来正确接收返回的节点值,确实。但是,这种做法在一种时候让人觉得眼前一亮——让迭代器称为可迭代对象,因为自己就是可迭代器,让自己成为自己的迭代器,就像1=1一样正确自然。

回到开头埋下的雷,我们只需要稍加改动

let iter = {
    i: 0,
    next() {
        if (this.i > 10) return { done: true };
        return { value: this.i++ };
    },
    //让迭代器的迭代器接口返回自身
     [Symbol.iterator]() {
        return this;
    }
}
//这样,你就可以把迭代器用在任何可迭代对象的地方
for (let i of iter) {
    console.log(i);
}

这样,这个迭代器本身也是可迭代的。需要注意的是,内置可迭代类型的迭代器也都是可迭代的,类似for(let i of arr[Symbol.iterator]())的操作是可行,其原理是让Array的迭代器继承Array.prototype。其它类型也有类似的继承,如Generator与generator对象。

4、可迭代对象的意义

可迭代对象作为数组的扩充,具有非凡的意义。在以前,对一个需要操作一组数据的接口,只有数组这种结构能支持,非数组对象必须通过某种方式转化为数组,完成之后,还可能需要还原成原来的结构,这种繁琐的来回切换很不理想。有了可迭代对象的概念,这类操作可以接受一个可迭代对象,数组是可迭代对象,所以之前的数组参数是仍然可行的,在此之上,任何实现了可迭代接口的对象,也能正常处理。考虑这个下面例子:

function sumInArray(arr){
    let sum=0;
    for(let i = 0;i<arr.length;i++){
        sum+=arr[i];
    }
    return sum;
}
function sumInIterable(iterable){
    let sum = 0;
    for(let num of iterable){
        sum+=num;
    }
    return sum;
}

sumInArray()只对数组友好,而sumInIterable()是所有可迭代对象通用的,例如我们前面的Range对象,iter对象。是的,数组到可迭代对象的提升,代表了接口的通用性的提升。这个道理太浅显易懂,以至于你可能觉得我说废话,那么,请问你在接口设计的时候,会考虑能否使用可迭代对象代替数组吗?个人认为这种提升很多时候是有益的,特别在一些应用场景较多的接口,我发现很多ES6操作也是基于可迭代对象。如果有什么看法,也欢迎评论区探讨。

5、使用可迭代对象

先认识JS内建的可迭代对象:

  • 非weak的数据结构,包括Array,Set, Map
  • DOM中的NodeList对象
  • String对象
  • 函数的arguments属性

再了解哪些操作是基于可迭代对象的:

  • for...of语法
  • ...iterable:展开语法和解构赋值
  • yield*语法
  • Map, Set, WeakMap,WeakSet的构造器。为什么没有Array?因为Array直接把它对象当成元素了,但是有Array.from(iterable)
  • Object.fromEntries(iterable) ,每次迭代结果应该是一个对应键值对的二元数组,与Map的迭代结果吻合,常有let obj = Object.fromEntries(map)实现从map到object的转化。
  • promise.all(iterable)promist.race(iterable).

我认为对这些方法的具体使用不该放在这里,如果使用过它们,自然了解,只需要记住它们对任何可迭代对象都是支持的。如果不认识它们我也说不完,你应该一一学习去。

6、后记

到此这篇关于JavaScript可迭代对象详细介绍的文章就介绍到这了,更多相关JS可迭代对象内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • JavaScript中的迭代器和生成器详解

    处理集合里的每一项是一个非常普通的操作,JavaScript提供了许多方法来迭代一个集合,从简单的for和for each循环到 map(),filter() 和 array comprehensions(数组推导式).在JavaScript 1.7中,迭代器和生成器在JavaScript核心语法中带来了新的迭代机制,而且还提供了定制 for-in 和 for each 循环行为的机制. 迭代器 迭代器是一个每次访问集合序列中一个元素的对象,并跟踪该序列中迭代的当前位置.在JavaScript中

  • 总结javascript中的六种迭代器

    1.forEach迭代器 forEach方法接收一个函数作为参数,对数组中每个元素使用这个函数,只调用这个函数,数组本身没有任何变化 //forEach迭代器 function square(num){ document.write(num + ' ' + num*num + '<br>'); } var nums = [1,2,3,4,5,6,7,8]; nums.forEach(square); 在浏览器中输出的结果是: 2.every迭代器 every方法接受一个返回值为布尔类型的函数,

  • JavaScript前端迭代器Iterator与生成器Generator讲解

    目录 Iterator 概念 默认 Iterator 接口 Iterator 的 return() 原生具备 Iterator 接口的数据结构 调用 Iterator 接口的场合 模拟实现 for of Generator 认识 Generator next 方法的参数 yield 表达式 Generator 与 Iterator 之间的关系 Generator.prototype.return() yield* 表达式 Generator 函数的 this Generator 实现一个状态机

  • JavaScript中的迭代器和可迭代对象与生成器

    目录 1. 什么是迭代器? 1.1 迭代器的基本实现 1.2 迭代器的封装实现 2. 什么是可迭代对象 2.1 原生可迭代对象(JS内置) 2.1.1 部分for of 演示 2.1.2 查看内置的[Symbol.iterator]方法 2.2 可迭代对象的实现 2.3 可迭代对象的应用 2.4 自定义类迭代实现 3. 生成器函数 3.1 生成器函数基本实现 3.2 生成器函数单次执行 3.3 生成器函数多次执行 3.4 生成器函数的分段传参 3.5 生成器代替迭代器 4. 可迭代对象的终极封装

  • JavaScript详解类数组与可迭代对象的实现原理

    目录 可迭代对象(Iterable object) Symbol.iterator 把对象本身构造成迭代器 String也是可迭代的 String的迭代器 类数组对象和可迭代对象 Array.from 总结 可迭代对象(Iterable object) 数组是一个特殊的对象,它和普通对象的区别不仅仅在于元素的顺序访问.存储.另外一个重要的区别是:数组是可迭代的,也就是可以使用for ... of语句访问(迭代)所有的元素. 我们可以简单的做一个小实验: let arr = [1,2,3,4,5]

  • JavaScript迭代器的含义及用法

    什么是迭代器 迭代器就是为实现对不同集合进行统一遍历操作的一种机制,只要给需要遍历的数据结构部署Iterator接口,通过调用该接口,或者使用消耗该接口的API实现遍历操作. 迭代器模式 在接触迭代器之前,一起先了解什么是迭代器模式,回想一下我们生活中的事例.我们在参观景区需要买门票的时候,售票员需要做的事情,他会对排队购票的每一个人依次进行售票,对普通成人,对学生,对儿童都依次售票.售票员需要按照一定的规则,一定顺序把参观人员一个不落的售完票,其实这个过程就是遍历,对应的就是计算机设计模式中的

  • JavaScript可迭代对象详细介绍

    目录 1.迭代器 2.迭代器接口与可迭代对象 3.自定义可迭代对象 3.1.可迭代的Range对象 3.2.使用Generator函数作为迭代器接口 3.3.可迭代的List 3.3.可迭代的迭代器 4.可迭代对象的意义 5.使用可迭代对象 6.后记 1.迭代器 迭代器是借鉴C++等语言的概念,迭代器的原理就像指针一样,它指向数据集合中的某个元素,你可以获取它指向的元素,也可以移动它以获取其它元素.迭代器类似于数组中下标的拓展,各种数据结构,如链表(List).集合(Set).映射(Map)都有

  • JavaScript的function函数详细介绍

    通过函数来封装任意多条语句,而且可以在任何地方.任何时间调用执行. 而我们的JavaScript脚本语言比较特殊,相对于C语言,它的参数是不需要数据类型加持的.返回值return,我就不过多描述,他是和 C语言通的,如果没写他就会自动返回undefined function fun(x,y){ } //写成这样就可以声明一个函数 以我的理解他就是以对象的形式来传入参数,通过对象的各项属性值(引用类型的值),来作为我的实际参数, 例如我有以下做法: function fun(x, y) { //

  • JavaScript可视化与Echarts详细介绍

    目录 一.可视化介绍 二.可视化库介绍 三.Echarts Echarts引入和使用 了解基础配置 一.可视化介绍 可视化:将数据用图表展示出来,让数据更加直观.让数据特点更加突出 应用场景:营销数据.生产数据.用户数据 二.可视化库介绍 常见的数据可视化库: D3.js:目前 Web 端评价最高的 Javascript 可视化工具库(入手难) ECharts.js:百度出品的一个开源 Javascript 数据可视化库 Highcharts.js:国外的前端数据可视化库,非商用免费,被许多国外

  • Python 类和对象详细介绍

    目录 对象 = 属性 + 方法 self是什么 公有和私有 继承 调用未绑定的父类方法 使用super函数 多重继承 组合 构造和析构 _ _init_ _(self[, …])构造方法 _ _new_ _(cls[, …])方法 _ _del_ _(self)析构方法 什么是绑定 对象 = 属性 + 方法 我们前面其实已经接触过封装的概念,把乱七八糟的数据扔进列表里面,这是一种封装,是数据层面的封装:把常用的代码段打包成一个函数,这也是一种封装,是语句层面的封装:现在我们要学习的对象,也是一种

  • Android 通过Intent使用Bundle传递对象详细介绍

    Android 通过Intent使用Bundle传递对象 Android开发中有时需要在应用中或进程间传递对象,下面详细介绍Intent使用Bundle传递对象的方法. 被传递的对象需要先实现序列化,而序列化对象有两种方式:java.io.Serializable和android.os.Parcelable Java中使用的是Serializable,而谷歌在Android使用了自定义的Parcelable. 两种序列化方式的区别: 1.在使用内存的时候,Parcelable比Serializa

  • JavaScript中的对象序列化介绍

    与Java语言一样,JavaScript中可以对对象进行序列化和反序列化,藉此对对象进行保存.ECMAScript 5标准中,JavaScript中的对象序列化是通过JSON.stringify()来实现的,而反序列化则通过JSON.parse()来实现: 复制代码 代码如下: var o = {x:1, y:29, z:42}; var s = JSON.stringify(o); console.log(s);//{"x":1,"y":29,"z&qu

  • javascript面向对象入门基础详细介绍

    什么是对象 简单点说,编程语言中的对象是对现实中事物的简化.例如,我们一个人就是一个对象,但是编程语言很难完全描述一个这样复杂的对象.所以我们必须做出简化,首先,将人简化成属性和行为的组合,然后仅仅保留对程序有意义的几个属性以及行为.例如,我们做一个统计某学校的人的身高的程序,那么我们在这个程序中就可以把人的行为省略掉,只保留行为,并且只保留身高这一个属性.这样,我们就得到了一个最简单的对象. JavaScript字符串对象 对象的属性 其实我们之前在HTML DOM中已经就是在使用对象了.例如

  • JavaScript中的ArrayBuffer详细介绍

    相信每一个 javascript 学习者,都会去了解 JS 的各种基本数据类型,数组就是数据的组合,这是一个很基本也十分简单的概念,他的内容没多少,学好它也不是件难事情.但是本文着重要介绍的并不是我们往常看到的 Array,而是 ArrayBuffer. 我写的很多东西都是因为要完成某些特定的功能而刻意总结的,可以算是备忘,本文也是如此!前段时间一直在研究 Web Audio API 以及语音通信相关的知识,内容侧重于音频流在 AudioContext 各个节点之间的流动情况,而现在要摸清楚音频

  • Javascript 链式作用域详细介绍

     Javascript 链式作用域 其实对于Javascript链式作用域的描述,包括,JS权威指南,都有些太冗长了--但是很准确:JavaScript中的函数运行在他们被定义的作用域里,而不是他们被执行的作用域里. 这句话有点难懂,但程序的设计,基本都是为了简单,便于理解的.记住JS中经典的一句话是,一切皆对象. 说白了链式作用域,其实就是Javascript的一个特性:子函数中可以访问父函数的所有变量.当然也包括全局变量window(一般的函数定义function a(){},其实都是win

  • Javascript 事件冒泡机制详细介绍

    1. 事件 在浏览器客户端应用平台,基本生都是以事件驱动的,即某个事件发生,然后做出相应的动作. 浏览器的事件表示的是某些事情发生的信号.事件的阐述不是本文的重点,尚未了解的朋友,可以访问W3school教程 进行了解,这将有助于更好地理解以下的内容 . 2.冒泡机制 什么是冒泡呢? 下面这个图片大家应该心领神会吧,气泡从水底开始往上升,由深到浅,升到最上面.在上升的过程中,气泡会经过不同深度层次的水. 相对应地:这个气泡就相当于我们这里的事件,而水则相当于我们的整个dom树:事件从dom 树的

随机推荐