Почему по умолчанию объекты не повторяются?
Я все время вижу вопросы, связанные с итерацией объектов, обычное решение - перебирать свойства объекта и таким образом получать доступ к значениям внутри объекта. Это кажется настолько обычным, что мне интересно, почему сами объекты не повторяются.
Такие утверждения, как ES6, for...of
было бы неплохо использовать для объектов по умолчанию. Поскольку эти функции доступны только для специальных «повторяемых объектов», которые не включают {}
объекты, мы должны пройти через все препятствия, чтобы заставить эту работу работать с объектами, для которых мы хотим ее использовать.
Оператор for ... of создает цикл, перебирающий итерируемые объекты (включая массив, карту, набор, объект аргументов и т. Д.) ...
Например, используя функцию генератора ES6 :
var example = {a: {e: 'one', f: 'two'}, b: {g: 'three'}, c: {h: 'four', i: 'five'}};
function* entries(obj) {
for (let key of Object.keys(obj)) {
yield [key, obj[key]];
}
}
for (let [key, value] of entries(example)) {
console.log(key);
console.log(value);
for (let [key, value] of entries(value)) {
console.log(key);
console.log(value);
}
}
Вышеупомянутое правильно регистрирует данные в том порядке, в котором я ожидал, когда я запускаю код в Firefox (который поддерживает ES6 ):
По умолчанию {}
объекты не повторяются, но почему? Перевесят ли недостатки потенциальные преимущества итерации объектов? Какие проблемы с этим связаны?
Кроме того, поскольку {}
объекты отличаются от «Array-как» коллекция и «итерация объектов» , таких как NodeList
, HtmlCollection
и arguments
они не могут быть преобразованы в массивы.
Например:
var argumentsArray = Array.prototype.slice.call(arguments);
или использоваться с методами массива:
Array.prototype.forEach.call(nodeList, function (element) {})
.
Помимо вопросов, которые у меня есть выше, мне бы хотелось увидеть рабочий пример того, как превратить {}
объекты в итерируемые объекты, особенно от тех, кто упомянул [Symbol.iterator]
. Это должно позволить этим новым {}
«повторяемым объектам» использовать такие операторы, как for...of
. Кроме того, мне интересно, позволяет ли создание итерируемых объектов преобразовывать их в массивы.
Я попробовал приведенный ниже код, но у меня появился файл TypeError: can't convert undefined to object
.
var example = {a: {e: 'one', f: 'two'}, b: {g: 'three'}, c: {h: 'four', i: 'five'}};
// I want to be able to use "for...of" for the "example" object.
// I also want to be able to convert the "example" object into an Array.
example[Symbol.iterator] = function* (obj) {
for (let key of Object.keys(obj)) {
yield [key, obj[key]];
}
};
for (let [key, value] of example) { console.log(value); } // error
console.log([...example]); // error
источник
Symbol.iterator
свойство, можно повторять. Так что вам просто нужно реализовать это свойство. Одно из возможных объяснений того, почему объекты не являются итерируемыми, может заключаться в том, что это будет означать, что все было итеративным, поскольку все является объектом (кроме, конечно, примитивов). Однако что означает перебор функции или объекта регулярного выражения?Ответы:
Я попробую. Обратите внимание, что я не связан с ECMA и не имею представления о процессе принятия ими решений, поэтому я не могу однозначно сказать, почему они что- то сделали или не сделали. Однако я выскажу свои предположения и сделаю все возможное.
1. Зачем вообще нужно добавлять
for...of
конструкцию?JavaScript уже включает в себя
for...in
конструкцию, которая может использоваться для итерации свойств объекта. Однако на самом деле это не цикл forEach , поскольку он перечисляет все свойства объекта и работает предсказуемо только в простых случаях.Он не работает в более сложных случаях (в том числе с массивами, когда его использование обычно либо не рекомендуется, либо полностью запутывается мерами безопасности, необходимыми для правильного использования
for...in
с массивом ). Вы можете обойти это, используя (среди прочего), но это немного неуклюже и неэлегантно.hasOwnProperty
Поэтому я предполагаю, что
for...of
конструкция добавляется для устранения недостатков, связанных сfor...in
конструкцией, и обеспечения большей полезности и гибкости при итерации. Люди склонны рассматривать ихfor...in
какforEach
цикл, который может применяться к любой коллекции и давать разумные результаты в любом возможном контексте, но это не то, что происходит. Вfor...of
затруднительном цикле , который.Я также предполагаю, что важно, чтобы существующий код ES5 работал под ES6 и давал тот же результат, что и под ES5, поэтому критические изменения не могут быть внесены, например, в поведение
for...in
конструкции.2. Как
for...of
работает?Справочная документация полезна для этой части. В частности, объект считается,
iterable
если он определяетSymbol.iterator
свойство.Определение свойства должно быть функцией, которая возвращает элементы в коллекции, один за другим, и устанавливает флаг, указывающий, есть ли еще элементы для выборки. Предопределенные реализации предусмотрены для некоторых типов объектов , и относительно ясно, что использование
for...of
просто делегирует функцию итератора.Этот подход полезен, поскольку он позволяет очень просто предоставить свои собственные итераторы. Я мог бы сказать, что этот подход мог вызвать практические проблемы из-за того, что он полагался на определение свойства там, где раньше его не было, за исключением того, что я могу сказать, что это не так, поскольку новое свойство по существу игнорируется, если вы специально не ищите его (т. Е. он не будет присутствовать в
for...in
циклах как ключ и т. д.). Так что дело не в этом.Если оставить в стороне практические проблемы, то, возможно, считалось концептуально спорным начинать все объекты с нового предварительно определенного свойства или неявно говорить, что «каждый объект является коллекцией».
3. Почему по умолчанию объекты не
iterable
используютсяfor...of
?Я предполагаю , что это комбинация:
iterable
по умолчанию могло считаться неприемлемым, потому что оно добавляет свойство там, где раньше его не было, или потому что объект не (обязательно) является коллекцией. Как отмечает Феликс, «что значит перебирать функцию или объект регулярного выражения»?for...in
, и не ясно, что реализация встроенного итератора могла бы сделать иначе / лучше, чем существующееfor...in
поведение. Таким образом, даже если №1 ошибочен и добавление свойства было приемлемым, оно могло оказаться бесполезным .iterable
могут легко сделать это, определивSymbol.iterator
свойство.iterable
по умолчанию и имеет некоторые другие небольшие преимущества по сравнению с использованием простого объекта в качествеMap
.В справочной документации есть даже пример для №3:
var myIterable = {}; myIterable[Symbol.iterator] = function* () { yield 1; yield 2; yield 3; }; for (var value of myIterable) { console.log(value); }
Учитывая, что объекты могут быть легко созданы
iterable
, что они уже могут быть повторены с использованиемfor...in
и что, вероятно, нет четкого соглашения о том, что должен делать итератор объекта по умолчанию (если то, что он делает, должно как-то отличаться от того, чтоfor...in
делает), это кажется разумным Достаточно того, чтоiterable
по умолчанию объекты не делались .Обратите внимание, что ваш пример кода можно переписать, используя
for...in
:for (let levelOneKey in object) { console.log(levelOneKey); // "example" console.log(object[levelOneKey]); // {"random":"nest","another":"thing"} var levelTwoObj = object[levelOneKey]; for (let levelTwoKey in levelTwoObj ) { console.log(levelTwoKey); // "random" console.log(levelTwoObj[levelTwoKey]); // "nest" } }
... или вы также можете создать свой объект
iterable
так, как хотите, выполнив что-то вроде следующего (или вы можете создать все объектыiterable
, назначивObject.prototype[Symbol.iterator]
вместо этого):obj = { a: '1', b: { something: 'else' }, c: 4, d: { nested: { nestedAgain: true }} }; obj[Symbol.iterator] = function() { var keys = []; var ref = this; for (var key in this) { //note: can do hasOwnProperty() here, etc. keys.push(key); } return { next: function() { if (this._keys && this._obj && this._index < this._keys.length) { var key = this._keys[this._index]; this._index++; return { key: key, value: this._obj[key], done: false }; } else { return { done: true }; } }, _index: 0, _keys: keys, _obj: ref }; };
Вы можете поиграть с этим здесь (в Chrome, если хотите): http://jsfiddle.net/rncr3ppz/5/
редактировать
И в ответ на ваш обновленный вопрос: да, можно преобразовать
iterable
в массив, используя оператор распространения в ES6.Однако, похоже, это еще не работает в Chrome, или, по крайней мере, я не могу заставить его работать в моем jsFiddle. Теоретически это должно быть так просто:
var array = [...myIterable];
источник
obj[Symbol.iterator] = obj[Symbol.enumerate]
в вашем последнем примере?[[enumerate]]
это не общеизвестный символ (@@ enumerate), а внутренний метод. Я бы должен был бытьobj[Symbol.iterator] = function(){ return Reflect.enumerate(this) }
for...in
, имеющего другую цель - перебирать свойства объекта.Map
на данный момент.Object
s не реализуют протоколы итераций в Javascript по очень веским причинам. Есть два уровня, на которых свойства объекта можно перебирать в JavaScript:Итерация уровня программы
Когда вы перебираете объект на программном уровне, вы исследуете часть структуры своей программы. Это рефлексивная операция. Давайте проиллюстрируем этот оператор с помощью типа массива, который обычно повторяется на уровне данных:
const xs = [1,2,3]; xs.f = function f() {}; for (let i in xs) console.log(xs[i]); // logs `f` as well
Мы только что изучили программный уровень
xs
. Поскольку в массивах хранятся последовательности данных, нас обычно интересует только уровень данных.for..in
очевидно, в большинстве случаев не имеет смысла в связи с массивами и другими «ориентированными на данные» структурами. Это причина, по которой ES2015 представилfor..of
итеративный протокол.Итерация уровня данных
Означает ли это, что мы можем просто отличать данные от уровня программы, отделяя функции от примитивных типов? Нет, потому что функции также могут быть данными в Javascript:
Array.prototype.sort
например, ожидает, что функция будет выполнять определенный алгоритм сортировки() => 1 + 2
- это просто функциональные оболочки для лениво вычисляемых значений.Помимо примитивов, значения могут также представлять программный уровень:
[].length
например,Number
но представляет длину массива и, следовательно, принадлежит программной областиЭто означает, что мы не можем различить программу и уровень данных, просто проверяя типы.
Важно понимать, что реализация итерационных протоколов для простых старых объектов Javascript будет зависеть от уровня данных. Но, как мы только что убедились, достоверное различие между итерацией на уровне данных и программным обеспечением невозможно.
С
Array
s это различие тривиально: каждый элемент с целочисленным ключом является элементом данных.Object
s имеют эквивалентную функцию:enumerable
дескриптор. Но действительно ли целесообразно на это полагаться? Я верю, что это не так! Значениеenumerable
дескриптора слишком размыто.Вывод
Нет осмысленного способа реализовать протоколы итерации для объектов, потому что не каждый объект является коллекцией.
Если свойства объекта были итерируемыми по умолчанию, уровень программы и данных смешивался. Поскольку каждый составной тип в Javascript основан на простых объектах, это также применимо к
Array
иMap
.for..in
,Object.keys
иReflect.ownKeys
т. д. могут использоваться как для отражения, так и для итерации данных, четкое различие обычно невозможно. Если вы не будете осторожны, вы быстро закончите с метапрограммированием и странными зависимостями.Map
Тип абстрактных данных эффективно завершает приравнивая программы и уровня данных. Я считаю,Map
что это самое значительное достижение ES2015, даже если оноPromise
намного интереснее.источник
Думаю, должен возникнуть вопрос: «Почему нет встроенной итерации объекта?»
Добавление итеративности к самим объектам может иметь непредвиденные последствия, и нет, нет способа гарантировать порядок, но написать итератор так же просто, как
function* iterate_object(o) { var keys = Object.keys(o); for (var i=0; i<keys.length; i++) { yield [keys[i], o[keys[i]]]; } }
затем
for (var [key, val] of iterate_object({a: 1, b: 2})) { console.log(key, val); } a 1 b 2
источник
[Symbol.iterator]
а также, если бы вы могли расширить эти непредвиденные последствия.Вы можете легко сделать все объекты итерируемыми глобально:
Object.defineProperty(Object.prototype, Symbol.iterator, { enumerable: false, value: function * (){ for(let key in this){ if(this.hasOwnProperty(key)){ yield [key, this[key]]; } } } });
источник
Это последний подход (который работает в chrome canary)
var files = { '/root': {type: 'directory'}, '/root/example.txt': {type: 'file'} }; for (let [key, {type}] of Object.entries(files)) { console.log(type); }
Да
entries
, теперь это метод, который является частью Object :)редактировать
Изучив его подробнее, кажется, что вы могли бы сделать следующее
Object.prototype[Symbol.iterator] = function * () { for (const [key, value] of Object.entries(this)) { yield {key, value}; // or [key, value] } };
так что теперь ты можешь сделать это
for (const {key, value:{type}} of files) { console.log(key, type); }
edit2
Вернемся к вашему исходному примеру, если вы хотите использовать вышеупомянутый метод прототипа, он бы хотел, чтобы это
for (const {key, value:item1} of example) { console.log(key); console.log(item1); for (const {key, value:item2} of item1) { console.log(key); console.log(item2); } }
источник
Меня тоже беспокоил этот вопрос.
Затем мне пришла в голову идея использовать
Object.entries({...})
, он возвращает,Array
который являетсяIterable
.Кроме того, доктор Аксель Раушмайер опубликовал отличный ответ по этому поводу. См. Почему простые объекты НЕ повторяются
источник
Технически это не ответ на вопрос, почему? но я адаптировал приведенный выше ответ Джека Слокума в свете комментариев BT к чему-то, что можно использовать для того, чтобы сделать объект повторяемым.
var iterableProperties={ enumerable: false, value: function * () { for(let key in this) if(this.hasOwnProperty(key)) yield this[key]; } }; var fruit={ 'a': 'apple', 'b': 'banana', 'c': 'cherry' }; Object.defineProperty(fruit,Symbol.iterator,iterableProperties); for(let v of fruit) console.log(v);
Не так удобно, как должно было быть, но вполне работоспособно, особенно если у вас несколько объектов:
var instruments={ 'a': 'accordion', 'b': 'banjo', 'c': 'cor anglais' }; Object.defineProperty(instruments,Symbol.iterator,iterableProperties); for(let v of instruments) console.log(v);
И поскольку каждый имеет право на свое мнение, я не могу понять, почему объекты еще не являются итеративными. Если вы можете полифилить их, как указано выше, или использовать,
for … in
тогда я не вижу простого аргумента.Одно из возможных предположений состоит в том, что итерируемый объект является типом объекта, поэтому возможно, что итерабельность была ограничена подмножеством объектов на случай, если при попытке взорвутся некоторые другие объекты.
источник