Понять разницу между циклом по объекту и циклом по массиву
Быстрый вопрос. Вы не можете перебрать {x: 1, y:2}
с помощью цикла for-of. Почему?
const obj = { x: 1, y: 2 }; for (const key of obj) { // ERROR console.log(key); }
Если вы не знаете точную причину, вы попали в нужное место! В этом посте я расскажу о том, что можно повторить.
Давайте сначала посмотрим, что можно зациклить
Перво-наперво. В JavaScript есть некоторые значения, которые можно перебирать с помощью for-of, а некоторые значения нельзя перебирать. Как правило, вы можете использовать for-of с массивами.
Струны также можно перематывать.
Но эти значения нельзя переоценить.
Как только вы выполните for-of, который выдает ошибку, вы можете увидеть, что сообщение об ошибке выглядит странно.
Uncaught TypeError: 123 is not iterable
Ни одно из имен переменных не повторяется, но что это означает?
Итератор - это тот, который вам действительно нужен
Начиная с ES6, была добавлена новая функция Symbol
, которая предназначена для уникального значения. В функции Symbol
есть объект, который вы можете вызвать, Symbol.iterator
. Это функция, которая возвращает итератор по умолчанию для объекта.
Когда объект создается, JavaScript внутренне создает объект с именем %IteratorPrototype
и назначает его объекту-итератору вновь созданного объекта.
Когда этот объект используется, например, в цикле for-of, JavaScript проверяет, есть ли у него объект-итератор, который наследуется от %IteratorPrototype
, внутреннего базового прототипа JavaScript для итерации. Если его не существует, выдается следующая ошибка.
Uncaught TypeError: 123 is not iterable
Это означает, что %IteratorPrototype
должен использоваться объектом при его создании, чтобы его можно было зациклить, а объект-итератор объекта, например [1, 2, 3]
, называется Symbol.iterator
- его внутренний термин в спецификации - %%iterator
, но вы можете ' Не используйте это имя в своем коде.
Если Тип (O) не Object
, выбросить TypeError
исключение.
Symbol.iterator
У каждого отображаемого объекта есть Symbol.iterator
- Array, String, TypedArray, Map и Set. Вы можете явно использовать функцию итератора, чтобы перебрать его.
Когда вы получаете объект-итератор, он выглядит так.
{ __proto__: { next: f next(), Symbol(Symbol.toStringTag): "String Iterator" } }
У него есть функция next
, которая перемещает заголовок указателя индекса объекта, и метод Symbol.toStringTag
, который определяет, как он распечатывает значение при вызове его toString
.
Если вы вызываете next
, он возвращает это.
{ value: "h", done: false }
done
- это свойство, которое показывает, все ли индексы посещаются итератором, а value
- это значение текущего индекса.
Если вы снова вызовете next
, возвращаемое значение будет следующим.
{ value: "e", done: false }
Теперь вы знаете, почему value
равно “e”
.
Вы можете продолжать звонить next
и проверять, что value
будет «l», «l» и «o», таким образом, это «привет». Если вы вызовете next
еще раз, возвращаемое значение будет немного другим.
{ value: undefined, done: true }
value
теперь не определено, а done
равно true
.
Как только итератор перебирает все индексы строк или массивов или что-либо, что имеет Symbol.iterator
, его заголовок, который указывал на индексы, теперь указывает на пустое пространство.
Таким образом, value
будет неопределенным. И done
заменяется на true
, так как голова указывает на пустое пространство.
Тем не менее, вы можете продолжать звонить next
, он просто даст тот же ответ, где value
не определено, а done
истинно.
Сделайте объект итерируемым
Теперь давайте создадим итерируемый объект, используя Symbol.iterator
.
const obj = { x: 1, y: 2 };
Это был оригинальный объект. Что я собираюсь сделать, так это добавить функцию [Symbol.iterator]
, чтобы сделать ее циклической.
Код выглядит довольно запутанным, но я объясню вам, как это работает.
next()
имеет область действия для this
, которая относится к next()
. Итак, this
в next()
не будет смотреть на obj
.
Итак, во-первых, я использовал Object.entries()
, чтобы получить каждую запись в obj
.
// Object.entries(obj) [ 0: ["x", 1], 1: ["y", 2], length: 2 ]
Затем я создал свойство index для обхода entries
- i: 0
.
if (this.i >= this.entries.length) { return { done: true, value: undefined }; }
Если i
равно или больше длины entries
, что означает, что итератор завершает цикл по entries
, он начинает возвращать { done: true, value: undefined }
из следующего вызова итератора.
Пока не будет выполнено указанное выше условие, возвращаемое значение будет:
{ done: false, value: { [this.entries[this.i][0]]: this.entries[this.i++][1] } }
Теперь данные будут успешно напечатаны.
const (let k of obj) { console.log(k); } // { x: 1 } // { y: 2 }
Заключение
Фактором, определяющим, является ли объект в JavaScript циклическим, является то, есть ли у него Symbol.iterator
внутри. Только объекты, которые имеют Symbol.iterator
, могут быть зациклены - обычно массив.
Таким образом, такие объекты, как { x: 1, y: 2 }
, не могут быть отображены, но есть способ сделать их отображаемыми. Вы можете добавить к ним индивидуальный next()
метод, который я лично не рекомендую - это слишком много работы.