美文网首页
『ES6脚丫系列』遍历器iterator

『ES6脚丫系列』遍历器iterator

作者: 吃码小妖 | 来源:发表于2019-12-17 23:22 被阅读0次
    图片.png

    『ES6脚丫系列』遍历器iterator
    本文内容如下:

    1 具有iterator接口的数据结构
    2 遍历器过程
    3 遍历器作用:
    4 模拟next()方法
    5 使用while循环
    6 TypeScript的写法
    7 Iterator接口与Generator函数
    8 对象的遍历器接口
    8.1 对于类似数组的对象
    9 调用Iterator接口的场合
    9.1 解构赋值
    9.2 扩展运算符
    9.3 yield*
    9.4 其他场合
    10 字符串的Iterator接口
    

    具有iterator接口的数据结构

    【01】原生就具有Iterator接口,它们的遍历器接口部署在Symbol.iterator属性上:

    Array,Object(类数组对象),Map,WeakMap,Set,WeakSet,字符串。

    let arr = new Array();

    let iterator =arrSymbol.iterator;

    【02】其他数据结构(主要是对象)的Iterator接口,都需要自己部署在Symbol.iterator属性上面。(原型链上的对象具有该方法也可)。

    【03】一个数据结构只要具有Symbol.iterator属性,就可以认为是“可遍历的”(iterable)。
    就称为部署了遍历器接口。就可以遍历所有成员。可通过for of 遍历。

    【04】遍历器对象本身也有Symbol.iterator方法,执行后返回自身。

    gen是一个Generator函数,调用它会生成一个遍历器对象g。它的Symbol.iterator属性,也是一个遍历器对象生成函数,执行后返回它自己。

    function* gen(){ // some code
    }
    var g = gen();
    
    g[Symbol.iterator]() === g // true
    

    遍历器过程

    【01】吃码小妖:说白了,等于这些数据结构有一个方法(PS,这个方法称为遍历器函数)。

    方法名是[Symbol.iterator],方法中返回一个对象(PS,这个对象称为遍历器对象),该对象有一个next()方法。这个next()会遍历数据结构的成员。返回{value:XX||undefined , done:true||false}

    let obj = {

    ​ [Symbo.iterator] (){

    return {next(){}}

    }

    }

    调用遍历器函数,就会返回一个遍历器对象。

    每次调用iterator.next(),就使用该数据结构的一个成员,成员的值就是返回对象中value的值。done为false,最后会返回一个有value和done两个属性的对象。

    从头到尾依次使用数据结构的成员。直到使用的成员都访问完了。

    当数据结构的成员已全部访问了,此时,再调用iterator.next()。返回的对象中,value的值为undefined,done为true。

    done属性是一个布尔值,表示遍历是否结束。

    let arr = [1,2,3];

    let iterator = arrSymbol.iterator;

    iterator.next();//{value:"",done:false};

    let arr = ['a', 'b', 'c'];
    let iter = arr[Symbol.iterator]();//返回遍历器对象。
    
    iter.next() // { value: 'a', done: false }
    iter.next() // { value: 'b', done: false }
    iter.next() // { value: 'c', done: false }
    iter.next() // { value: undefined, done: true }
    

    【03】遍历器与它所遍历的那个数据结构是分开的。


    遍历器作用:

    一是为各种数据结构提供一个统一的访问接口;

    二是让数据结构的成员可以按某种次序排列;

    三是供for...of使用。

    模拟next()方法

    使用一个函数,函数参数是数组,返回一个对象,对象具有next()方法。

    在next()中,使用一个变量,遍历数组的下标。通过和数组的长度对比,来判断返回不同的对象。

    返回对象都有value和done属性。

    function Convert(arr){
        let index = 0;
        return {
            next (){
                if(index<arr.length){
                    return {value:arr[index++],done:false}
                }
                else {
                    return {value:undefined,done:true}
                }
            }
        }
    }
    var it = Convert(['a', 'b']);
    
    it.next() // { value: "a", done: false }
    it.next() // { value: "b", done: false }
    it.next() // { value: undefined, done: true }
    

    吃码小妖:next返回的就是指针对象了。此时,it是对象,它的变量index是保存在函数中的。

    对于遍历器对象来说,done: false和value: undefined属性都是可以省略的,因此上面的Covert函数可以简写成下面的形式。

    function Covert(array) {    var nextIndex = 0;
        return {
            next: function() {
                return nextIndex < array.length ? { value: array[nextIndex++] } : { done: true }; }
        }
    }
    

    【】下面是一个无限运行的遍历器对象的例子。

    吃码小妖:仅了解。实用性不大。

    遍历器生成函数idMaker,返回一个遍历器对象(即指针对象)。但是并没有对应的数据结构,或者说,遍历器对象自己描述了一个数据结构出来。

    function idMaker() {
        let index = 0;
        return {
            next: function() {
                return { value: index++, done: false }; }
        }
    }var it = idMaker();
    
    it.next().value // '0'
    it.next().value // '1'
    it.next().value // '2'
        // ...
    

    使用while循环

    有了遍历器接口,数据结构就可以用for...of循环遍历,也可以使用while循环遍历。

    var $iterator = ITERABLE[Symbol.iterator]();
    var $result = $iterator.next();
    while (!$result.done) {
        var x = $result.value; // ...
        $result = $iterator.next();
    }
    

    TypeScript的写法

    遍历器接口(Iterable)、指针对象(Iterator)和next方法返回值的规格可以描述如下。

    interface Iterable {
        [Symbol.iterator](): Iterator, }
    
    interface Iterator { next(value ? : any): IterationResult, }
    
    interface IterationResult {
        value: any,
            done: boolean,
    }
    

    Iterator接口与Generator函数

    var myIterable = {};
    
    myIterable[Symbol.iterator] = function* () {
        yield 1;
        yield 2;
        yield 3;
    };
    [...myIterable] // [1, 2, 3]
    
    // 或者采用下面的简洁写法
    let obj = { * [Symbol.iterator]() {
            yield 'hello';
            yield 'world';
        }
    };
    for (let x of obj) {
        console.log(x);
    }
    // hello
    // world
    

    对象的遍历器接口

    【】对象部署遍历器接口并不是很必要,可以使用Map。

    对于类似数组的对象

    (存在数值键名和length属性),部署Iterator接口,有一个简便方法,就是Symbol.iterator方法直接引用数组的Iterator接口。

    NodeList.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];
    // 或者
    NodeList.prototype[Symbol.iterator] = [][Symbol.iterator];
    [...document.querySelectorAll('div')] // 可以执行了
    

    下面是类似数组的对象调用数组的Symbol.iterator方法的例子。

    let iterable = {
        0: 'a',
        1: 'b',
        2: 'c',
        length: 3,
        [Symbol.iterator]: Array.prototype[Symbol.iterator]
    };
    for (let item of iterable) {
        console.log(item); // 'a', 'b', 'c'
    }
    

    调用Iterator接口的场合

    有一些场合会默认调用Iterator接口(即Symbol.iterator方法)。

    解构赋值

    对数组和Set结构进行解构赋值时,会默认调用Symbol.iterator方法。

    let set = new Set().add('a').add('b').add('c');
    let [x, y] = set;
    // x='a'; y='b'
    let [first, ...rest] = set;
    // first='a'; rest=['b','c'];
    

    扩展运算符

    扩展运算符(...)会内部调用默认的iterator接口。

    可以将任何部署了Iterator接口的数据结构,转为数组。

    // 例一
    var str = 'hello';
    [...str] //  ['h','e','l','l','o']
    
    // 例二
    let arr = ['b', 'c'];
    ['a', ...arr, 'd']
    // ['a', 'b', 'c', 'd']
    
    let arr = [...iterable];
    

    yield*

    yield*后面跟的是一个可遍历的结构,它会调用该结构的遍历器接口。

    let generator = function* () {
      yield 1;
      yield* [2,3,4];
      yield 5;};var iterator = generator();
    
    iterator.next() // { value: 1, done: false }
    iterator.next() // { value: 2, done: false }
    iterator.next() // { value: 3, done: false }
    iterator.next() // { value: 4, done: false }
    iterator.next() // { value: 5, done: false }
    iterator.next() // { value: undefined, done: true }
    

    其他场合

    由于数组的遍历会调用遍历器接口,所以任何接受数组作为参数的场合,其实都调用了遍历器接口。

    下面是一些例子。

    • for...of
    • Array.from()
    • Map(), Set(), WeakMap(), WeakSet()(比如new Map([['a',1],['b',2]]))
    • Promise.all([])
    • Promise.race([])

    字符串的Iterator接口

    字符串是一个类似数组的对象,原生具有Iterator接口。

    var someString = "hi";
    typeof someString[Symbol.iterator]    // "function"
    var iterator = someString[Symbol.iterator]();
    iterator.toString(); // => '[object String Iterator]'
    iterator.next() // { value: "h", done: false }
    iterator.next() // { value: "i", done: false }
    iterator.next() // { value: undefined, done: true }
    
    let some = "book";
    for (let i of some){
        console.log(i)
    }
    

    相关文章

      网友评论

          本文标题:『ES6脚丫系列』遍历器iterator

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