Обход, перечисление и итерация операции JavaScript Sao (часть 2)

внешний интерфейс JavaScript Promise опрос

предисловие

Обход JavaScript, перечисление и повторение операций Sao (Часть 1)Обобщаются методы обхода некоторых часто используемых объектов, которые в большинстве случаев могут удовлетворить требования работы. Однако содержание, представленное в следующей статье, не используется в 95% случаев на работе и ограничивается притворством. Как говорится: если слишком много притворяться, машина перевернется! Если в этой статье есть сцена опрокидывания, пожалуйста, слегка распылите.

Итераторы, генераторы ES6

Как упоминалось в предыдущей статье, цикл for of работает, полагаясь на итератор объекта.Если цикл for of используется для обхода неитерируемого объекта (то есть объекта без итератора по умолчанию), цикл for of цикл сообщит об ошибке. Так что же такое итератор?

Итератор — это объект особого типа, у которого есть метод next, который вызывается один раз для каждого перечисления (каждый цикл for) и возвращает объект, содержащий два значения:

  • атрибут value, указывающий возвращаемое значение этого вызова (цикл for of возвращает только это значение);
  • done, логический тип значения, указывающий, завершился ли вызов.

Генератор, как следует из названия, это ебаный итератор; генератор — это специальная функция, которая возвращает итератор, сгенерированный генератором.

Способ объявления генератора аналогичен описанию обычных функций, за исключением того, что перед именем функции добавляется знак * (пробел вокруг знака * тоже может работать корректно, но для читабельности кода рекомендуется оставлять пробелы слева, а не справа); функция используется внутри. Ключевое слово yield определяет возвращаемое значение для каждой итерации.

    // 生成器
    function *iteratorMother() {
        yield 'we';
        yield 'are';
        yield 'the BlackGold team!';
    }

    // 迭代器
    let iterator = iteratorMother();

    console.log(iterator.next());  // { value: "we", done: false }
    console.log(iterator.next());  // { value: "are", done: false }
    console.log(iterator.next());  // { value: "the BlackGold team!", done: false }

    console.log(iterator.next());  // { value: undefined, done: true }
    console.log(iterator.next());  // { value: undefined, done: true }

В приведенном выше примере показано, как объявить функцию-генератор iteratorMother, и вызов этой функции возвращает итератор-итератор.

yield — это ключевое слово в ES6, которое указывает значение, возвращаемое объектом итератора каждый раз, когда вызывается следующий метод. Например, строка «мы» после первого ключевого слова yield — это значение, возвращаемое объектом итератора при первом вызове следующего метода, и так далее, пока не будут выполнены все операторы yield.

Уведомление:Когда оператор yield выполняется, вызов iterator.next() всегда будет возвращать {value: undefined, done: true}, поэтому не используйте цикл for of для обхода одного и того же итератора дважды.

    function *iteratorMother() {
        yield 'we';
        yield 'are';
        yield 'the BlackGold team!';
    }

    let iterator = iteratorMother();

    for (let element of iterator) {
        console.log(element);
    }

    // we
    // are
    // the BlackGold team!

    for (let element of iterator) {
        console.log(element);
    }

    // nothing to be printed
    // 这个时候迭代器iterator已经完成他的使命,如果想要再次迭代,应该生成另一个迭代器对象以进行遍历操作

Уведомление:Вы можете указать возвращаемое значение генератора.Когда оператор return выполняется, независимо от того, есть ли в следующем коде ключевое слово yield или нет, он не будет выполняться снова, а возвращаемое значение возвращается только один раз, а вызов следующего метод снова просто возвращает {value: undefined, done: true}

    function *iteratorMother() {
        yield 'we';
        yield 'are';
        yield 'the BlackGold team!';
        return 'done';

        // 不存在的,这是不可能的
        yield '0 error(s), 0 warning(s)'
    }

    // 迭代器
    let iterator = iteratorMother();

    console.log(iterator.next());  // { value: "we", done: false }
    console.log(iterator.next());  // { value: "are", done: false }
    console.log(iterator.next());  // { value: "the BlackGold team!", done: false }

    console.log(iterator.next());  // { value: "done", done: true }
    console.log(iterator.next());  // { value: undefined, done: true }

Обратите внимание в третий раз:Ключевое слово yield можно использовать только внутри функции-генератора.При использовании вне генератора (включая использование примера функции внутри генератора) будет сообщено об ошибке, поэтому будьте осторожны, чтобы не пересечь границу функции при ее использовании.

    function *iteratorMother() {
        let arr = ['we', 'are', 'the BlackGold team!'];

        // 报错了
        // 以下代码实际上是在forEach方法的参数函数里面使用yield
        arr.forEach(item => yield item);
    }

В приведенном выше примере об ошибке сообщается, когда движок JavaScript поднимает объявление функции, а не когда создается экземпляр итератора.

Обратите внимание на четвертый раз:Не пытайтесь получить возвращаемое значение, указанное yield внутри генератора, иначе вы получите неопределенное

    function *iteratorMother() {
        let a = yield 'we';
        let b = yield a + ' ' +  'are';
        yield b + ' ' + 'the BlackGold team!';
    }

    let iterator = iteratorMother();

    for (let element of iterator) {
        console.log(element);
    }

    // we
    // undefined are
    // undefined the BlackGold team!

примечание: вы можете использовать выражение анонимной функции для объявления генератора, просто добавьте симпатичный знак * после ключевого слова function, и пример не будет написан;Но нельзя объявлять генераторы со стрелочными функциями..

Добавить генераторы для объектов

При использовании цикла for of для обхода объекта он сначала выясняет, есть ли у объекта генератор.Если да, то он использует свой генератор по умолчанию для создания итератора, а затем проходит по итератору, если нет, то будет ошибка. сообщил!

В прошлой статье также упоминалось, что специальные типы объектов, такие как Set, Map и Array, имеют несколько генераторов, но пользовательские объекты не имеют встроенных генераторов.Я не знаю, почему, у меня есть подруги с другими, и я не есть девушка, я не знаю, почему. Не беда, сделай сам, а еды и одежды хватай, добавляем генератор кастомных объектов (насчет решения задачи с подругой, не спрашивайте меня)

    let obj = {
        arr: ['we', 'are', 'the BlackGold team!'],
        *[Symbol.iterator]() {
            for (let element of this.arr) {
                yield element;
            }
        }
    }

    for (let key of obj) {
        console.log(key);
    }

    // we
    // are
    // the BlackGold team!

Ну, я признаю, что приведенный выше пример немного похож на то, чтобы снять штаны и поставить П. Конечно, это не то, чтобы этот пример вонючий, но он немного избыточен, ведь мы хотим пройтись по свойствам объекта, так что давайте по другому.

    let father = {
        *[Symbol.iterator]() {
            for (let key of Reflect.ownKeys(this)) {
                yield key;
            }
        }
    };

    let obj = Object.create(father);

    obj.a = 1;
    obj[0] = 1;
    obj[Symbol('PaperCrane')] = 1;
    Object.defineProperty(obj, 'b', {
        writable: true,
        value: 1,
        enumerable: false,
        configurable: true
    });

    for (let key of obj) {
        console.log(key);
    }

    /* 看起来什么鬼属性都能被Reflect.ownKeys方法获取到 */
    // 0
    // a
    // b
    // Symbol(PaperCrane)

Обернув объект так, как показано в приведенном выше примере, вы действительно можете использовать for of для обхода свойств объекта, но это все еще немного проблематично, и в настоящее время нет лучшего решения. Когда мы создаем собственный класс (конструктор), мы можем добавить генератор Symbol.iterator, после чего экземпляр класса можно будет пройти с помощью цикла for of.

примечание: Объект Reflect является объектом отражения. Свойства предоставляемых им методов по умолчанию соответствуют методам, предоставляемым нижним слоем. Например, производительность Reflect.ownKeys эквивалентна работе трех операций объекта .keys, Object.getOwnPropertyNames и Object.getOwnPropertySymbols. В прошлой статье друг с идентификатором "webgzh907247189" упомянул, что есть такой метод получения имени атрибута объекта. Эта статья продемонстрирует его, и я очень благодарен этому другу за ценное мнение.

итератор по значению

Как упоминалось выше, если вы получите возвращаемое значение, указанное yield внутри итератора, вы получите неопределенное значение, но если логика кода зависит от предыдущего возвращаемого значения, вам необходимо передать параметры следующему методу итератора для достижения этой цели.

    function *iteratorMother() {
        let a = yield 'we';
        let b = yield a + ' ' +  'are';
        yield b + ' ' + 'the BlackGold team!';
    }

    let iterator = iteratorMother(),
        first, second, third;

    // 第一次调用next方法时,传入的参数将不起任何作用
    first = iterator.next('anything,even an Error instance');
    console.log(first.value);                // we
    second = iterator.next(first.value);
    console.log(second.value);               // we are
    third = iterator.next(second.value);
    console.log(third.value);                // we are the BlackGold team!

Параметр, переданный методу next, станет возвращаемым значением ключевого слова yield, соответствующего последнему вызову next, и это значение можно получить внутри генератора. Следовательно, при вызове следующего метода будет выполняться блок кода от правой части ключевого слова yield до левой части предыдущего ключевого слова yield; объявление и присвоение переменной a внутри генератора выполняются при вызове следующего метода вызывается во второй раз.

примечание: при передаче параметров следующему методу, вызываемому в первый раз, это не повлияет на итерацию. Кроме того, вы также можете передать экземпляр Error в метод next.Когда итератор сообщает об ошибке, следующий код не будет выполняться.

Решить ад обратного вызова

Всякий раз, когда мы спрашиваем, как решить проблему ада обратных вызовов во время интервью, наша первая реакция должна состоять в том, чтобы использовать объект Promise; если вы большая корова, вы можете легко отказаться от принципа реализации Promise интервьюера; но если вы этого не сделаете, понять принцип обещания, вы хотите притвориться, что вас заставляют, вы можете попробовать использовать итераторы, чтобы решить проблему ада обратного вызова

    // 执行迭代器的函数,参数iteratorMother是一个生成器
    let iteratorRunner = iteratorMother => {
        let iterator = iteratorMother(),
            result = iterator.next(); // 开始执行迭代器
        
        let run = () => {
            if (!result.done) {
                // 假如上一次迭代的返回值是一个函数
                // 执行result.value,传入一个回调函数,当result.value执行完毕时执行下一次迭代
                if ((typeof result.value).toUpperCase() === 'FUNCTION') {
                    result.value(params => {
                        result = iterator.next(params);

                        // 继续迭代
                        run();
                    });
                } else {
                    // 上一次迭代的返回值不是一个函数,直接进入下一次迭代
                    result = iterator.next(result.value);
                    run();
                }
            }
        }

        // 循环执行迭代器,直到迭代器迭代完毕
        run();
    }

        // 异步函数包装器,为了解决向异步函数传递参数问题
    let asyncFuncWrapper = (asyncFunc, param) => resolve => asyncFunc(param, resolve),
        // 模拟的异步函数
        asyncFunc = (param, callback) => setTimeout(() => callback(param), 1000);

    iteratorRunner(function *() {
        // 按照同步的方式快乐的写代码
        let a = yield asyncFuncWrapper(asyncFunc, 1);
        a += 1;
        let b = yield asyncFuncWrapper(asyncFunc, a);
        b += 1;
        let c = yield asyncFuncWrapper(asyncFunc, b);

        let d = yield c + 1;
        console.log(d);          // 4
    });

В приведенном выше примере setTimeout используется для имитации асинхронной функции asyncFunc, которая принимает два параметра: param и callback-функцию обратного вызова; внутри генератора значение, возвращаемое каждым ключевым словом yield, представляет собой функцию, обертывающую асинхронную функцию, которая используется для передачи параметров асинхронной функции; функция iteratorRunner, которая выполняет итератор, используется для циклического прохождения итератора и запуска функции, возвращенной итератором. Наконец, мы можем обрабатывать нашу логику кода синхронно внутри анонимного генератора.

Хотя приведенный выше метод решает проблему ада обратных вызовов, он по-прежнему использует метод обратного вызова для вызова кода по существу, но организация кода изменилась. Метод организации кода внутри генератора чем-то похож на синтаксис async и await ES7; разница в том, что асинхронная функция может возвращать объект обещания, и каменщик может продолжать использовать этот объект обещания для вызова асинхронных функций синхронным образом.

    let asyncFuncWrapper = (asyncFunction, param) => {
            return new Promise((resolve, reject) => {
                asyncFunction(param, data => {
                    resolve(data);
                });
            });
        },
        asyncFunc = (param, callback) => setTimeout(() => callback(param), 1000);

    async function asyncFuncRunner() {
        let a = await asyncFuncWrapper(asyncFunc, 1);
        a += 1;
        let b = await asyncFuncWrapper(asyncFunc, a);
        b += 1;
        let c = await asyncFuncWrapper(asyncFunc, b);

        let d = await c + 1;
        return d;
    }

    asyncFuncRunner().then(data => console.log(data));    // 三秒后输出 4

Генератор делегатов

В эту эпоху DRY (не повторяйтесь) генераторы также можно использовать повторно.

    function *iteratorMother() {
        yield 'we';
        yield 'are';
    }

    function *anotherIteratorMother() {
        yield 'the BlackGold team!';
        yield 'get off work now!!!!!!';
    }

    function *theLastIteratorMother() {
        yield *iteratorMother();
        yield *anotherIteratorMother();
    }

    let iterator = theLastIteratorMother();

    for (let key of iterator) {
        console.log(key);
    }

    // we
    // are
    // the BlackGold team!
    // get off work now!!!!!!

В приведенном выше примере в определении генератора theLastIteratorMother повторно используются генератор iteratorMother и еще один IteratorMother, что эквивалентно объявлению двух связанных итераторов внутри генератора theLastIteratorMother с последующим повторением. Следует отметить, что генератор мультиплексирования отмечен звездочкой после ключевого слова yield.

Производительность нескольких операторов цикла

В прошлой статье небольшой партнер упомянул, чтобы сравнить производительность метода обхода.Здесь я кратко сравню производительность каждого цикла для обхода массива.Длина тестового массива составляет 10 миллионов.Код теста выглядит следующим образом:

    let arr = new Array(10 * 1000 * 1000).fill({ test: 1 });

    console.time();
    for (let i = 0, len = arr.length; i < len; i++) {}
    console.timeEnd();

    console.time();
    for (let i in arr) {}
    console.timeEnd();

    console.time();
    for (let i of arr) {}
    console.timeEnd();

    console.time();
    arr.forEach(() => {});
    console.timeEnd();

Результат выглядит следующим образом (единица измерения — мс, IE не учитывается):

Приведенные выше результаты могут немного различаться в разных средах, но в основном можно показать, что собственный цикл является самым быстрым, за ним следует forEach, за ним следует цикл for of и за ним следует цикл forin. На самом деле, если объем данных невелик, метод обхода не станет узким местом в производительности, может быть практичнее рассмотреть, как уменьшить обход цикла.

Суммировать

После написания этой статьи со слезами на глазах я должен уйти с работы.Всем до свидания.

@Author: PaperCrane