ES6之遍历语法

作者: DC_er | 来源:发表于2017-06-07 16:06 被阅读8692次

    如何使用遍历的

    我们暂且以数组为例,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属性才能使用。

    相关文章

      网友评论

      本文标题:ES6之遍历语法

      本文链接:https://www.haomeiwen.com/subject/ppoofxtx.html