Por que os objetos não são iteráveis por padrão?
Eu vejo perguntas o tempo todo relacionadas à iteração de objetos, a solução comum sendo iterar sobre as propriedades de um objeto e acessar os valores dentro de um objeto dessa maneira. Isso parece tão comum que me faz pensar por que os próprios objetos não são iteráveis.
Instruções como o ES6 for...of
seriam boas para usar para objetos por padrão. Como esses recursos estão disponíveis apenas para "objetos iteráveis" especiais que não incluem {}
objetos, temos que passar por muitos obstáculos para fazer isso funcionar para os objetos para os quais queremos usá-lo.
A instrução for ... of cria um loop Iterando sobre objetos iteráveis (incluindo Array, Map, Set, objetos de argumentos e assim por diante) ...
Por exemplo, usando uma função de gerador 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);
}
}
Os dados acima registram corretamente os dados na ordem que eu espero quando executo o código no Firefox (que suporta ES6 ):
Por padrão, os {}
objetos não são iteráveis, mas por quê? As desvantagens superariam os benefícios potenciais de objetos serem iteráveis? Quais são os problemas associados a isso?
Além disso, porque {}
os objetos são diferentes de "array-like" coleções e "iterable objetos", como NodeList
, HtmlCollection
, e arguments
, eles não podem ser convertidas em matrizes.
Por exemplo:
var argumentsArray = Array.prototype.slice.call(arguments);
ou ser usado com métodos Array:
Array.prototype.forEach.call(nodeList, function (element) {})
.
Além das perguntas que tenho acima, eu adoraria ver um exemplo {}
prático sobre como transformar objetos em iteráveis, especialmente daqueles que mencionaram o [Symbol.iterator]
. Isso deve permitir que esses novos {}
"objetos iteráveis" usem instruções como for...of
. Além disso, gostaria de saber se tornar os objetos iteráveis permite que eles sejam convertidos em Arrays.
Tentei o código abaixo, mas recebo um 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
propriedade é iterável. Então, você apenas teria que implementar essa propriedade. Uma possível explicação de por que os objetos não são iteráveis poderia ser que isso implicaria que tudo era iterável, já que tudo é um objeto (exceto primitivos, é claro). No entanto, o que significa iterar sobre uma função ou um objeto de expressão regular?Respostas:
Vou tentar. Observe que não sou afiliado à ECMA e não tenho visibilidade de seu processo de tomada de decisão, portanto, não posso dizer com certeza por que eles fizeram ou não fizeram nada. No entanto, vou expor minhas suposições e dar o meu melhor.
1. Por que adicionar uma
for...of
construção em primeiro lugar?JavaScript já inclui uma
for...in
construção que pode ser usada para iterar as propriedades de um objeto. No entanto, não é realmente um loop forEach , já que enumera todas as propriedades em um objeto e tende a funcionar de forma previsível apenas em casos simples.Ele quebra em casos mais complexos (incluindo com matrizes, onde seu uso tende a ser desencorajado ou completamente ofuscado pelas salvaguardas necessárias para o uso correto de
for...in
uma matriz ). Você pode contornar isso usando (entre outras coisas), mas isso é um pouco desajeitado e deselegante.hasOwnProperty
Portanto, minha suposição é que o
for...of
construto está sendo adicionado para resolver as deficiências associadas aofor...in
construto e fornecer maior utilidade e flexibilidade ao iterar coisas. As pessoas tendem a tratarfor...in
como umforEach
loop que pode ser geralmente aplicado a qualquer coleção e produzir resultados sensatos em qualquer contexto possível, mas não é isso que acontece. Ofor...of
loop corrige isso.Também presumo que seja importante que o código ES5 existente seja executado em ES6 e produza o mesmo resultado que em ES5, portanto, não podem ser feitas alterações significativas, por exemplo, no comportamento da
for...in
construção.2. Como
for...of
funciona?A documentação de referência é útil para esta parte. Especificamente, um objeto é considerado
iterable
se definir aSymbol.iterator
propriedade.A definição da propriedade deve ser uma função que retorna os itens na coleção, um, por, um, e define um sinalizador indicando se há ou não mais itens para buscar. Implementações predefinidas são fornecidas para alguns tipos de objeto e é relativamente claro que usar
for...of
simplesmente delegados para a função iteradora.Essa abordagem é útil, pois torna muito simples fornecer seus próprios iteradores. Eu poderia dizer que a abordagem poderia ter apresentado problemas práticos devido à sua confiança na definição de uma propriedade onde anteriormente não havia nenhuma, exceto pelo que posso dizer que não é o caso, pois a nova propriedade é essencialmente ignorada, a menos que você deliberadamente vá procurá-la (ou seja, não se apresentará em
for...in
loops como uma chave, etc.). Então esse não é o caso.Deixando de lado as questões práticas, pode ter sido considerado conceitualmente controverso iniciar todos os objetos com uma nova propriedade predefinida ou dizer implicitamente que "todo objeto é uma coleção".
3. Por que os objetos não são
iterable
usadosfor...of
por padrão?Meu palpite é que esta é uma combinação de:
iterable
por padrão pode ter sido considerado inaceitável porque adiciona uma propriedade onde antes não havia nenhuma, ou porque um objeto não é (necessariamente) uma coleção. Como Felix observa, "o que significa iterar sobre uma função ou um objeto de expressão regular"?for...in
, e não está claro o que uma implementação de iterador embutido poderia ter feito de forma diferente / melhor do que ofor...in
comportamento existente . Portanto, mesmo se o nº 1 estiver errado e adicionar a propriedade for aceitável, pode não ter sido considerado útil .iterable
podem fazê-lo facilmente, definindo aSymbol.iterator
propriedade.iterable
por padrão e tem algumas outras pequenas vantagens sobre o uso de um objeto simples como umMap
.Existe até um exemplo fornecido para o nº 3 na documentação de referência:
var myIterable = {}; myIterable[Symbol.iterator] = function* () { yield 1; yield 2; yield 3; }; for (var value of myIterable) { console.log(value); }
Dado que os objetos podem ser facilmente feitos
iterable
, que eles já podem ser iterados usandofor...in
e que provavelmente não há um acordo claro sobre o que um iterador de objeto padrão deve fazer (se o que ele faz é para ser de alguma forma diferente do quefor...in
faz), parece razoável o suficiente para que os objetos não fossem feitositerable
por padrão.Observe que seu código de exemplo pode ser reescrito usando
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" } }
... ou você também pode fazer seu objeto
iterable
da maneira que quiser, fazendo algo como o seguinte (ou você pode fazer todos os objetositerable
atribuindo aObject.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 }; };
Você pode brincar com isso aqui (no Chrome, pelo menos): http://jsfiddle.net/rncr3ppz/5/
Editar
E em resposta à sua pergunta atualizada, sim, é possível converter um
iterable
em um array, usando o operador de propagação no ES6.No entanto, isso não parece estar funcionando no Chrome ainda, ou pelo menos não consigo fazê-lo funcionar no meu jsFiddle. Em teoria, deveria ser tão simples como:
var array = [...myIterable];
fonte
obj[Symbol.iterator] = obj[Symbol.enumerate]
em seu último exemplo?[[enumerate]]
não é um símbolo conhecido (@@ enumerate), mas um método interno. Eu teria que serobj[Symbol.iterator] = function(){ return Reflect.enumerate(this) }
for...in
, o que tem um propósito diferente - iterar nas propriedades de um objeto.Map
por enquanto.Object
s não implementam os protocolos de iteração em Javascript por boas razões. Existem dois níveis nos quais as propriedades do objeto podem ser iteradas em JavaScript:Iteração de nível de programa
Quando você itera sobre um objeto no nível do programa, examina uma parte da estrutura do programa. É uma operação reflexiva. Vamos ilustrar essa instrução com um tipo de array, que geralmente é iterado no nível dos dados:
const xs = [1,2,3]; xs.f = function f() {}; for (let i in xs) console.log(xs[i]); // logs `f` as well
Acabamos de examinar o nível do programa de
xs
. Como os arrays armazenam sequências de dados, estamos regularmente interessados apenas no nível de dados.for..in
evidentemente, não faz sentido em conexão com matrizes e outras estruturas "orientadas a dados" na maioria dos casos. Esta é a razão pela qual ES2015 introduziufor..of
o protocolo iterável.Iteração de nível de dados
Isso significa que podemos simplesmente distinguir os dados do nível do programa distinguindo funções de tipos primitivos? Não, porque as funções também podem ser dados em Javascript:
Array.prototype.sort
por exemplo, espera que uma função execute um certo algoritmo de classificação() => 1 + 2
são apenas invólucros funcionais para valores avaliados lentamenteAlém disso, os valores primitivos também podem representar o nível do programa:
[].length
por exemplo, é a,Number
mas representa o comprimento de uma matriz e, portanto, pertence ao domínio do programaIsso significa que não podemos distinguir o programa e o nível de dados apenas verificando os tipos.
É importante entender que a implementação dos protocolos de iteração para objetos JavaScript antigos simples dependeria do nível de dados. Mas, como acabamos de ver, uma distinção confiável entre dados e iteração em nível de programa não é possível.
Com
Array
s, essa distinção é trivial: cada elemento com uma chave semelhante a um inteiro é um elemento de dados.Object
s têm um recurso equivalente: oenumerable
descritor. Mas é realmente aconselhável confiar nisso? Eu acredito que não é! O significado doenumerable
descritor é muito borrado.Conclusão
Não há uma maneira significativa de implementar os protocolos de iteração para objetos, porque nem todo objeto é uma coleção.
Se as propriedades do objeto fossem iteráveis por padrão, o programa e o nível de dados eram misturados. Uma vez que cada tipo composto em Javascript é baseado em objetos simples, isso se aplicaria para
Array
eMap
bem.for..in
,Object.keys
,Reflect.ownKeys
Etc, podem ser usados tanto para a reflexão e dados iteração, uma clara distinção não é regularmente possível. Se você não for cuidadoso, acabará rapidamente com metaprogramação e dependências estranhas. OMap
tipo de dados abstratos efetivamente termina a fusão do nível de programa e dados. Acredito queMap
seja a conquista mais significativa no ES2015, emboraPromise
seja muito mais emocionante.fonte
Acho que a pergunta deveria ser "por que não há iteração de objeto embutido ?
Adicionar iterabilidade aos próprios objetos pode ter consequências indesejadas e não, não há como garantir a ordem, mas escrever um iterador é tão simples quanto
function* iterate_object(o) { var keys = Object.keys(o); for (var i=0; i<keys.length; i++) { yield [keys[i], o[keys[i]]]; } }
Então
for (var [key, val] of iterate_object({a: 1, b: 2})) { console.log(key, val); } a 1 b 2
fonte
[Symbol.iterator]
, bem como se você pudesse expandir essas consequências não intencionais.Você pode facilmente tornar todos os objetos iteráveis globalmente:
Object.defineProperty(Object.prototype, Symbol.iterator, { enumerable: false, value: function * (){ for(let key in this){ if(this.hasOwnProperty(key)){ yield [key, this[key]]; } } } });
fonte
Esta é a abordagem mais recente (que funciona em canário cromado)
var files = { '/root': {type: 'directory'}, '/root/example.txt': {type: 'file'} }; for (let [key, {type}] of Object.entries(files)) { console.log(type); }
Sim,
entries
agora é um método que faz parte do Object :)editar
Depois de analisar mais a fundo, parece que você poderia fazer o seguinte
Object.prototype[Symbol.iterator] = function * () { for (const [key, value] of Object.entries(this)) { yield {key, value}; // or [key, value] } };
então agora você pode fazer isso
for (const {key, value:{type}} of files) { console.log(key, type); }
edit2
Voltando ao seu exemplo original, se você quiser usar o método de protótipo acima, gostaria que este
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); } }
fonte
Eu também estava incomodado com esta questão.
Então tive a ideia de usar
Object.entries({...})
, ele retorna umArray
que é umIterable
.Além disso, o Dr. Axel Rauschmayer postou uma excelente resposta sobre isso. Veja por que objetos simples NÃO são iteráveis
fonte
Tecnicamente, esta não é uma resposta à pergunta por quê? mas adaptei a resposta de Jack Slocum acima à luz dos comentários de BT para algo que pode ser usado para tornar um objeto iterável.
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);
Não é tão conveniente quanto deveria, mas é viável, especialmente se você tiver vários objetos:
var instruments={ 'a': 'accordion', 'b': 'banjo', 'c': 'cor anglais' }; Object.defineProperty(instruments,Symbol.iterator,iterableProperties); for(let v of instruments) console.log(v);
E, como cada um tem direito a uma opinião, também não consigo ver por que os objetos já não são iteráveis. Se você pode polyfill como acima, ou use
for … in
então não consigo ver um argumento simples.Uma sugestão possível é que o que é iterável é um tipo de objeto, então é possível que iterável tenha sido limitado a um subconjunto de objetos, apenas no caso de algum outro objeto explodir na tentativa.
fonte