博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
ES6迭代器指南
阅读量:4084 次
发布时间:2019-05-25

本文共 4266 字,大约阅读时间需要 14 分钟。

前言

EcmaScript 2015 (又称ES6)提供一个全新的迭代器的概念,它允许我们在语言层面上定义一个(有限或无限的)序列。

暂时先抛开它。我们对于for循环以及它的兄弟for-in循环,都已经十分的熟悉。后者可以被用来帮助我们理解迭代器。

for (var key in table) {  console.log(key + ' = ' + table[key]);}

对于for-in循环,它有许多的问题。但是最大的问题,便是它不保证迭代的顺序。但是当我们使用ES6迭代器时,这个问题就迎刃而解了。

for-of

for-of是ES6中的新语法,用来配合迭代器。

for (var key of table) {  console.log(key + ' = ' + table[key]);}

使用for-of,我们得到的是一个可以保证顺序的迭代。为了让一个对象可以被迭代器所迭代,对象需要实现一个“迭代协议”,即拥有一个Symbol.iterator属性。这个属性会被for-of所使用,在我们的例子中,它就是table[Symbol.iterator]

Symbol.iterator也是在ES6中新增的内容,我们会在另一篇文章中详细讨论。在这里,我们只需认为它是对象的一个特殊属性,并且永远不会和其他普通属性产生冲突。

table[Symbol.iterator]的值,必须是一个符合“迭代协议”的函数,即它需要返回一个类似于{ next: function () {} }的对象。

table[Symbol.iterator] = function () {   return {    next: function () {}  }}

然后,在for-of循环每次调用next()函数时,它需要返回一个类似于{value: …, done: [true/false]}的对象。所以,一个迭代器的完整实现类似于如下的例子:

table[Symbol.iterator] = function () {  var keys = Object.keys(this).sort();  var index = 0;  return {    next: function () {      return {        value: keys[index], done: index++ >= keys.length      };    }  }}

惰性执行

迭代器允许我们在第一次调用next()函数之后,再执行相应的逻辑。在上面的例子里,当我们调用迭代器的瞬间,我们就立刻执行了排序和取值的工作。但是,如果next()函数永远不被调用的话,我们就浪费了性能。所以让我们来优化它:

table[Symbol.iterator] = function () {  var _this = this;  var keys = null;  var index = 0;  return {    next: function () {      if (keys === null) {        keys = Object.keys(_this).sort();      }      return {        value: keys[index], done: index++ >= keys.length      };    }  }}

for-offor-in的差别

理解for-offor-in之间的差别,是十分重要的。以下是一个简单的,但是非常好的解释差别的例子:

var list = [3, 5, 7];list.foo = 'bar';for (var key in list) {  console.log(key); // 0, 1, 2, foo}for (var value of list) {  console.log(value); // 3, 5, 7}

正如所见的,for-of循环仅打印出了数组中的值,忽略了其他属性。这是因为数组的迭代器只返回其中预期的元素。

内置迭代器

StringArrayTypedArrayMapSet都是内置迭代器,因为它们的原型中都有一个Symbol.iterator方法。

var string = "hello";for (var chr of string) {  console.log(chr); // h, e, l, l, o}

解构赋值

解构操作同样也接受一个迭代器:

var hello = 'world';var [first, second, ...rest] = [...hello];console.log(first, second, rest); // w o ["r","l","d"]

无限迭代器

只要永远不返回done: true,就实现了一个无限迭代器。当然,需要极力避免出现这种情况。

var ids = {  *[Symbol.iterator]: function () {    var index = 0;    return {      next: function () {        return { value: 'id-' + index++, done: false };      }    };  }};var counter = 0;for (var value of ids) {  console.log(value);  if (counter++ > 1000) { // let's make sure we get out!    break;  }}

Generator函数

如果你还不了解ES6 generator 函数,请参考。简而言之,generator函数是当前被谈论最多的ES6特性,它是一个可以暂时退出,并且稍后重新进入继续执行的函数。在多次的进入中,它的上下文(绑定的变量)是会被保存的。generator函数自身就是一个迭代器,来看下面的例子:

function* list(value) {  for (var item of value) {    yield item;  }}for (var value of list([1, 2, 3])) {  console.log(value);}var iterator = list([1, 2, 3]);console.log(typeof iterator.next); // functionconsole.log(typeof iterator[Symbol.iterator]); // functionconsole.log(iterator.next().value); // 1for (var value of iterator) {  console.log(value); // 2, 3}

所以,我们可以使用generator函数重写我们上面的迭代器:

table[Symbol.iterator] = function* () {  var keys = Object.keys(this).sort();  for (var item of keys) {    yield item;  }}

最后

迭代器给JavaScript中的循环,generator函数和值序列(value series)带来了一个新的维度。你可以使用它,定义一个类中,它的值的排序方式,也可以用通过其来创建一个惰性的或无限的序列,等等。

如何循环一个数组?20年前 JavaScript 诞生的时候,你会这么写:

for (var index = 0; index < myArray.length; index++) {  console.log(myArray[index]);}

ES5 之后,可以使用内置的 forEach 方法:

myArray.forEach(function (value) {  console.log(value);});

这样就稍微短了点了,但是仍然有一个小的缺点:无法使用 break 语句跳出循环,或者使用 return 从函数体内返回。

要是有可以用 for 循环的语法遍历数组的所有元素,那该多好。

那么for-in 循环如何?

for (var index in myArray) { // 不要真的这样写  console.log(myArray[index]);}

这样写有以下几个问题:

  • 代码中赋值为index的值是字符串"0", "1","2"等,而不是真是的数字。由于你不想要碰到字符串计算("2" + 1 == "21")的状况,这对于编程而言是极其不方便的。
  • 循环体不仅仅会遍历数组元素,还会遍历任意其他的自定义添加的属性。例如,如果数组包含了一个不能枚举的属性 myArray.name,那么这次循环就会在 index == "name"的时候额外执行一遍。甚至数组原型链上的属性也都会被遍历到。
  • 最让人感到惊奇的是,在某些状况下,这段代码会以随机顺序循环数组元素。

简而言之,for-in 循环在设计之初就是用于普通的以字符串为 key 值的对象的语法,而不适用与数组。

强大的 for-of 循环

让我们来看看 for-of 循环:

for (var value of myArray) {  console.log(value);}

唔,上述代码就是看起来并没有很强大,对吗?好吧,我们之后会探索 for-of 循环隐藏的强大之处。就现在而言,只需要记住:

  • 这是最简洁、直白的循环数组元素的方法
  • 可以避免所有 for-in 循环的陷阱
  • 不同于 forEach(),可以使用 breakcontinue 和 return

for-in 循环用以遍历对象的属性。

for-of 循环用以遍历数据 -- 就像数组中的值一样

但这还不是所有的内容。

其他集合也支持 for-of 进行遍历

for-of 循环不仅仅支持数组的遍历。同样适用于很多类似数组的对象,例如 DOM。

它也支持字符串的遍历,会把字符串作为一组 Unicode 的字符进行遍历:

for (var chr of "

转载地址:http://qkeni.baihongyu.com/

你可能感兴趣的文章
专业和业余的区别就在于你在基础在基本功打磨练习花的时间
查看>>
通过mavlink实现自主航线的过程笔记
查看>>
Ardupilot飞控Mavlink代码学习
查看>>
这些网站有一些嵌入式面试题合集
查看>>
我觉得刷题是有必要的,不然小心实际被问的时候懵逼,我觉得你需要刷个50份面试题。跟考研数学疯狂刷卷子一样!
查看>>
我觉得嵌入式面试三要素:基础吃透+项目+大量刷题,缺一不可。不刷题是不行的。而且得是大量刷,刷出感觉套路,别人做题都做得是固定题型套路条件反射了,你还在那慢慢理解慢慢推是不行的,也是考研的教训。
查看>>
相机标定的目的:获取摄像机的内参和外参矩阵(同时也会得到每一幅标定图像的选择和平移矩阵),内参和外参系数可以对之后相机拍摄的图像就进行矫正,得到畸变相对很小的图像。
查看>>
现在来看,做个普罗米修斯的docker镜像对我而言并不难,对PX4仿真环境配置也熟悉了。
查看>>
删除docker容器和镜像的命令
查看>>
VINS-Fusion Intel® RealSense™ Depth Camera D435i
查看>>
使用Realsense D435i运行VINS-Fusion并建图
查看>>
gazebo似乎就是在装ROS的时候一起装了,装ROS的时候选择的是ros-melodic-desktop-full的话。
查看>>
React + TypeScript 实现泛型组件
查看>>
TypeScript 完全手册
查看>>
React Native之原理浅析
查看>>
Git操作清单
查看>>
基础算法
查看>>
前端面试
查看>>
React Hooks 异步操作踩坑记
查看>>
聊聊编码那些事,顺带实现base64
查看>>