ES6 借鉴 C++、Java、C# 和 Python 语言,引入了for...of循环,作为遍历所有数据结构的统一的方法。

一个数据结构只要部署了Symbol.iterator属性,就被视为具有 iterator 接口,就可以用for...of循环遍历它的成员。也就是说,for...of循环内部调用的是数据结构的Symbol.iterator方法。

for...of循环可以使用的范围包括数组、Set 和 Map 结构、某些类似数组的对象(比如arguments对象、DOM NodeList 对象)、Generator 对象,以及字符串。

数组

数组原生具备iterator接口(即默认部署了Symbol.iterator属性),可以使用for...of循环

const arr = [1, 3, 5, 7, 9];

for (let item of arr) {
    console.log(item)  // 1 3 5 7 9
}

上面代码表明for...of循环读取键值。如果要通过for...of循环,获取数组的索引,可以借助数组实例的entries方法和keys方法。

获取键名

const arr = [1, 3, 5, 7, 9]

for (let v of arr.keys()) {
    console.log(v) // 0 1 2 3 4
}

获取键名和键值

const arr = [1, 3, 5, 7, 9]

for(let [index, item] of arr.entries()) {
    console.log(index, item)
}

/*
    0  1
    1  3
    2  5
    3  7
    4  9
*/

Set和Map结构

Set 和 Map 结构也原生具有 Iterator 接口,可以直接使用for...of循环。

const engines = new Set([1, 3, 5, 3, 5]) // Set结构会去重

for (let v of engines) {
    console.log(v)
}
// 1
// 3
// 5

// Map
const es6 = new Map()
es6.set('a', 6)
es6.set('b', 8)

for (let [name, value] of es6) {
    console.log(name + ": " + value);
}

// a: 6
// b: 8

类似数组的对象

类似数组的对象包括好几类。下面是for...of循环用于字符串、DOM NodeList 对象、arguments对象的例子。

// 字符串
let str = 'hello'

for (let v of str) {
    console.log(v) // h e l l o
}

// DOM NodeList对象
let paras = document.querySelectorAll("div")

for (let div of paras) {
  div.classList.add("test")
}

// arguments对象
function printArgs() {
  for (let x of arguments) {
    console.log(x)
  }
}
printArgs('a', 'b')
// 'a'
// 'b'

对象

对于普通的对象,for...of结构不能直接使用,会报错,必须部署了 Iterator 接口后才能使用。

遍历对象可使用for...in循环。或者使用Object.keys方法将对象的键名生成一个数组,然后遍历这个数组。

let obj = {
  edition: 6,
  committee: "TC39",
  standard: "ECMA-262"
};

for (let e in obj) {
  console.log(e);
}

// edition
// committee
// standard


// 使用Object.keys方法
for (let key of Object.keys(obj)) {
  console.log(obj[key]);
}
// 6
// TC39
// ECMA-262

与其他遍历语法的比较

以数组为例,JavaScript 提供多种遍历语法。最原始的写法就是for循环。

let arr = [1, 3, 5]

for (let i=0; i< arr.length; i++) {
    console.log(arr[i])
}

这种写法比较麻烦,因此数组提供内置的forEach方法。

let arr = [1, 3, 5]

arr.forEach(item => {
    console.log(item)
})

这种写法的问题在于,无法中途跳出forEach循环,break命令或return命令都不能奏效。

for...in循环可以遍历数组的键名。

let arr = [1, 3, 5]

for (let index in arr) {
    console.log(arr[index])
}

for...in循环有几个缺点。

  • 数组的键名是数字,但是for...in循环是以字符串作为键名“0”、“1”、“2”等等。
  • for...in循环不仅遍历数字键名,还会遍历手动添加的其他键,甚至包括原型链上的键。
  • 某些情况下,for...in循环会以任意顺序遍历键名。

总之,for...in循环主要是为遍历对象而设计的,不适用于遍历数组。

for...of循环相比上面几种做法,有一些显著的优点。

  • 有着同for...in一样的简洁语法,但是没有for...in那些缺点。
  • 不同于forEach方法,它可以与breakcontinuereturn配合使用。
  • 提供了遍历所有数据结构的统一操作接口。