Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Understanding the For…of Loop In JavaScript #33

Open
icepy opened this issue May 26, 2019 · 0 comments
Open

Understanding the For…of Loop In JavaScript #33

icepy opened this issue May 26, 2019 · 0 comments
Assignees
Labels
翻译完成 Nice work 译文 翻译

Comments

@icepy
Copy link
Contributor

icepy commented May 26, 2019

原文地址: https://blog.bitsrc.io/understanding-the-for-of-loop-in-javascript-8aded97d7ef8

原文作者: https://blog.bitsrc.io/@kurtwanger40

理解 JavaScript 中的循环

JavaScript的世界中,我们可以使用很多种循环表达式:

  • while 表达式
  • do...while 表达式
  • for 表达式
  • for...in 表达式
  • for...of 表达式

所有这些表达式都有一个基本的功能:它们会重复一件事情直到一个具体的条件出现。

在这篇文章中,我们将深入 for...of 表达式,去了解它是如何工作的,以及在我们的应用中可以使用它来优化代码的地方。

for…of

for...of 是一种 for 表达式,用来迭代 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

你知道如果我们使用for循环,我们将必须使用数学和逻辑去判断何时我们将会达到myname的末尾并且停止循环。但是正如你所见的,使用for...of循环之后,我们将会避免这些烦人的事情。

for...of有以下通用的使用方法:

for ( variable of iterable) {
    //...
}

variable - 保存每次迭代过程中的迭代对象的属性值
iterable - 我们进行迭代的对象

Iterables(可迭代的对象) and Iterator(迭代器)

for...of循环的定义中,我们说它是“迭代 iterables(iterable objects)”。这个定义告诉我们for...of循环只能用于可迭代对象。

那么, 什么是可迭代的对象(iterables)?

简单来说的话,可迭代对象(Iterables)是可以用于迭代的对象。在ES6中增加了几个特性。这些特性包含了新的协议,其中就有迭代器(Iterator)协议和可迭代(Iterable)协议。

参考MDN的描述,“可迭代协议允许JS对象定义或者修改它们的迭代行为,比如哪些值可以被for...of循环到。”同时“为了变成可迭代的,对象必须实现@@iterator方法,这意味着这个对象(或者其原型链上的对象)必须包含一个可以使用Symbol.iterator常量访问的@@iterator的属性”

说人话就是,对于可以使用for...of循环的对象来说,它必须是可迭代的,换句话就是它必须有@@iterator属性。这就是可迭代协议。

所以当对象有@@iterator属性的时候就可以被for...of循环迭代,@@iterator方法被for...of调用,并且返回一个迭代器。

同时,迭代器协议定义了一种对象中的值如何返回的方式。一个迭代器对象必须实现next方法,next 方法需要遵循以下准则:

  • 必须返回一个包含 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 方法回返回一个迭代器,for...of 循环正是使用这个迭代器去循环操作目标对象从而得到值。因此,如果一个对象没有@@iterator方法同时这个返回值是一个迭代器,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

注意,Object 不是可迭代的。如果我们尝试使用for...of去迭代对象的属性:

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, 这意味着@@iterator属性存在,并且是函数类型。如果我们在 Object 上面进行尝试:

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

哇!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

字符串也是可迭代的。

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)
}
// 打印结果是:
// 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)
}
// 打印结果是:
// ["surname", "Chidume"]
// ["firstname","Nnamdi"]
for(const [key, value] of map) {
    log(`key: ${key}, value: ${value}`)
}
// 打印结果是:
// key: surname, value: Chidume
// key: firstname, value: Nnamdi

for…of: Set类型

const set = new Set(["Chidume","Nnamdi"])
for(const a of set) {
    log(a)
}
// 打印结果是:
// Chidume
// Nnamdi

for…of: TypedArray类型

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

for…of: arguments对象

arguments对象是可遍历的吗?我们先来看:

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

答案出来了。如果进一步探讨,arguments其实是IArguments类型的对象,而且实现了IArguments接口的class都有一个@@iterator属性,使得arguments对象可遍历。

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

for…of: 自定义可遍历对象(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]赋予它一个@@iterator属性,然后创建一个返回遍历器的方法。

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

记住,遍历器一定要有一个next()方法。

在next方法里面,我们实现了在for...of遍历过程中会返回的值,这个过程很清晰。

Let’s test this our obj against a for..of to see what will happen:

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

耶!成功了!

把Object和普通对象(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()得到的object的所有属性。在next方法里面,我们只需要返回properties里面的每一个值,并且通过更新作为索引的count变量来获取下一个值。当count达到properties的长度的时候,就把done设为true,遍历就结束了。

用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)
}
900
800

也成功了!! :)

所以我们可以把这个添加到polyfill里面,然后就可以在app里面使用for...of来遍历对象了。

在ES6的类(class)中使用for…of

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

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

Profiles类有一个profile属性,包含一个用户列表。当我们需要在app中用for...of来展示这个列表的时候,如果这样做:

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

显然是不行的。

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

为了把profiles变成可遍历,请记住以下规则:

  • 这个对象一定要有@@iterator属性。
  • 这个@@iterator的方法一定要返回一个遍历器(iterator).
  • 这个iterator一定要实现next()方法。

我们通过一个熟悉的常量[Symbol.iterator]来定义这个@@iterator

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 引入了一个新的语法,可以循环遍历一个 Promise 数组,它就是 for-await-of 和新的 Symbol Symbol.asyncIterator

iterable 中的 Symbol.asyncIterator 函数需要返回一个返回 Promise 的迭代器。

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

[Symbol.iterator][Symbol.asyncIterator] 的区别在于前者返回的是 { value, done } 而后者返回的是一个 Promise,只不过当 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 才返回真正的值。

要遍历 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

Conclusion

在这篇文章中我们深入研究了 for...of 循环,我们首先定义理解什么是 for...of,然后看看什么是可迭代的。for...of 为我们节省了许多复杂性和逻辑,并有助于使我们的代码看起来很清晰易读,如果你还没有尝试过,我认为现在正是时候。

@icepy icepy added the 译文 翻译 label May 26, 2019
@icepy icepy added the 翻译完成 Nice work label Jun 19, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
翻译完成 Nice work 译文 翻译
Projects
None yet
Development

No branches or pull requests

6 participants