Проверка возможности итерации чего-либо

105

В документах MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...of

for...ofКонструкция описана , чтобы иметь возможность перебрать «Iterable» объекты. Но есть ли хороший способ решить, является ли объект повторяемым?

Я пытался найти общие свойства для массивов, итераторов и генераторов, но не смог.

Помимо выполнения for ... ofв блоке try и проверки ошибок типа, есть ли чистый способ сделать это?

Саймонзак
источник
Конечно, как автор, вы знаете, является ли ваш объект повторяющимся?
andrewb
6
Объект передается как аргумент, я не уверен.
simonzack
1
Почему бы не проверить тип аргумента?
Джеймс Брукнер
2
@ andrew-buchan, Джеймс Брукнер: Проверка типов может работать, но если вы прочтете документацию MDN, вы заметите, что там написано «как массив». Я не знаю, что именно это означает, отсюда вопрос.
simonzack
1
wiki.ecmascript.org/doku.php?id=harmony:iterators заявляет: « Объект является итерируемым, если у него есть iterator()метод ». Тем не менее, поскольку это черновик, только проверка может зависеть от реализации. Какую среду вы используете?
Берги

Ответы:

144

Правильный способ проверки итерабельности следующий:

function isIterable(obj) {
  // checks for null and undefined
  if (obj == null) {
    return false;
  }
  return typeof obj[Symbol.iterator] === 'function';
}

Почему это работает (подробный итеративный протокол): https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Iteration_protocols

Поскольку мы говорим о for..of, я полагаю, мы придерживаемся образа мышления ES6.

Кроме того, не удивляйтесь, что эта функция возвращает, trueесли objявляется строкой, поскольку строки повторяют свои символы.

Томаш Кулич
источник
15
или Symbol.iterator in Object(obj),.
14
Есть (по крайней мере) одно исключение из использования оператора in: строка. Строка является итерируемой (с точки зрения for..of), но вы не можете использовать в ней 'in'. Если бы не это, я бы предпочел использовать in, это выглядит определенно лучше.
Томаш Кулич
не должно быть return typeof obj[Symbol.iterator] === 'function'? «Чтобы быть итерируемым, объект должен реализовывать метод @@ iterator» - он определяет метод
callum
Нет правильной семантики для того, чтобы obj [Symbol.iterator] был чем-то иным, кроме (undefined или) функцией. Если кто-то поместит туда, например, String, это плохо, и IMO хорошо, если код выйдет из строя как можно скорее.
Tomas Kulich
Будет typeof Object(obj)[Symbol.iterator] === 'function'работать во всех случаях?
Крейг Гидни
25

Почему так многословно?

const isIterable = object =>
  object != null && typeof object[Symbol.iterator] === 'function'
Адиус
источник
57
Readability > Clevernessвсегда возвращается true.
jfmercer 07
29
Ха-ха, обычно я жалуюсь на нечитаемый код, но на самом деле я думаю, что он довольно читабельный. Как английское предложение: если объект не равен нулю, а свойство символического итератора является функцией, то его можно итерировать. Если это не так просто, я не знаю, что ...
Адиус
4
imo, смысл «читабельности» - понять, что происходит, без фактического чтения
Дима Паржицкий
2
@Alexander Mills Ваше улучшение сделало код хуже. 1. Как сказал @jfmercer Readability > Cleverness, сокращение переменной objectдо oникому не помогает. 2. Пустая строка ''повторяется и поэтому должна возвращаться true.
adius
2
Может быть, единственное , что вам нужно читать это имя: isIterable.
Матиас
22

На самом деле самое простое решение:

function isIterable (value) {
  return Symbol.iterator in Object(value);
}

Objectобернет все, что не является объектом, в один, позволяя inоператору работать, даже если исходное значение не является объектом. nullи undefinedпревращаются в пустые объекты, поэтому нет необходимости в обнаружении крайнего случая, а строки обертываются в объекты String, которые можно итерировать.

Домино
источник
1
Вы использовали неправильный символ. Это: Symbol.iterator.
Gil
@Gil Ты совершенно прав, ой! Я должен был скопировать проверенный код вместо того, чтобы печатать прямо в сообщении.
Domino
Создание нового объекта с помощью Objectэтого типа проверки расточительно.
Рубен Верборг,
Я не могу сказать, что точно знаю, как Objectреализована функция, но она создает новый объект только в том случае, если valueэто еще не объект. Я бы ожидать , что сочетание inи Object(...)быть чем - то браузер двигатели могут легко оптимизировать, в отличие говорят value !== undefined && value !== null && value[Symbol.iterator] && true. Кроме того, он очень удобочитаемый, что меня волнует.
Domino
9

В качестве примечания: ОСТЕРЕГАЙТЕСЬ определения итерации . Если вы переходите с других языков, вы ожидаете, что что-то, что вы можете перебирать, например, forцикл является повторяемым . Боюсь, что это не тот случай, когда итерация означает что-то, что реализует протокол итерации .

Чтобы прояснить ситуацию, все приведенные выше примеры возвращаются falseк этому объекту, {a: 1, b: 2}потому что этот объект не реализует протокол итерации. Таким образом, вы не сможете перебирать его с помощью for...of НО, вы все еще можете с помощью for...in.

Поэтому, если вы хотите избежать болезненных ошибок, сделайте свой код более конкретным, переименовав метод, как показано ниже:

/**
 * @param variable
 * @returns {boolean}
 */
const hasIterationProtocol = variable =>
    variable !== null && Symbol.iterator in Object(variable);
Франческо Казула
источник
В тебе нет смысла. Как вы думаете, почему это порвется undefined?
adius
@adius Я думаю, я ошибочно предположил, что вы делаете object !== nullв своем ответе, но вы делаете это object != nullтак, что undefinedв этом конкретном случае он не нарушает . Я соответствующим образом обновил свой ответ.
Francesco Casula
Да я вижу. Кстати: ваш код неверен. hasIterationProtocol('')должен вернуться true! Как насчет того, чтобы удалить свой код и просто оставить повторяющийся раздел объяснений, который является единственным, что добавляет реальную ценность / что-то новое в ваш ответ.
adius
1
Он возвращается true, удалив часть старого ответа, я объединил обе функции и забыл о строгом сравнении. Теперь я вернул исходный ответ, который работал нормально.
Francesco Casula
1
Никогда бы не подумал об использовании Object(...), хороший улов. Но в этом случае нулевая проверка не нужна.
Domino
3

Для асинхронного итераторов вы должны проверить «Symbol.asyncIterator» вместо «Symbol.iterator»:

async function* doSomething(i) {
    yield 1;
    yield 2;
}

let obj = doSomething();

console.log(typeof obj[Symbol.iterator] === 'function');      // false
console.log(typeof obj[Symbol.asyncIterator] === 'function'); // true
Камарей
источник
2

В настоящее время, как уже говорилось, чтобы проверить, objявляется ли итерация, просто выполните

obj != null && typeof obj[Symbol.iterator] === 'function' 

Исторический ответ (более не действующий)

В for..of конструкция является частью проекта спецификации языка ECMASCript 6-го издания. Так что это могло измениться до финальной версии.

В этом черновике повторяющиеся объекты должны иметь функциюiterator как свойство.

Вы можете проверить, является ли объект повторяющимся, следующим образом:

function isIterable(obj){
   if(obj === undefined || obj === null){
      return false;
   }
   return obj.iterator !== undefined;
}
Ортомала Локни
источник
7
Свойство итератора было временной вещью Firefox. Это не совместимо с ES6. см .: developer.mozilla.org/en/docs/Web/JavaScript/Reference/…
Амир Арад,
1

Если вы хотите действительно проверить, является ли переменная объектом ( {key: value}) или массивом ( [value, value]), вы можете сделать это:

const isArray = function (a) {
    return Array.isArray(a);
};

const isObject = function (o) {
    return o === Object(o) && !isArray(o) && typeof o !== 'function';
};

function isIterable(variable) {
    return isArray(variable) || isObject(variable);
}
Сеглинглин
источник