Я попробую. Обратите внимание, что я не связан с 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свойство.
- Спецификация ES6 также предоставляет тип карты , который используется
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);
console.log(object[levelOneKey]);
var levelTwoObj = object[levelOneKey];
for (let levelTwoKey in levelTwoObj ) {
console.log(levelTwoKey);
console.log(levelTwoObj[levelTwoKey]);
}
}
... или вы также можете создать свой объект 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) {
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];
Symbol.iteratorсвойство, можно повторять. Так что вам просто нужно реализовать это свойство. Одно из возможных объяснений того, почему объекты не являются итерируемыми, может заключаться в том, что это будет означать, что все было итеративным, поскольку все является объектом (кроме, конечно, примитивов). Однако что означает перебор функции или объекта регулярного выражения?