javascript arrays iterator ecmascript-6 ecmascript-5

¿Por qué los objetos no son iterables en JavaScript?



arrays iterator (6)

¿Por qué los objetos no son iterables por defecto?

Veo preguntas todo el tiempo relacionadas con la iteración de objetos, la solución común es iterar sobre las propiedades de un objeto y acceder a los valores dentro de un objeto de esa manera. Esto parece tan común que me hace preguntarme por qué los objetos en sí no son iterables.

Declaraciones como el ES6 for...of sería bueno usar para objetos por defecto. Debido a que estas características solo están disponibles para "objetos iterables" especiales que no incluyen objetos {} , tenemos que pasar por aros para que esto funcione para los objetos para los que queremos usarlo.

La instrucción for ... of crea un bucle que itera sobre objetos iterables (incluidos Array, Map, Set, argumentos, etc.) ...

Por ejemplo, usando una función de generador 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); } }

Lo anterior registra correctamente los datos en el orden que espero cuando ejecuto el código en Firefox (que admite ES6 ):

Por defecto, los objetos {} no son iterables, pero ¿por qué? ¿Las desventajas superarían los beneficios potenciales de que los objetos sean iterables? ¿Cuáles son los problemas asociados con esto?

Además, dado que los objetos {} son diferentes de las colecciones "tipo matriz" y los "objetos iterables" como NodeList , HtmlCollection y arguments , no se pueden convertir en matrices.

Por ejemplo:

var argumentsArray = Array.prototype.slice.call(arguments);

o ser utilizado con los métodos de matriz:

Array.prototype.forEach.call(nodeList, function (element) {}) .

Además de las preguntas que tengo arriba, me encantaría ver un ejemplo de trabajo sobre cómo convertir {} objetos en iterables, especialmente de aquellos que han mencionado el [Symbol.iterator] . Esto debería permitir que estos nuevos {} "objetos iterables" utilicen sentencias como for...of . Además, me pregunto si hacer que los objetos sean iterables les permite convertirse en matrices.

Intenté el siguiente código, pero obtengo un 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


Este es el último enfoque (que funciona en Chrome Chrome)

var files = { ''/root'': {type: ''directory''}, ''/root/example.txt'': {type: ''file''} }; for (let [key, {type}] of Object.entries(files)) { console.log(type); }

Sí, las entries ahora son un método que forma parte de Object :)

editar

Después de analizarlo más, parece que podrías hacer lo siguiente

Object.prototype[Symbol.iterator] = function * () { for (const [key, value] of Object.entries(this)) { yield {key, value}; // or [key, value] } };

así que ahora puedes hacer esto

for (const {key, value:{type}} of files) { console.log(key, type); }

edit2

Volviendo a su ejemplo original, si desea utilizar el método prototipo anterior, le gustaría que esto

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); } }


Puede hacer que todos los objetos sean iterables globalmente:

Object.defineProperty(Object.prototype, Symbol.iterator, { enumerable: false, value: function * (){ for(let key in this){ if(this.hasOwnProperty(key)){ yield [key, this[key]]; } } } });


Supongo que la pregunta debería ser "¿por qué no hay iteración de objetos incorporada ?

Agregar iterabilidad a los propios objetos podría tener consecuencias no deseadas, y no, no hay forma de garantizar el orden, pero escribir un iterador es tan simple como

function* iterate_object(o) { var keys = Object.keys(o); for (var i=0; i<keys.length; i++) { yield [keys[i], o[keys[i]]]; } }

Entonces

for (var [key, val] of iterate_object({a: 1, b: 2})) { console.log(key, val); } a 1 b 2


Técnicamente, esta no es una respuesta a la pregunta ¿por qué? pero he adaptado la respuesta de Jack Slocum anterior a la luz de los comentarios de BT a algo que se puede usar para hacer que un objeto sea iterable.

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);

No es tan conveniente como debería haber sido, pero es viable, especialmente si tiene varios objetos:

var instruments={ ''a'': ''accordion'', ''b'': ''banjo'', ''c'': ''cor anglais'' }; Object.defineProperty(instruments,Symbol.iterator,iterableProperties); for(let v of instruments) console.log(v);

Y, dado que todos tienen derecho a una opinión, tampoco puedo ver por qué los objetos no son iterables. Si puede rellenarlos como anteriormente, o usarlos for … in ese momento, no puedo ver un argumento simple.

Una posible sugerencia es que lo que es iterable es un tipo de objeto, por lo que es posible que iterable se haya limitado a un subconjunto de objetos en caso de que otros objetos exploten en el intento.


Voy a intentar esto. Tenga en cuenta que no estoy afiliado a ECMA y no tengo visibilidad en su proceso de toma de decisiones, por lo que no puedo decir definitivamente por qué han hecho o no han hecho nada. Sin embargo, expondré mis suposiciones y haré mi mejor intento.

1. ¿Por qué agregar un for...of construcción en primer lugar?

JavaScript ya incluye una construcción for...in que se puede usar para iterar las propiedades de un objeto. Sin embargo, no es realmente un ciclo forEach , ya que enumera todas las propiedades de un objeto y tiende a funcionar de manera predecible en casos simples.

Se desglosa en casos más complejos (incluso con matrices, donde su uso tiende a ser desalentado o completamente ofuscado por las salvaguardas necesarias para usar for...in con una matriz correctamente ). Puede hasOwnProperty utilizando hasOwnProperty (entre otras cosas), pero eso es un poco torpe e poco elegante.

Por lo tanto, mi suposición es que el for...of construct se está agregando para abordar las deficiencias asociadas con el for...in construct, y proporcionar una mayor utilidad y flexibilidad al iterar las cosas. Las personas tienden a tratar for...in como un bucle forEach que generalmente se puede aplicar a cualquier colección y producir resultados sanos en cualquier contexto posible, pero eso no es lo que sucede. El for...of loop soluciona eso.

También supongo que es importante que el código ES5 existente se ejecute en ES6 y produzca el mismo resultado que en ES5, por lo que no se pueden realizar cambios importantes, por ejemplo, en el comportamiento de for...in construct.

2. ¿Cómo funciona for...of trabajo?

La documentación de referencia es útil para esta parte. Específicamente, un objeto se considera iterable si define la propiedad Symbol.iterator .

La definición de propiedad debe ser una función que devuelva los elementos de la colección, uno, por, uno, y establezca un indicador que indique si hay más elementos para recuperar. Se proporcionan implementaciones predefinidas para algunos tipos de objetos , y es relativamente claro que usar for...of simplemente delega a la función de iterador.

Este enfoque es útil, ya que hace que sea muy sencillo proporcionar sus propios iteradores. Podría decir que el enfoque podría haber presentado problemas prácticos debido a su dependencia de definir una propiedad donde anteriormente no había ninguna, excepto por lo que puedo decir que ese no es el caso, ya que la nueva propiedad se ignora esencialmente a menos que la busque deliberadamente (es decir no se presentará en for...in loops como una clave, etc.). Entonces ese no es el caso.

Dejando a un lado los problemas prácticos, puede haberse considerado conceptualmente controvertido comenzar todos los objetos con una nueva propiedad predefinida, o decir implícitamente que "cada objeto es una colección".

3. ¿Por qué los objetos no son iterable usando for...of por defecto?

Mi conjetura es que esta es una combinación de:

  1. Hacer que todos los objetos sean iterable por defecto puede haberse considerado inaceptable porque agrega una propiedad donde anteriormente no había ninguno, o porque un objeto no es (necesariamente) una colección. Como señala Felix, "¿qué significa iterar sobre una función o un objeto de expresión regular"?
  2. Los objetos simples ya se pueden iterar usando for...in , y no está claro qué podría haber hecho una implementación de iterador integrada diferente / mejor que la existente for...in comportamiento. Entonces, incluso si el n. ° 1 es incorrecto y agregar la propiedad fue aceptable, puede que no se haya visto como útil .
  3. Los usuarios que desean que sus objetos sean iterable pueden hacerlo fácilmente definiendo la propiedad Symbol.iterator .
  4. La especificación ES6 también proporciona un tipo de Map , que es iterable por defecto y tiene algunas otras pequeñas ventajas sobre el uso de un objeto plano como un Map .

Incluso hay un ejemplo proporcionado para el n. ° 3 en la documentación de referencia:

var myIterable = {}; myIterable[Symbol.iterator] = function* () { yield 1; yield 2; yield 3; }; for (var value of myIterable) { console.log(value); }

Dado que los objetos se pueden hacer fácilmente iterable , que ya se pueden iterar usando for...in , y que es probable que no haya un acuerdo claro sobre lo que debe hacer un iterador de objetos predeterminado (si lo que hace debe ser de alguna manera diferente de lo que for...in does), parece bastante razonable que los objetos no se hayan hecho iterable por defecto.

Tenga en cuenta que su código de ejemplo puede reescribirse 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" } }

... o también puede hacer que su objeto sea iterable de la manera que desee haciendo algo como lo siguiente (o puede hacer que todos los objetos sean iterable asignando a 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 }; };

Puedes jugar con eso aquí (en Chrome, en arrendamiento): http://jsfiddle.net/rncr3ppz/5/

Editar

Y en respuesta a su pregunta actualizada, sí, es posible convertir un iterable en una matriz, utilizando el operador de propagación en ES6.

Sin embargo, esto todavía no parece funcionar en Chrome, o al menos no puedo hacerlo funcionar en mi jsFiddle. En teoría debería ser tan simple como:

var array = [...myIterable];


Object no implementan los protocolos de iteración en Javascript por muy buenas razones. Hay dos niveles en los que las propiedades de los objetos se pueden iterar en JavaScript:

  • el nivel del programa
  • el nivel de datos

Programa de iteración de nivel

Cuando itera sobre un objeto en el nivel del programa, examina una parte de la estructura de su programa. Es una operación reflexiva. Vamos a ilustrar esta declaración con un tipo de matriz, que generalmente se repite en el nivel de datos:

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 el nivel del programa de xs . Dado que las matrices almacenan secuencias de datos, regularmente solo estamos interesados ​​en el nivel de datos. for..in evidentemente no tiene sentido en relación con matrices y otras estructuras "orientadas a datos" en la mayoría de los casos. Esa es la razón por la cual ES2015 ha introducido para for..of y el protocolo iterable.

Iteración del nivel de datos

¿Significa eso que simplemente podemos distinguir los datos del nivel del programa distinguiendo las funciones de los tipos primitivos? No, porque las funciones también pueden ser datos en Javascript:

  • Array.prototype.sort por ejemplo, espera que una función realice un cierto algoritmo de ordenación
  • Thunks como () => 1 + 2 son solo envoltorios funcionales para valores vagamente evaluados

Además, los valores primitivos también pueden representar el nivel del programa:

  • [].length por ejemplo, es un Number pero representa la longitud de una matriz y, por lo tanto, pertenece al dominio del programa

Eso significa que no podemos distinguir el programa y el nivel de datos simplemente comprobando los tipos.

Es importante comprender que la implementación de los protocolos de iteración para objetos Javascript antiguos simples dependería del nivel de datos. Pero como acabamos de ver, no es posible una distinción confiable entre los datos y la iteración a nivel de programa.

Con Array s esta distinción es trivial: cada elemento con una clave de tipo entero es un elemento de datos. Object tienen una característica equivalente: el descriptor enumerable . Pero, ¿es realmente recomendable confiar en esto? ¡Creo que no lo es! El significado del descriptor enumerable es demasiado borroso.

Conclusión

No hay una manera significativa de implementar los protocolos de iteración para los objetos, porque no todos los objetos son una colección.

Si las propiedades del objeto eran iterables de manera predeterminada, el nivel de datos y el programa estaban mezclados. Dado que cada tipo compuesto en Javascript se basa en objetos simples, esto también se aplicaría a Array y Map .

for..in , for..in , Object.keys , etc. se pueden usar tanto para la reflexión como para la iteración de datos, una distinción clara no es posible. Si no tienes cuidado, terminas rápidamente con meta programación y dependencias extrañas. El tipo de datos abstractos de Map termina efectivamente la combinación de programa y nivel de datos. Creo que Map es el logro más significativo en ES2015, incluso si Promise s es mucho más emocionante.