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

внешний интерфейс JavaScript Perfect

Стандартная операция для цикла

Я считаю, что большинство каменщиков написали подобный код:

    var arr = ['element1', 'element2', 'element3'];

    for (var i = 0, len = arr.length; i < len; i++) {
        console.log(arr[i]);
    }

    // element1
    // element2
    // element3

Это стандарт кода цикла, который использует переменную i для отслеживания индекса массива arr для доступа к каждому элементу массива.

Но следует сказать, что это очень примитивный метод с несколькими значительными недостатками:

  • Нельзя просто сосредоточиться на самом элементе, нужно тратить энергию на поддержание переменной i и границы len;

  • Когда есть несколько вложений, нужно будет отслеживать и поддерживать несколько переменных, и код будет очень сложным;

  • Чтобы справиться с проблемой выхода за границы, требуются усилия.Некоторые скомпилированные языки будут сообщать об ошибках, когда индекс массива выходит за пределы, но движок JavaScript не сообщит вам никакой информации об ошибке, а стоимость позиционирования ошибки будет быть относительно высоким.

Массив имеет собственный метод forEach

Конечно, мы можем использовать метод forEach, который поставляется вместе с массивом, для обхода массива:

    var arr = ['element1', 'element2', 'element3'];

    arr.forEach(function(value, index, arr) {
        console.log(value);
    });

    // element1
    // element2
    // element3

Приятно, что все выглядит настолько идеально, что не требуется усилий, чтобы следить за индексом и беспокоиться о проблемах, выходящих за рамки. Но что, если вы хотите выйти при переходе к определенному условию?

    var arr = ['element1', 'element2', 'element3'];

    arr.forEach(function(value, index, arr) {
        if (index === 1) {
            break;
        } else {
            console.log(value);
        }
    });

    // Uncaught SyntaxError: Illegal break statement

Жаль, что приведенный выше код не запустился так, как мы ожидали (break, continue и другие операторы пересекают границу функции), поэтому этот, казалось бы, идеальный метод может привести к провалу только в одном направлении, столь фундаментальном, как поедание Xuanmai. . Когда массив большой, нет возможности сэкономить ресурсы, прервав обход раньше.

для в цикле

Рис нужно есть по одному кусочку за раз (пожалуйста, не обращайте внимания на рисовое ведерко), мы можем сначала решить проблему траты времени, чтобы предотвратить выход массива за пределы, например, мы можем использовать цикл for in:

    var arr = ['element1', 'element2', 'element3'];

    for (var i in arr) {
        console.log(arr[i]);
    }

    // element1
    // element2
    // element3

Оператор for in — это точный итеративный оператор, который может перечислять все перечисляемые свойства объекта (вы можете использовать метод Object.getOwnPropertyDescriptor(targetObj, attrName), чтобы проверить, является ли свойство объекта перечислимым). Это означает, что его можно использовать для перебора объектов:

    var obj = {
        a: 1,
        b: 1,
        c: 1
    };

    for (let attr in obj) {
        console.log(attr, obj[attr]);
    }

    // a 1
    // b 1
    // c 1

Помимо обхода объектов и массивов, циклы for in также могут частично проходить по строкам:

    var str = 'I am a handsome boy!';

    for (var i in str) {
        console.log(str[i]);
    }

    // 太帅(chang)了,结果就不打印了

Разумеется, операции break и continue также поддерживаются, и я не буду писать примеры.

Эта вещь выглядит очень круто, она универсальна. Но, как правило, люди, которые вроде бы все умеют, на самом деле не во всем хороши (я другой, я не только вроде бы не во всем умею, но еще и не умею), и тут тоже самое, посмотрите на пример:

    var father = {
        fatherAttr: 1
    };

    // 以father为原型创建对象实例instance
    var instance = Object.create(father);

    instance.a = 1;
    instance.b = 1;
    instance.c = 1;

    for (var attr in instance) {
        console.log(attr, instance[attr]);
    }

    // a 1
    // b 1
    // c 1
    // fatherAttr 1

    // 获取instance实例的自有属性名
    console.log(Object.getOwnPropertyNames(instance));

    // ["a", "b", "c"]

В приведенном выше примере экземпляр экземпляра объекта сначала создается с родительским объектом в качестве прототипа, затем к экземпляру экземпляра добавляются три атрибута a, b и c, а затем для обхода объекта используется цикл for in. Глядя на собственные атрибуты экземпляра, мы можем обнаружить, что FatherAttr является не атрибутом экземпляра, а атрибутом его отца-прототипа.Цикл for in также перечислит атрибуты прототипа объекта. Следовательно, при использовании этого метода для обхода свойств объекта вам необходимо добавить еще один уровень суждения:

    for (var attr in obj) {
        if (obj.hasOwnProperty(attr)) {
            // 是对象的自有属性,可以尽情的玩耍了
        }
    }

Недостаток цикла for in с перечислением свойств прототипа имеет ту же проблему при работе с массивами, но в целом безопаснее использовать его для обхода массива, ведь прототипом массива является встроенный в JavaScript объект Array, свойство по умолчанию объекта Array не является перечисляемым, но если вы решитесь изменить объект Array, эта небольшая ошибка не станет для вас проблемой.

Следует сказать, что цикл for in по-прежнему является широко используемым методом обхода объектов, в основном из-за его совместимости. Конечно, есть и другие способы перебора объектов, о которых мы поговорим позже. Продолжаем испытание for в цикле:

    var str = 'a𠮷c';

    for (let index in str) {
        console.log(str[index]);
    }

    // a
    // 无法用言语描述的字符
    // 无法用言语描述的字符
    // c

Когда ES5 и до обработки строк, он был основан на 16-битных единицах кодирования; 16-битное кодирование, очевидно, не может кодировать все символы в мире, поэтому некоторые символы необходимо кодировать в 32 бита, например, слово «𠮷».

Так что нетрудно понять результат печати четырех символов в приведенном выше примере. С логической точки зрения, это не должно быть причиной цикла for in, но иногда вы просто не хотите быть разумным.

Хотя строки обработки es6 вынуждают использовать строки UTF-16 для решения вышеуказанных проблем (ниже будут соответствующие примеры), цикл for in по-прежнему имеет вышеуказанные проблемы, если ваша программа должна быть совместима с браузерами, которые не поддерживают es6. , можно тыкатьздесь

для цикла

Цикл for in может решить проблему, которая требуется традиционному циклу for для поддержания границ, но он также вводит некоторые новые проблемы, которые очень похожи на ежедневную работу сценария «решить 3 ошибки и ввести 8 новых ошибок» .

Итак, попробуйте другую операцию for of loop, определенную es6:

    let str = 'a 𠮷 c';

    for (let char of str) {
        if (char === ' ') {
            continue;
        } else {
            console.log(char);
        }
    }

    // a
    // 𠮷
    // c

В приведенном выше примере эффект идеальный: индекс удаляется, граница удаляется, если вы хотите продолжить, вы можете продолжить, если вы хотите выйти, вы можете выйти, а также вы можете решить проблему кодирования файла. струна кстати.

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

В соответствии с рутиной сначала похвалы, а затем умаления, давайте взглянем на некоторые из ее ограничений:

  • Операционная среда ES6 и выше, поэтому совместимость не так хороша, как цикл for in и традиционная операция.Если вам нужно учитывать совместимость с браузерами прошлого века, вы не можете использовать эту вещь.

  • Его можно использовать только для обхода итерируемых объектов, то есть объектов с методами-генераторами (используемыми для генерации итераторов).Если он используется для обхода неитерируемых объектов, не подлежит обсуждению сообщение об ошибке в минутах. Вы можете определить, является ли объект итерируемым, определив, является ли метод объекта Symbol.iterator (связанное содержание будет в следующей статье) функцией.

    let arr = ['a', 'b', 'c'];

    // 判断其Symbol.iterator属性是否为函数
    if ((typeof arr[Symbol.iterator]).toUpperCase() === 'FUNCTION') {
        for (let element of arr) {
            console.log(element);
        }
    } else {
        console.log('此对象不可迭代');
    }

    // a
    // b
    // c

На самом деле, большинство встроенных объектов JavaScript поддерживают итерацию, например: Array, Set, Map, String и т. д. При использовании цикла for для обхода вышеуказанных объектов он будет использовать итератор, сгенерированный генератором по умолчанию:

    let map = new Map([['a', 1], ['b', 1], ['c', 1], ['d', 1]]);

    // 正经操作
    for (let item of map) {
        console.log(item);
    }

    // ["a", 1]
    // ["b", 1]
    // ["c", 1]
    // ["d", 1]

    // 使用解构,方便读取值
    for (let [key, value] of map) {
        console.log(key, value);
    }

    // a 1
    // b 1
    // c 1
    // d 1

В приведенном выше примере for of используется для обхода карты экземпляра типа Map, а объект итерации — это итератор, сгенерированный генератором по умолчанию типа Map. Конечно, такие типы, как Array, Set и Map, также предоставляют некоторые специальные генераторы, которые могут упростить работу с контентом, на который они хотят обратить внимание:

  • entry() возвращает итератор, возвращаемое значение которого представляет собой массив пар ключ-значение (итератор по умолчанию для коллекции Map; для коллекции Set элементы возвращаемого массива значений одинаковы, то есть значение)

  • keys() возвращает итератор, возвращаемое значение которого является именем ключа коллекции (для коллекции Set этот итератор возвращает то же значение, что и итератор значений; для массива этот итератор возвращает индекс)

  • values() возвращает итератор, возвращаемое значение которого является значениями коллекции (итератор по умолчанию для коллекций Array, Set)

    let arr = ['a', 'b', 'c', 'd']
    let set = new Set(arr);

    for (let item of set.entries()) {
        console.log(item);
    }
    for (let item of arr.entries()) {
        console.log(item);
    }

    // ["a", "a"]
    // ["b", "b"]
    // ["c", "c"]
    // ["d", "d"]
    // [0, "a"]
    // [1, "b"]
    // [2, "c"]
    // [3, "d"]

    for (let item of set.keys()) {
        console.log(item);
    }
    for (let item of arr.keys()) {
        console.log(item);
    }

    // a
    // b
    // c
    // d
    // 0
    // 1
    // 2
    // 3

    for (let item of set.values()) {
        console.log(item);
    }
    for (let item of arr.values()) {
        console.log(item);
    }

    // a
    // b
    // c
    // d
    // a
    // b
    // c
    // d

В дополнение к встроенным объектам JavaScript, некоторые стандартные типы DOM, такие как NodeList, также могут быть пройдены с помощью цикла for of:

    let containers = document.querySelectorAll('.container');

    for (let node of containers) {
        // 搞事情专用注释
    }

Жаль, что цикл for of не поддерживает обход пользовательских объектов (10 000 ****, проносящихся мимо...), поэтому, если вы не хотите использовать цикл for in для обхода объектов, вы можете только повернуть вокруг .

Поверните операции, которые пересекают объекты

Object.keys() получает массив имен ключей

Используйте Object.keys(), чтобы получить все перечисляемые свойства экземпляра объекта, возвращаемое значение — массив, а элементы массива — имена ключей объекта:

    let father = {
        fatherAttr: 1
    };

    // 以father为原型创建对象实例instance
    let instance = Object.create(father);

    instance.a = 1;
    instance.b = 1;
    instance.c = 1;

    Object.defineProperty(instance, 'd', {
        writable: true,
        value: 1,
        enumerable: false,
        configurable: true
    });

    for (let key of Object.keys(instance)) {
        console.log(key);
    }

    // a
    // b
    // c

Как видно из вышеприведенного примера, метод Object.keys() не получает свойства прототипа объекта и собственные неперечислимые свойства, что больше соответствует нашим потребностям, и это особенность ES5, совместимость Это еще лучше, и я предпочитаю использовать именно этот метод.

Конечно, если вы умрете и передадите необъектные параметры (например, строки) этому методу, его производительность в среде ES5 и среде ES6 будет отличаться:

    console.log(Object.keys('I am a handsome boy!'));

    // ES5 直接报错,但说不定是浏览器嫉妒我的帅气才会报错的

    // ES6 估计见多了大风大浪,没啥感觉了
    // ["0", "1", "2", "3", "4", "5", ...]

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

Object.getOwnPropertyNames() получает массив имен ключей

Этот метод ведет себя как метод keys, за исключением того, что возвращаемый массив содержит неперечисляемые свойства объекта:

    let father = {
        fatherAttr: 1
    };

    let instance = Object.create(father);

    instance.a = 1;
    instance.b = 1;
    instance.c = 1;

    Object.defineProperty(instance, 'd', {
        writable: true,
        value: 1,
        enumerable: false,
        configurable: true
    });

    for (let key of Object.getOwnPropertyNames(instance)) {
        console.log(key);
    }

    // a
    // b
    // c
    // d

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

Object.entries() получает массив пар ключ-значение

О том, что возвращает этот метод, много говорить не нужно, рассмотрим пример:

    let father = {
        fatherAttr: 1
    };

    let instance = Object.create(father);

    instance.a = 1;
    instance.b = 1;
    instance.c = 1;

    Object.defineProperty(instance, 'd', {
        writable: true,
        value: 1,
        enumerable: false,
        configurable: true
    });

    for (let key of Object.entries(instance)) {
        console.log(key);
    }

    // ["a", 1]
    // ["b", 1]
    // ["c", 1]

Поэтому при инициализации экземпляра Map с объектом вы можете использовать этот метод:

    let obj = { a: 1, b: 1, c: 1 },
        map = new Map(Object.entries(obj));
    
    console.log(map.get('a'));
    console.log(map.get('b'));
    console.log(map.get('c'));

    // 1
    // 1
    // 1

Object.values() Получает массив значений свойств объекта

Ну утомительно писать примеры, так что больше не буду, вы и сами можете это сделать.

Object.getOwnPropertySymbols() получает имя свойства Symbol.

Ни один из методов, упомянутых выше, не может получить имя свойства типа Symbol экземпляра объекта, если вам нужно обойти эту штуку, вам нужно использовать метод Object.getOwnPropertySymbols():

    let father = {
        fatherAttr: 1
    };

    let instance = Object.create(father);

    instance.a = 1;
    instance.b = 1;
    instance.c = 1;

    instance[Symbol('I am a handsome boy!')] = 1;

    for (let key of Object.keys(instance)) {
        console.log(key);
    }

    // a
    // b
    // c

    for (let key of Object.getOwnPropertySymbols(instance)) {
        console.log(key);
    }

    // Symbol(I am a handsome boy!)

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

Если есть какие-либо ошибки или упущения выше, пожалуйста, поправьте меня.

@Author: papercrane.