美文网首页
从Iterator到map、set和Generator

从Iterator到map、set和Generator

作者: 灯不梨喵 | 来源:发表于2017-12-25 00:24 被阅读0次

    在ES6的教程里先说了Map和Set再说Iteratator,尽管我们理解遍历的意思是什么,但是学习Iterator的原理和结构有助于我们进一步理解有应用到遍历器的语法。

    Iterator原理

    Iterator 的遍历过程是这样的。
    (1)创建一个指针对象,指向当前数据结构的起始位置。也就是说,遍历器对象本质上,就是一个指针对象。
    (2)第一次调用指针对象的next方法,可以将指针指向数据结构的第一个成员。
    (3)第二次调用指针对象的next方法,指针就指向数据结构的第二个成员。
    (4)不断调用指针对象的next方法,直到它指向数据结构的结束位置。

    默认的Iterator

    存在这样的一个默认接口:如果一个数据结构拥有属性Symbol.iterator,则这个数据结构是可遍历的。属性Symbol.iterator对应一个函数,该函数规定了遍历的规则。执行这个函数,就会返回一个遍历器。

    const obj = {
      [Symbol.iterator] : function () {
        return {
          next: function () {
            return {
              value: 1,
              done: true
            };
          }
        };
      }
    };
    

    该例子中常量obj是可遍历的,因为它拥有Symbol.iterator属性,该属性对应一个函数,函数返回一个拥有next属性(对应一个返回value和done的函数)的对象。所以当我们调用obj.next()的时候,就会返回对应的value和done的值。

    原生Iterator

    以下是原生拥有Iterator接口的数据结构:
    Array
    Map
    Set
    String
    TypedArray
    函数的 arguments 对象
    NodeList 对象
    对于原生部署 Iterator 接口的数据结构,不用自己写遍历器生成函数,for...of循环会自动遍历它们。除此之外,其他数据结构(主要是对象)的 Iterator 接口,都需要自己在Symbol.iterator属性上面部署(部署Symbol.iterator的函数,还有next函数),这样才会被for...of循环遍历。部署还有一种取巧的方法,就是类似数组的对象可以直接借用Array.prototype[Symbol.iterator]。

    return方法和throw方法

    return方法和throw方法在部署中是可选的。
    return方法针对以下情况执行:
    在for...of循环中,
    1.出错 2.break语句 3.continue语句

    for...of和for...in

    for...in循环,只能获得对象的键名,不能直接获取键值。ES6 提供for...of循环,允许遍历获得键值。

    var arr = ['a', 'b', 'c', 'd'];
    
    for (let a in arr) {
      console.log(a); // 0 1 2 3
    }
    
    for (let a of arr) {
      console.log(a); // a b c d
    }
    

    for...of循环调用遍历器接口,数组的遍历器接口只返回具有数字索引的属性。这一点跟for...in循环也不一样。

    let arr = [3, 5, 7];
    arr.foo = 'hello';
    
    for (let i in arr) {
      console.log(i); // "0", "1", "2", "foo"
    }
    
    for (let i of arr) {
      console.log(i); //  "3", "5", "7"
    }
    

    iterator的使用

    iterator主要应用于Generator,但在此之前还要温习一下具备原生接口的Set和Map。

    Set和weakset

    Set

    创建Set
    const set = new Set()
    也可以传入数组初始化set
    const set = new Set([1,2,3,4])
    set里面没有重复的值,传入重复的值会被排除掉。

    Set 实例的属性和方法

    Set 结构的实例有以下属性。
    Set.prototype.constructor:构造函数,默认就是Set函数。
    Set.prototype.size:返回Set实例的成员总数。
    操作方法:
    add(value):添加某个值,返回 Set 结构本身。
    delete(value):删除某个值,返回一个布尔值,表示删除是否成功。
    has(value):返回一个布尔值,表示该值是否为Set的成员。
    clear():清除所有成员,没有返回值。
    遍历方法:
    keys():返回键名的遍历器
    values():返回键值的遍历器
    entries():返回键值对的遍历器
    forEach():使用回调函数遍历每个成员

    Weakset

    const ws = new WeakSet();
    WeakSet 可以接受一个数组或类似数组的对象作为参数。(实际上,任何具有 Iterable 接口的对象,都可以作为 WeakSet 的参数。)该数组的所有成员,都会自动成为 WeakSet 实例对象的成员。

    WeakSet.prototype.add(value):向 WeakSet 实例添加一个新成员。
    WeakSet.prototype.delete(value):清除 WeakSet 实例的指定成员。
    WeakSet.prototype.has(value):返回一个布尔值,表示某个值是否在

    WeakSet 没有size属性,没有办法遍历它的成员。

    Map和weakmap

    Map

    Map是一种具有键值对的数据结构,相对于Object来说,它的优点是键的范围不限于字符串,各种类型的值(包括对象)都可以当作键。也就是说,Object 结构提供了“字符串-值”的对应,Map 结构提供了“值-值”的对应。

    const m = new Map();
    const o = {p: 'Hello World'};
    //Map { { p: 'hello world' } => 'content' }
    m.set(o, 'content')
    m.get(o) // "content"
    
    m.has(o) // true
    m.delete(o) // true
    m.has(o) // false
    

    赋值应用:任何具有 Iterator 接口、且每个成员都是一个双元素的数组的数据结构(详见《Iterator》一章)都可以当作Map构造函数的参数。这就是说,Set和Map都可以用来生成新的 Map。

    const set = new Set([
      ['foo', 1],
      ['bar', 2]
    ]);
    const m1 = new Map(set);
    m1.get('foo') // 1
    
    const m2 = new Map([['baz', 3]]);
    const m3 = new Map(m2);
    m3.get('baz') // 3
    

    如果对同一个键多次赋值,后面的值将覆盖前面的值。

    const map = new Map();
    
    map
    .set(1, 'aaa')
    .set(1, 'bbb');
    
    map.get(1) // "bbb"
    

    上面代码对键1连续赋值两次,后一次的值覆盖前一次的值。

    如果读取一个未知的键,则返回undefined。

    new Map().get('asfddfsasadf')
    // undefined
    

    Map 的键实际上是跟内存地址绑定的,只要内存地址不一样,就视为两个键。如果 Map 的键是一个简单类型的值(数字、字符串、布尔值),则只要两个值严格相等,Map 将其视为一个键,比如0和-0就是一个键,布尔值true和字符串true则是两个不同的键。另外,undefined和null也是两个不同的键。虽然NaN不严格相等于自身,但 Map 将其视为同一个键。

    实例属性和方法

    1.size属性返回 Map 结构的成员总数。
    2.set(key, value) set方法返回的是当前的Map对象,因此可以采用链式写法。
    3.get(key) 如果找不到key,返回undefined。
    4.has(key)
    5.delete(key)删除某个键,返回true。如果删除失败,返回false
    6.clear()清除所有成员,没有返回值。
    遍历方法:
    keys():返回键名的遍历器。
    values():返回键值的遍历器。
    entries():返回所有成员的遍历器。
    forEach():遍历 Map 的所有成员。

    Weakmap

    首先,WeakMap只接受对象作为键名(null除外),不接受其他类型的值作为键名。
    其次,WeakMap的键名所指向的对象,不计入垃圾回收机制。
    只要所引用的对象的其他引用都被清除,垃圾回收机制就会释放该对象所占用的内存。也就是说,一旦不再需要,WeakMap 里面的键名对象和所对应的键值对会自动消失,不用手动删除引用。
    垃圾回收机制以后再写一篇笔记说明。
    WeakMap只有四个方法可用:get()、set()、has()、delete()。
    没有遍历机制和size属性。

    Generator

    终于讲到了Generator。
    Generator可以看作是一个封装了多个内部状态的函数,这些状态是可遍历的。执行Generator返回一个遍历器。
    形式上,Generator 函数是一个普通函数,但是有两个特征。一是,function关键字与函数名之间有一个星号;二是,函数体内部使用yield表达式,定义不同的内部状态

    function* helloWorldGenerator() {
      yield 'hello';
      yield 'world';
      return 'ending';
    }
    
    var hw = helloWorldGenerator();
    hw.next()
    // { value: 'hello', done: false }
    
    hw.next()
    // { value: 'world', done: false }
    
    hw.next()
    // { value: 'ending', done: true }
    
    hw.next()
    // { value: undefined, done: true }
    

    Generator 函数是分段执行的,yield表达式是暂停执行的标记,而next方法可以恢复执行。
    Generator 函数可以不用yield表达式,这时就变成了一个单纯的暂缓执行函数。

    function* f() {
      console.log('执行了!')
    }
    
    var generator = f();
    
    setTimeout(function () {
      generator.next()
    }, 2000);
    

    注意的地方:
    1.yield表达式只能用在 Generator 函数里面,用在其他地方都会报错。
    2.yield表达式如果用在另一个表达式之中,必须放在圆括号里面。
    3.yield表达式用作函数参数或放在赋值表达式的右边,可以不加括号。

    next()带参数

    next方法的参数表示上一个yield表达式的返回值(所以在第一次使用next方法时,传递参数是无效的)

    function* foo(x) {
      var y = 2 * (yield (x + 1));
      var z = yield (y / 3);
      return (x + y + z);
    }
    
    var a = foo(5);
    a.next() // Object{value:6, done:false}
    a.next() // Object{value:NaN, done:false}
    a.next() // Object{value:NaN, done:true}
    
    var b = foo(5);
    b.next() // { value:6, done:false }
    b.next(12) // { value:8, done:false }
    b.next(13) // { value:42, done:true }
    
    for...of
    function *foo() {
      yield 1;
      yield 2;
      yield 3;
      yield 4;
      yield 5;
      return 6;
    }
    
    for (let v of foo()) {
      console.log(v);
    }
    // 1 2 3 4 5
    

    从例子可以看出,当done为true时,for..of停止遍历并且不返回当前值。
    除了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
    
    return()和throw()

    throw方法可以接受一个参数,该参数会被catch语句接收。
    在generator内如果有try...catch,那么generator的错误会被捕捉到一次。当内部已经捕获到错误,后继的错误就由外部处理或者直接中断程序。
    throw方法被捕获以后,会附带执行下一条yield表达式。也就是说,会附带执行一次next方法。

    return方法,可以返回给定的值,并且终结遍历 Generator 函数。如果return方法调用时,不提供参数,则返回值的value属性为undefined。

    generator的this指向哪里

    如果不作任何处理,在generator实例内定义一个this.a=1,this指向的是全局。
    如果希望generator可以兼备next方法和正常的this指向,有两个方法
    1,使用call或者apply绑定一个空对象
    2,绑定generator函数的prototype

    相关文章

      网友评论

          本文标题:从Iterator到map、set和Generator

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