Pourquoi les objets ne sont-ils pas itérables par défaut?
Je vois tout le temps des questions liées à l'itération d'objets, la solution courante étant de parcourir les propriétés d'un objet et d'accéder aux valeurs d'un objet de cette façon. Cela semble si courant que je me demande pourquoi les objets eux-mêmes ne sont pas itérables.
Des déclarations comme l'ES6 for...of
seraient bien utilisées pour les objets par défaut. Parce que ces fonctionnalités ne sont disponibles que pour les "objets itérables" spéciaux qui n'incluent pas d' {}
objets, nous devons passer par des cercles pour que cela fonctionne pour les objets pour lesquels nous voulons l'utiliser.
L'instruction for ... of crée une boucle itérant sur des objets itérables (y compris Array, Map, Set, arguments object et ainsi de suite) ...
Par exemple en utilisant une fonction de générateur 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);
}
}
Ce qui précède enregistre correctement les données dans l'ordre dans lequel je m'attends lorsque j'exécute le code dans Firefox (qui prend en charge ES6 ):
Par défaut, les {}
objets ne sont pas itérables, mais pourquoi? Les inconvénients l'emporteraient-ils sur les avantages potentiels des objets itérables? Quels sont les problèmes associés à cela?
De plus, parce que les {}
objets sont différents des « Array comme » collections « et des objets itérables » tels que NodeList
, HtmlCollection
et arguments
, ils ne peuvent pas être convertis en tableaux.
Par exemple:
var argumentsArray = Array.prototype.slice.call(arguments);
ou être utilisé avec les méthodes Array:
Array.prototype.forEach.call(nodeList, function (element) {})
.
Outre les questions que j'ai ci-dessus, j'aimerais voir un exemple de travail sur la façon de transformer des {}
objets en itérables, en particulier de ceux qui ont mentionné le [Symbol.iterator]
. Cela devrait permettre à ces nouveaux {}
"objets itérables" d'utiliser des instructions comme for...of
. Aussi, je me demande si rendre les objets itérables permet de les convertir en tableaux.
J'ai essayé le code ci-dessous, mais j'obtiens 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
Symbol.iterator
propriété est un itérable. Il vous suffit donc de mettre en œuvre cette propriété. Une explication possible de la raison pour laquelle les objets ne sont pas itérables pourrait être que cela impliquerait que tout était itérable, puisque tout est un objet (sauf les primitifs bien sûr). Cependant, que signifie itérer sur une fonction ou un objet d'expression régulière?Réponses:
Je vais essayer. Notez que je ne suis pas affilié à l'ECMA et que je n'ai aucune visibilité sur leur processus de prise de décision, je ne peux donc pas dire avec certitude pourquoi ils ont ou n'ont rien fait. Cependant, je vais exposer mes hypothèses et faire de mon mieux.
1. Pourquoi ajouter une
for...of
construction en premier lieu?JavaScript inclut déjà une
for...in
construction qui peut être utilisée pour itérer les propriétés d'un objet. Cependant, ce n'est pas vraiment une boucle forEach , car elle énumère toutes les propriétés d'un objet et a tendance à ne fonctionner de manière prévisible que dans des cas simples.Il se décompose dans des cas plus complexes (y compris avec des baies, où son utilisation a tendance à être soit découragée, soit complètement obscurcie par les garanties nécessaires pour une utilisation
for...in
avec une baie correcte ). Vous pouvez contourner cela en utilisanthasOwnProperty
(entre autres choses), mais c'est un peu maladroit et inélégant.Donc, mon hypothèse est que le
for...of
concept est ajouté pour combler les lacunes associées aufor...in
concept et pour offrir une plus grande utilité et flexibilité lors de l'itération des choses. Les gens ont tendance à traiterfor...in
comme unforEach
boucle qui peut généralement être appliquée à n'importe quelle collection et produire des résultats sains dans n'importe quel contexte possible, mais ce n'est pas ce qui se passe. Lafor...of
boucle corrige cela.Je suppose également qu'il est important pour le code ES5 existant de fonctionner sous ES6 et de produire le même résultat que sous ES5, de sorte que des modifications radicales ne peuvent pas être apportées, par exemple, au comportement de la
for...in
construction.2. Comment ça
for...of
marche?La documentation de référence est utile pour cette partie. Plus précisément, un objet est considéré
iterable
s'il définit laSymbol.iterator
propriété.La définition de propriété doit être une fonction qui renvoie les éléments de la collection, un, par, un, et définit un indicateur indiquant s'il y a ou non plus d'éléments à récupérer. Des implémentations prédéfinies sont fournies pour certains types d'objets , et il est relativement clair que l'utilisation de
for...of
délégués simplement à la fonction d'itérateur.Cette approche est utile, car elle permet de fournir très simplement vos propres itérateurs. Je pourrais dire que l'approche aurait pu présenter des problèmes pratiques en raison de sa dépendance à la définition d'une propriété là où il n'y en avait pas auparavant, sauf de ce que je peux dire, ce n'est pas le cas car la nouvelle propriété est essentiellement ignorée à moins que vous ne la cherchiez délibérément (c.-à-d. il ne se présentera pas dans les
for...in
boucles comme clé, etc.) Ce n'est donc pas le cas.Mis à part les problèmes pratiques, il peut avoir été considéré comme controversé sur le plan conceptuel de commencer tous les objets avec une nouvelle propriété prédéfinie, ou de dire implicitement que «chaque objet est une collection».
3. Pourquoi les objets ne sont-ils pas
iterable
utilisésfor...of
par défaut?Je suppose que c'est une combinaison de:
iterable
par défaut peut avoir été considérée comme inacceptable car elle ajoute une propriété là où il n'y en avait pas auparavant, ou parce qu'un objet n'est pas (nécessairement) une collection. Comme le note Felix, "que signifie itérer sur une fonction ou un objet d'expression régulière"?for...in
, et on ne sait pas ce qu'une implémentation d'itérateur intégré aurait pu faire différemment / mieux que lefor...in
comportement existant . Donc, même si # 1 est faux et que l'ajout de la propriété était acceptable, cela n'a peut-être pas été considéré comme utile .iterable
peuvent le faire facilement, en définissant laSymbol.iterator
propriété.iterable
par défaut et présente d'autres petits avantages par rapport à l'utilisation d'un objet simple commeMap
.Il y a même un exemple fourni pour # 3 dans la documentation de référence:
var myIterable = {}; myIterable[Symbol.iterator] = function* () { yield 1; yield 2; yield 3; }; for (var value of myIterable) { console.log(value); }
Étant donné que les objets peuvent être facilement créés
iterable
, qu'ils peuvent déjà être itérés en utilisantfor...in
, et qu'il n'y a probablement pas d'accord clair sur ce qu'un itérateur d'objet par défaut devrait faire (si ce qu'il fait est censé être quelque peu différent de ce qu'ilfor...in
fait), cela semble raisonnable assez pour que les objets ne soient pas créésiterable
par défaut.Notez que votre exemple de code peut être réécrit en utilisant
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 vous pouvez également créer votre objet
iterable
comme vous le souhaitez en faisant quelque chose comme ce qui suit (ou vous pouvez créer tous les objetsiterable
en attribuant à laObject.prototype[Symbol.iterator]
place):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 }; };
Vous pouvez jouer avec ça ici (dans Chrome, au bail): http://jsfiddle.net/rncr3ppz/5/
Éditer
Et en réponse à votre question mise à jour, oui, il est possible de convertir un
iterable
en tableau, en utilisant l' opérateur de propagation dans ES6.Cependant, cela ne semble pas encore fonctionner dans Chrome, ou du moins je ne peux pas le faire fonctionner dans mon jsFiddle. En théorie, cela devrait être aussi simple que:
var array = [...myIterable];
la source
obj[Symbol.iterator] = obj[Symbol.enumerate]
dans votre dernier exemple?[[enumerate]]
n'est pas un symbole bien connu (@@ enumerate) mais une méthode interne. Je devrais êtreobj[Symbol.iterator] = function(){ return Reflect.enumerate(this) }
for...in
, qui a un objectif différent: parcourir les propriétés d'un objet.Map
pour le moment.Object
s n'implémentent pas les protocoles d'itération en Javascript pour de très bonnes raisons. Il existe deux niveaux auxquels les propriétés des objets peuvent être itérées dans JavaScript:Itération au niveau du programme
Lorsque vous parcourez un objet au niveau du programme, vous examinez une partie de la structure de votre programme. C'est une opération réfléchissante. Illustrons cette déclaration avec un type de tableau, qui est généralement itéré au niveau des données:
const xs = [1,2,3]; xs.f = function f() {}; for (let i in xs) console.log(xs[i]); // logs `f` as well
Nous venons d'examiner le niveau de programme de
xs
. Puisque les tableaux stockent des séquences de données, nous nous intéressons régulièrement uniquement au niveau des données.for..in
n'a évidemment aucun sens en relation avec les tableaux et autres structures «orientées données» dans la plupart des cas. C'est la raison pour laquelle ES2015 a introduitfor..of
et le protocole itérable.Itération du niveau de données
Cela signifie-t-il que nous pouvons simplement distinguer les données du niveau programme en distinguant les fonctions des types primitifs? Non, car les fonctions peuvent aussi être des données en Javascript:
Array.prototype.sort
par exemple s'attend à ce qu'une fonction exécute un certain algorithme de tri() => 1 + 2
sont que des wrappers fonctionnels pour des valeurs évaluées paresseusementOutre les valeurs primitives peuvent également représenter le niveau du programme:
[].length
par exemple est aNumber
mais représente la longueur d'un tableau et appartient donc au domaine du programmeCela signifie que nous ne pouvons pas distinguer le programme et le niveau de données en vérifiant simplement les types.
Il est important de comprendre que la mise en œuvre des protocoles d'itération pour les anciens objets Javascript simples reposerait sur le niveau de données. Mais comme nous venons de le voir, une distinction fiable entre les données et l'itération au niveau du programme n'est pas possible.
Avec
Array
s, cette distinction est triviale: chaque élément avec une clé de type entier est un élément de données.Object
s ont une fonctionnalité équivalente: leenumerable
descripteur. Mais est-il vraiment conseillé de s'y fier? Je crois que non! La signification duenumerable
descripteur est trop floue.Conclusion
Il n'existe aucun moyen significatif d'implémenter les protocoles d'itération pour les objets, car tous les objets ne sont pas une collection.
Si les propriétés de l'objet étaient itérables par défaut, le programme et le niveau de données étaient mélangés. Étant donné que chaque type composite en Javascript est basé sur des objets simples , cela s'appliquerait pour
Array
etMap
aussi bien.for..in
,Object.keys
,Reflect.ownKeys
Etc. peuvent être utilisés à la fois itération de réflexion et des données, une distinction claire est régulièrement pas possible. Si vous ne faites pas attention, vous vous retrouvez rapidement avec une métaprogrammation et des dépendances étranges. LeMap
type de données abstrait met effectivement fin à la fusion du programme et du niveau de données. Je crois queMap
c'est la réalisation la plus significative de l'ES2015, même si lesPromise
s sont beaucoup plus excitantes.la source
Je suppose que la question devrait être «pourquoi n'y a-t-il pas d' itération d'objet intégrée ?
L'ajout d'itérabilité aux objets eux-mêmes pourrait éventuellement avoir des conséquences inattendues, et non, il n'y a aucun moyen de garantir l'ordre, mais écrire un itérateur est aussi simple que
function* iterate_object(o) { var keys = Object.keys(o); for (var i=0; i<keys.length; i++) { yield [keys[i], o[keys[i]]]; } }
ensuite
for (var [key, val] of iterate_object({a: 1, b: 2})) { console.log(key, val); } a 1 b 2
la source
[Symbol.iterator]
ainsi que si vous pouviez développer ces conséquences involontaires.Vous pouvez facilement rendre tous les objets itérables globalement:
Object.defineProperty(Object.prototype, Symbol.iterator, { enumerable: false, value: function * (){ for(let key in this){ if(this.hasOwnProperty(key)){ yield [key, this[key]]; } } } });
la source
Il s'agit de la dernière approche (qui fonctionne dans Chrome Canary)
var files = { '/root': {type: 'directory'}, '/root/example.txt': {type: 'file'} }; for (let [key, {type}] of Object.entries(files)) { console.log(type); }
Yes
entries
est maintenant une méthode qui fait partie d'Object :)Éditer
Après avoir examiné plus en détail, il semble que vous pourriez faire ce qui suit
Object.prototype[Symbol.iterator] = function * () { for (const [key, value] of Object.entries(this)) { yield {key, value}; // or [key, value] } };
donc tu peux maintenant faire ça
for (const {key, value:{type}} of files) { console.log(key, type); }
edit2
Revenez à votre exemple d'origine, si vous vouliez utiliser la méthode de prototype ci-dessus, cela devrait
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); } }
la source
J'ai également été dérangé par cette question.
Puis j'ai eu une idée d'utilisation
Object.entries({...})
, ça renvoie unArray
qui est unIterable
.En outre, le Dr Axel Rauschmayer a publié une excellente réponse à ce sujet. Voir Pourquoi les objets simples ne sont PAS itérables
la source
Techniquement, ce n'est pas une réponse à la question pourquoi? mais j'ai adapté la réponse de Jack Slocum ci-dessus à la lumière des commentaires de BT à quelque chose qui peut être utilisé pour rendre un objet itérable.
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);
Pas aussi pratique qu'il aurait dû l'être, mais c'est réalisable, surtout si vous avez plusieurs objets:
var instruments={ 'a': 'accordion', 'b': 'banjo', 'c': 'cor anglais' }; Object.defineProperty(instruments,Symbol.iterator,iterableProperties); for(let v of instruments) console.log(v);
Et, parce que chacun a droit à une opinion, je ne vois pas pourquoi les objets ne sont pas déjà itérables non plus. Si vous pouvez les polyfill comme ci-dessus, ou utilisez
for … in
je ne vois pas d'argument simple.Une suggestion possible est que ce qui est itérable est un type d'objet, il est donc possible qu'il ait été limité à un sous-ensemble d'objets juste au cas où d'autres objets exploseraient lors de la tentative.
la source