译文理解 JavaScript 中的 for…of 循环

Posted BeAce

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了译文理解 JavaScript 中的 for…of 循环相关的知识,希望对你有一定的参考价值。


原文链接: Understanding the For…of Loop In javascript

在  JavaScript 中,我们有很多循环语句。

  • while 语句

  • do...while 语句

  • for 语句

  • for...in 语句

  • for...of 语句

所有这些语句都有一个最基础的共同功能:他们会一直重复,直到到达某个条件。

在这篇文章,我们将研究 for…of 表达式来查看它是如何工作的并且如何在 JavaScript 应用中利用它书写更好的代码。

技巧:使用 Bit  构造一个 js 应用是非常快速的。使用 Bit 可以快速的在不同项目中分享和复用公共组件,可以在团队中分享,就像玩乐高那样简单。你可以免费使用它。

for…of

for…offor 语句的一种循环 iterables(iterable objects)(可迭代对象) ,直到它到达它的终止条件的一种形式。

下面是一个简答的例子。

let arr = [2,4,6,8,10]
for(let a of arr) {    log(a) }
// It logs:// 2// 4// 6// 8// 10

我们可以用比 for 语句更少的代码来迭代数组 arr

let myname = "Nnamdi Chidume"

for (let a of myname) {    log(a) }
// It logs:
// N
// n
// a
// m
// d
// i
//
// C
// h
// i
// d
// u
// m
// e

你应该直到,如果使用循环,我们不得不使用相关的数学知识和逻辑来计算到达循环 myname 的终点并推出程序。但是 for…of 语句帮我们解决了这个头疼的问题。

正如我们看到的,for..of  一般有以下定义:

for ( variable of iterable) {    
 //...
}
  • variable 保存了每次迭代的 iterable 中对象的值

  • iterable 是要被迭代的对象

Iterables and Iterator (迭代对象和迭代器)

for…of 循环的定义中,我们说它“循环遍历可迭代对象(iterable objects)”。因此有了上述定义就意味着 for...of 只能被用于可迭代的对象,否则不能使用 for...of 循环。

那么,什么是可迭代对象(Iterables)呢?

简单的说,可迭代对象是可以执行迭代的对象。在 ECMASCRIPT 2015  中加入了一个 coupla。这些新增内容是新协议。协议中包括Iterator协议和Iterable协议。

根据 Mozilla 开发者说,“iterable 协议允许 JavaScript 对象去定义或者自定义迭代行为,例如在 for..of 构造中循环的值。” 并且,“为了可迭代,对象必须实现 @@iterator 方法,意味着对象必须包含(或者在其原型链中包含)一个 @@interator 的属性,这个属性可以通过常量Symbol.iterator获得”

这实际上意味着,如果想要你的对象可以使用 for…of 进行迭代,它必须是可迭代对象(interables),换言之,必须具有 @@iterator 属性。这样才符合可迭代协议(iterable protocol)。

因此,当对对象具有 @@iterator 时,它就可以被 for…of@@interator 方法由 for...of 调用。@@interator 返回一个迭代器(interator)。

现在,可迭代协议定义了一种可以从对象中返回值的流的方式。迭代器必须实现 next 方法。next 方法需要遵守以下几个规则。

  • 必须返回一个带有 done 属性的对象。类似value {done, value}

  • done 属性是一个布尔值,表示是否到达流的末尾

  • value 属性 保存当前周期的值

举个例子:

const createIterator = function () {    
 var array = ['Nnamdi','Chidume']    
   return  {      next: function() {            
     if(this.index == 0) {                
       this.index++                
       return {
         value: array[this.index], done: false
       }      }            
     if(this.index == 1) {                
       return {
         value: array[this.index], done: true
       }      }    },    index: 0  } }
const iterator = createIterator() log(iterator.next()) // Nnamdi
log(iterator.next()) // Chidume

基本上,@@iterator 返回了一个迭代器(interator),for…of 用来循环对象并且获取其值。因此,如果一个对象不包含@@interator 方法并且(或者)返回的不是一个迭代器(interator),for…of 语句不能正常迭代。

const nonIterable = //...
for( let a of nonIterable) {
 // ...
}
for( let a of nonIterable) {               ^
TypeError: nonIterable is not iterable

例如有一些可迭代对象:

  • String

  • Map

  • TypedArray

  • Array

  • Set

  • Generator

注意这里少了 ObjectObject不是一个可迭代对象。如果我们尝试使用 for…of 循环去遍历 Object 中的属性:

let obj {
    firstname: "Nnamdi",
    surname: "Chidume"}for(const a of obj) {
    log(a)
}

它将会抛出以下错误:

for(const a of obj) {
               ^TypeError: obj is not iterable

我们可以通过以下方式来检查一个对象是否为可迭代对象

const str = new String('Chidume');
log(typeof str[Symbol.iterator]);
function

可以看到,它打印出了 function,这表明在 String 存在@@iterator 属性。如果我们尝试 Object:

const obj = { surname: "Chidume"}
log(typeof obj[Symbol.iterator]);
undefined

Woo!!  undefined 意味着不存在。

for…of: Array

数组是可迭代对象。

log(typeof new Array("Nnamdi", "Chidume")[Symbol.iterator]);
// function

这也是为什么我们可以使用 for…of 来遍历它的原因。

const arr = ["Chidume", "Nnamdi", "loves", "JS"]
for(const a of arr) {    log(a) }
// It logs:
// Chidume
// Nnamdi
// loves
// JS
const arr = new Array("Chidume", "Nnamdi", "loves", "JS")
for(const a of arr) {    log(a) }
// It logs:
// Chidume
// Nnamdi
// loves
// JS

for…of: String

String 也是可迭代对象。

const myname = "Chidume Nnamdi"
for(const a of myname) {    log(a) }
// It logs:// C// h// i// d// u// m// e// // N// n// a// m// d// i
const str = new String("The Young")
for(const a of str) {    log(a) }
// It logs:// T// h// e// // Y// o// u// n// g

for…of: Map

const map = new Map([["surname", "Chidume"],["firstname","Nnamdi"]])
for(const a of map) {    log(a) }
// It logs:// ["surname", "Chidume"]// ["firstname","Nnamdi"]
for(const [key, value] of map) {    log(`key: ${key}, value: ${value}`) }
// It logs:// key: surname, value: Chidume// key: firstname, value: Nnamdi

for…of: Set

const set = new Set(["Chidume","Nnamdi"])
for(const a of set) {    log(a) }
// It logs:// Chidume// Nnamdi

for…of: TypedArray

const typedarray = new Uint8Array([0xe8, 0xb4, 0xf8, 0xaa]);
for (const a of typedarray) {  log(a); }
// It logs:// 232// 180// 248// 170

for…of: arguments

arguments 是可迭代对象吗?让我们来检验一下:

// testFunc.js
function testFunc(arg) {    log(typeof arguments[Symbol.iterator]) } testFunc() $ node testFuncfunction

事实证明,它是的。如果我们更进一步调查,arguments 实际上是IArguments类型,并且实现IArguments接口的类具有@@iterator属性,该属性使参数可迭代。

// testFunc.js
function testFunc(arg) {    log(typeof arguments[Symbol.iterator])    
   for(const a of arguments) {        log(a)    } } testFunc("Chidume")// It:// Chidume

for…of: Custom Iterables

正如我们上面所说的,我们创建一个自定义的可迭代对象使得 for…of 可以遍历它。

var obj = {}
obj[Symbol.iterator] = function() {    
var array = ["Chidume", "Nnamdi"]    
   return {      next: function() {            
       let value = null        if (this.index == 0) {          value = array[this.index]                
         this.index++                    
         return { value, done: false }        }            
       if (this.index == 1) {          value = array[this.index]                
         this.index++                    
         return { value, done: false }        }            
       if (this.index == 2) {              
         return { done: true }        }    },    index: 0  } };

我创建了一个对象 obj并且使他可迭代,我通过 [Symbol.iterator] 增加了@@interator 属性。然后, function 最终返回一个迭代器(interator)。

//...
return {    next: function() {...} }
//...

记住,一个迭代器需要有一个 next 函数。

next 函数中,我定义的值将在迭代执行期间返回给 for…of。看上面的代码,你可以清楚的看到我做了什么。

让我们来测试一下 是用 for...of 遍历 obj 会得到什么:

// customIterableTest.js
//...
for (let a of obj) {    log(a) } $ node customIterableTest Chidume Nnamdi

是的,它正确执行了:)!

使对象和普通对象可迭代

普通对象(Plain objects)不可迭代,而且Object中的对象也不可迭代。

我们可以通过使用自定义迭代器将 @@iterator 添加到Object.prototype来实现。

Object.prototype[Symbol.iterator] = function() {    
 let properties = Object.keys(this)    
 let count = 0  let isdone = false  let next = () => {        
   let value = this[properties[count]]        
   if (count == properties.length) {        isdone = true    }    count++        
   return { done: isdone, value }  }    
 return { next } }

properties 变量保存使用 Object.keys() 获取的对象的属性。在 next 函数中,我们简单地返回了从 properties 中获取的每一个值并且更新了 count,从而使用 count作为索引从属性中获取下一个值。
count 等于properties 长度时,迭代就会停止。

通过 Object 来进行测试:

let o = new Object()
o.s = "SK"o.me = 'SKODA'
for (let a of o) {    log(a) } SK SKODA

成功执行了!

对于普通对象:

let dd = {
    shit: 900,
    opp: 800}for (let a of dd) {
    log(a)
}
900800

这样我们就可以将它添加为 polyfill ,在应用中使用 for..of

Using for…of on ES6 classes

我们可以使用 for..of 来遍历类实例中的数据列表。

class Profiles {
  constructor(profiles) {        
   this.profiles = profiles  } }
const profiles = new Profiles([  {    firstname: "Nnamdi",    surname: "Chidume"  },  {    firstname: "Philip",    surname: "David"  } ])

Profiles 具有一个 profile 属性包含一个数组 users 。我们可能需要在应用程序中使用 for...of展示此数据。如果我们这样做:

//...
for(const a of profiles) {    log(a) }

显然,它不能正常工作。

for(const a of profiles) {
               ^
TypeError: profiles is not iterable

需要让 profiles 迭代对象具有以下规则

  • 对象必须包含 @@iterator 属性

  • @@iterator 必须返回一个迭代器 interator

  • iterator 迭代器必须有 next  方法的实现

我们通过 [Symbol.iterator] 来定义了 @@interator

class Profiles {
  constructor(profiles) {            
   this.profiles = profiles  }  [Symbol.iterator]() {            
   let props = this.profiles            
   let propsLen = this.profiles.length            
   let count = 0        return {          next: function() {                    
           if (count < propsLen) {                        
             return { value: props[count++], done: false }            }                    
           if (count == propsLen) {
             return { done: true }            }        }     }  } }

然后我们执行以下代码:

//...
for(const a of profiles) {    log(a) } $ node profile.js { firstname: 'Nnamdi', surname: 'Chidume' } { firstname: 'Philip', surname: 'David' }

profiles 属性正确展示了。

异步迭代器(Async Iterator)

ECMAScript 2018引入了一个新的构造,以便能够遍历Promises数组,这个新构造是 for-await-of 和一个新的 Symbol Symbol.asyncIterator

Symbol.asyncIterator 函数是一个可以返回 promise 迭代器的可迭代对象。

const f = {
  [Symbol.asyncIterator]() {       
   return new Promise(...)  } }

[Symbol.iterator][Symbol.asyncIterator] 的区别在于前者返回 { value, done }, 后者返回 promise resolve { value, done }

上面的f 函数类似下面这样:

const f = {
  [Symbol.asyncIterator]() {        
   return {      next: function() {                
       if (this.index == 0) {                  
          this.index++                      
          return new Promise(res => res({ value: 900, done: false }))        }                
        return new Promise(res => res({ value: 1900, done: true }))     },     index: 0   } } }

f 是异步可迭代的对象。你可以看到它返回了一个 promise, Promise  的 resolve 方法在每一个迭代中都返回了 value

要迭代f,我们将不会使用 for..of 而是我们将使用新的 for-await-of这样:

// ...
async function fAsyncLoop(){    
 for await (const _f of f) {    log(_f)  } } fAsyncLoop() $ node fAsyncLoop.js
900

我们可以使用 for-await-of 来循环一个 Promise 数组。

const arrayOfPromises = [    
 new Promise(res => res("Nnamdi")),    
 new Promise(res => res("Chidume")) ] async function arrayOfPromisesLoop(){    
 for await (const p of arrayOfPromises) {    log(p)  } } arrayOfPromisesLoop() $ node arrayOfPromisesLoop.js Nnamdi Chidume

总结

在这篇文章中,我们深入挖掘了for... of循环。我们首先定义for..of是什么,然后继续看看是什么使得可迭代。然后,我们查看了JS中完整的可迭代列表,并浏览了它们中的每一个,以了解如何使用它们的循环。

就像我在开始时说的那样,for..of 为我们节省了许多复杂性和逻辑,并有助于使我们的代码看起来更清晰,更易读。如果您还没有尝试过这种令人敬畏的for循环变异,我认为现在是时候这样做了。

如果您对此或我应该添加,更正或删除的任何问题有任何疑问,请随时在下面发表评论,以及任何事情或DM我。谢谢阅读! :)

以上是关于译文理解 JavaScript 中的 for…of 循环的主要内容,如果未能解决你的问题,请参考以下文章

精心收集的 48 个 JavaScript 代码片段,仅需 30 秒就可理解

理解ECMAScript 6中的for...of循环

原译文理解storm拓扑并行度

深入理解枚举属性与for-in和for-of

javascript中 for in for forEach for of Object.keys().

就声明而言,Javascript for of 循环中的 let 和 const 之间没有区别吗?