ES6之遍历语法

2018/01/10 10:56
阅读数 16

 

如何使用遍历的

我们暂且以数组为例,javascript提供了多种遍历数组的方法,最开始我们可能习惯使用for循环:

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

这种写法比较麻烦,于是数组内提供了forEach方法:

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

这段代码看起来更加简洁,但这种方法也有一个小缺陷:你不能使用break语句中断循环,也不能使用return语句返回到外层函数。
for...in循环(其实for...in循环是为了遍历对象而设计的,并不适合遍历数组,切记!切记!切记!重要的事情说三遍!!!)

for (var index in myArray) { // 千万别这样做
    console.log(myArray[index]);
}

for...in循环便利数组绝对是一个糟糕的选择,原因如下:

  • 在这段代码中,赋给index的值不是实际的数字,而是字符串“0”、“1”、“2”,此时很可能在无意之间进行字符串算数计算,例如:“2” + 1 == “21”,这给编码过程带来极大的不便。

  • 作用于数组的for-in循环体除了遍历数组元素外,还会遍历自定义属性。举个例子,如果你的数组中有一个可枚举属性myArray.name,循环将额外执行一次,遍历到名为“name”的索引。就连数组原型链上的属性都能被访问到。

  • 最让人震惊的是,在某些情况下,这段代码可能按照随机顺序遍历数组元素。

  • 简而言之,for-in是为普通对象设计的,你可以遍历得到字符串类型的键,因此不适用于数组遍历。

强大的for-of循环

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

优点:

  • 这是最简洁、最直接的遍历数组元素的语法
  • 这个方法避开了for-in循环的所有缺陷
  • 与forEach()不同的是,它可以正确响应break、continue和return语句

其实不仅可以便利数组,ES6新增加的数据结构set和map也可以使用for-of循环来进行遍历

var engines = new Set(["Gecko", "Trident", "Webkit", "Webkit"]);
for (var e of engines) {
  console.log(e);
}
// Gecko
// Trident
// Webkit

var es6 = new Map();
es6.set("edition", 6);
es6.set("committee", "TC39");
es6.set("standard", "ECMA-262");
for (var [name, value] of es6) {
  console.log(name + ": " + value);
}
// edition: 6
// committee: TC39
// standard: ECMA-262

上面代码演示了如何遍历 Set 结构和 Map 结构。值得注意的地方有两个,首先,遍历的顺序是按照各个成员被添加进数据结构的顺序。其次,Set 结构遍历时,返回的是一个值,而 Map 结构遍历时,返回的是一个数组,该数组的两个成员分别为当前 Map 成员的键名和键值。
甚至使用for-of循环亦可以遍历字符串:

for (var chr of "") {
  alert(chr);
}

深入理解

1.Iterator(遍历器)的概念

遍历器(Iterator)就是这样一种机制。它是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署Iterator接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)。

Iterator的作用有三个:一是为各种数据结构,提供一个统一的、简便的访问接口;二是使得数据结构的成员能够按某种次序排列;三是ES6创造了一种新的遍历命令for...of循环,Iterator接口主要供for...of消费。

Iterator的遍历过程是这样的。

  • (1)创建一个指针对象,指向当前数据结构的起始位置。也就是说,遍历器对象本质上,就是一个指针对象。

  • (2)第一次调用指针对象的next方法,可以将指针指向数据结构的第一个成员。

  • (3)第二次调用指针对象的next方法,指针就指向数据结构的第二个成员。

  • (4)不断调用指针对象的next方法,直到它指向数据结构的结束位置。

每一次调用next方法,都会返回数据结构的当前成员的信息。具体来说,就是返回一个包含value和done两个属性的对象。其中,value属性是当前成员的值,done属性是一个布尔值,表示遍历是否结束。

在ES6中,有些数据结构原生具备Iterator接口(比如数组),即不用任何处理,就可以被for...of循环遍历,有些就不行(比如对象)。原因在于,这些数据结构原生部署了Symbol.iterator属性,另外一些数据结构没有。凡是部署了Symbol.iterator属性的数据结构,就称为部署了遍历器接口。调用这个接口,就会返回一个遍历器对象。

在ES6中,有三类数据结构原生具备Iterator接口:数组、某些类似数组的对象、Set和Map结构。对于这三类数据结构,不用自己写遍历器生成函数,for...of循环会自动遍历它们。除此之外,其他数据结构(主要是对象)的Iterator接口,都需要自己在Symbol.iterator属性上面部署,这样才会被for...of循环遍历。

2.计算生成的数据结构

有些数据结构是在现有数据结构的基础上,计算生成的。比如,ES6的数组、Set、Map 都部署了以下三个方法,调用后都返回遍历器对象。

  • entries() 返回一个遍历器对象,用来遍历[键名, 键值]组成的数组。对于数组,键名就是索引值;对于 Set,键名与键值相同。Map 结构的 Iterator 接口,默认就是调用entries方法。
  • keys() 返回一个遍历器对象,用来遍历所有的键名。
  • values() 返回一个遍历器对象,用来遍历所有的键值。

这三个方法调用后生成的遍历器对象,所遍历的都是计算生成的数据结构。

 

 

屏幕快照 2017-06-07 下午3.46.23.png


值得注意的是ES6中的新数据结构set和map原生提供的除了这三个遍历器生成函数之外,还有一个遍历方法forEach()。切记不要将遍历器生成函数和遍历方法混为一谈

 

let map = new Map([
  ['f','no'],
  ['t','true']
]);
for (let[key,value] of map.entries()){
    console.log(key,value);
}

//等同于使用map.entries()
for (let[key,value] of map){
    console.log(key,value);
}

3.原生javascript对象的遍历

前面我们提到过那些没有部署过Symbol.iterator属性的数据结构也即是不具备Iterator接口的的对象是不能使用for...of方法遍历的。通过Gnenrator函数为这类对象加上这个Iterator接口就可以了。

function* objectEntries(obj) {
  let propKeys = Reflect.ownKeys(obj);

  for (let propKey of propKeys) {
    yield [propKey, obj[propKey]];
  }
}

let jane = { first: 'Jane', last: 'Doe' };

for (let [key, value] of objectEntries(jane)) {
  console.log(`${key}: ${value}`);
}
// first: Jane
// last: Doe

上面代码中,对象jane原生不具备 Iterator 接口,无法用for...of遍历。这时,我们通过 Generator 函数objectEntries为它加上遍历器接口,就可以用for...of遍历了。加上遍历器接口的另一种写法是,将 Generator 函数加到对象的Symbol.iterator属性上面。

function* objectEntries() {
  let propKeys = Object.keys(this);

  for (let propKey of propKeys) {
    yield [propKey, this[propKey]];
  }
}

let jane = { first: 'Jane', last: 'Doe' };

jane[Symbol.iterator] = objectEntries;

for (let [key, value] of jane) {
  console.log(`${key}: ${value}`);
}
// first: Jane
// last: Doe

除了for...of循环以外,扩展运算符(...)、解构赋值和Array.from方法内部调用的,都是遍历器接口。这意味着,它们都可以将 Generator 函数返回的 Iterator 对象,作为参数。

function* numbers () {
  yield 1
  yield 2
  return 3
  yield 4
}

// 扩展运算符
[...numbers()] // [1, 2]

// Array.from 方法
Array.from(numbers()) // [1, 2]

// 解构赋值
let [x, y] = numbers();
x // 1
y // 2

// for...of 循环
for (let n of numbers()) {
  console.log(n)
}
// 1
// 2

总结

总而言之,ES6提供一个强大的遍历方法for-of,对于那些原生具备Iterator接口的数据结构,可以直接使用,而不具备具备Iterator接口的对象,需要部署Symbol.iterator属性才能使用。



作者:大闯仔
链接:https://www.jianshu.com/p/b9e31305bcbb
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

展开阅读全文
ES6
加载中

作者的其它热门文章

打赏
0
2 收藏
分享
打赏
0 评论
2 收藏
0
分享
返回顶部
顶部