Réponse originale simplifiée
Il existe une nouvelle façon de procéder si cela ne vous dérange pas d'abandonner IE et de prendre en charge principalement les navigateurs plus récents (voir la table es6 de kangax pour la compatibilité). Vous pouvez utiliser es2015 générateurs pour ça. J'ai mis à jour la réponse de @TheHippo en conséquence. Bien sûr, si vous voulez vraiment le support d'IE, vous pouvez utiliser la fonction babel Transpilateur JavaScript.
// Implementation of Traverse
function* traverse(o, path=[]) {
for (var i in o) {
const itemPath = path.concat(i);
yield [i,o[i],itemPath,o];
if (o[i] !== null && typeof(o[i])=="object") {
//going one step down in the object tree!!
yield* traverse(o[i], itemPath);
}
}
}
// Traverse usage:
//that's all... no magic, no bloated framework
for(var [key, value, path, parent] of traverse({
foo:"bar",
arr:[1,2,3],
subo: {
foo2:"bar2"
}
})) {
// do something here with each key and value
console.log(key, value, path, parent);
}
Si vous voulez seulement posséder des propriétés énumérables (en fait, des propriétés de la chaîne de non-prototypes), vous pouvez le changer pour itérer en utilisant Object.keys
et un for...of
à la place :
function* traverse(o,path=[]) {
for (var i of Object.keys(o)) {
const itemPath = path.concat(i);
yield [i,o[i],itemPath,o];
if (o[i] !== null && typeof(o[i])=="object") {
//going one step down in the object tree!!
yield* traverse(o[i],itemPath);
}
}
}
//that's all... no magic, no bloated framework
for(var [key, value, path, parent] of traverse({
foo:"bar",
arr:[1,2,3],
subo: {
foo2:"bar2"
}
})) {
// do something here with each key and value
console.log(key, value, path, parent);
}
EDITAR : Cette réponse éditée résout les traversées en boucle infinies.
Arrêter les trajets d'objets infinis gênants
Cette réponse modifiée offre toujours l'un des avantages supplémentaires de ma réponse originale, qui vous permet d'utiliser le système d'information fourni. fonction de générateur afin d'utiliser une méthode plus propre et plus simple interface itérable (pensez à utiliser for of
boucles comme dans for(var a of b)
donde b
est une itérable et a
est un élément de l'itérable). L'utilisation de la fonction de génération, en plus d'être une API plus simple, contribue également à la réutilisation du code en évitant de devoir répéter la logique d'itération chaque fois que l'on veut itérer profondément sur les propriétés d'un objet. break
hors de la boucle si vous souhaitez arrêter l'itération plus tôt.
J'ai remarqué une chose qui n'a pas été abordée et qui ne figure pas dans ma réponse initiale : vous devez être prudent lorsque vous traversez des objets arbitraires (c'est-à-dire tout ensemble "aléatoire" d'objets), car les objets JavaScript peuvent être auto-référencés. Cela crée la possibilité d'avoir des traversées en boucle infinie. Cependant, les données JSON non modifiées ne peuvent pas être auto-référencées, donc si vous utilisez ce sous-ensemble particulier d'objets JS, vous n'avez pas à vous soucier des traversées en boucle infinies et vous pouvez vous référer à ma réponse originale ou à d'autres réponses. Voici un exemple de traversée sans fin (notez qu'il ne s'agit pas d'un morceau de code exécutable, car sinon il ferait planter l'onglet de votre navigateur).
De même, dans l'objet générateur de mon exemple modifié, j'ai choisi d'utiliser Object.keys
au lieu de for in
qui n'itère que sur les clés non-prototype de l'objet. Vous pouvez changer cela vous-même si vous voulez que les clés prototypes soient incluses. Voir la section de ma réponse originale ci-dessous pour les deux implémentations avec Object.keys
y for in
.
Pire - Cela fera une boucle infinie sur les objets autoréférentiels :
function* traverse(o, path=[]) {
for (var i of Object.keys(o)) {
const itemPath = path.concat(i);
yield [i,o[i],itemPath, o];
if (o[i] !== null && typeof(o[i])=="object") {
//going one step down in the object tree!!
yield* traverse(o[i], itemPath);
}
}
}
//your object
var o = {
foo:"bar",
arr:[1,2,3],
subo: {
foo2:"bar2"
}
};
// this self-referential property assignment is the only real logical difference
// from the above original example which ends up making this naive traversal
// non-terminating (i.e. it makes it infinite loop)
o.o = o;
//that's all... no magic, no bloated framework
for(var [key, value, path, parent] of traverse(o)) {
// do something here with each key and value
console.log(key, value, path, parent);
}
Pour éviter cela, vous pouvez ajouter un ensemble dans une fermeture, de sorte que lorsque la fonction est appelée pour la première fois, elle commence à construire une mémoire des objets qu'elle a vus et ne continue pas l'itération une fois qu'elle rencontre un objet déjà vu. C'est ce que fait l'extrait de code ci-dessous, qui permet de gérer les cas de boucles infinies.
Mieux - Cela ne fera pas de boucle infinie sur les objets autoréférentiels :
function* traverse(o) {
const memory = new Set();
function * innerTraversal (o, path=[]) {
if(memory.has(o)) {
// we've seen this object before don't iterate it
return;
}
// add the new object to our memory.
memory.add(o);
for (var i of Object.keys(o)) {
const itemPath = path.concat(i);
yield [i,o[i],itemPath, o];
if (o[i] !== null && typeof(o[i])=="object") {
//going one step down in the object tree!!
yield* innerTraversal(o[i], itemPath);
}
}
}
yield* innerTraversal(o);
}
//your object
var o = {
foo:"bar",
arr:[1,2,3],
subo: {
foo2:"bar2"
}
};
/// this self-referential property assignment is the only real logical difference
// from the above original example which makes more naive traversals
// non-terminating (i.e. it makes it infinite loop)
o.o = o;
console.log(o);
//that's all... no magic, no bloated framework
for(var [key, value, path, parent] of traverse(o)) {
// do something here with each key and value
console.log(key, value, path, parent);
}
EDITAR : Tous les exemples ci-dessus dans cette réponse ont été modifiés afin d'inclure une nouvelle variable de chemin d'accès obtenue à partir de l'itérateur, comme indiqué ci-dessous. La demande de @supersan . La variable path est un tableau de chaînes de caractères où chaque chaîne du tableau représente chaque clé à laquelle on a accédé pour obtenir la valeur itérée résultante de l'objet source original. La variable path peut être introduite dans la fonction/méthode get de lodash . Ou bien vous pouvez écrire votre propre version de la fonction get de lodash qui ne gère que les tableaux, comme suit :
function get (object, path) {
return path.reduce((obj, pathItem) => obj ? obj[pathItem] : undefined, object);
}
const example = {a: [1,2,3], b: 4, c: { d: ["foo"] }};
// these paths exist on the object
console.log(get(example, ["a", "0"]));
console.log(get(example, ["c", "d", "0"]));
console.log(get(example, ["b"]));
// these paths do not exist on the object
console.log(get(example, ["e", "f", "g"]));
console.log(get(example, ["b", "f", "g"]));
Vous pourriez également créer une fonction de réglage comme suit :
function set (object, path, value) {
const obj = path.slice(0,-1).reduce((obj, pathItem) => obj ? obj[pathItem] : undefined, object)
if(obj && obj[path[path.length - 1]]) {
obj[path[path.length - 1]] = value;
}
return object;
}
const example = {a: [1,2,3], b: 4, c: { d: ["foo"] }};
// these paths exist on the object
console.log(set(example, ["a", "0"], 2));
console.log(set(example, ["c", "d", "0"], "qux"));
console.log(set(example, ["b"], 12));
// these paths do not exist on the object
console.log(set(example, ["e", "f", "g"], false));
console.log(set(example, ["b", "f", "g"], null));
EDIT Sep. 2020 : J'ai ajouté un parent pour un accès plus rapide à l'objet précédent. Cela pourrait vous permettre de construire plus rapidement un traverseur inverse. Vous pouvez aussi toujours modifier l'algorithme de traversée pour faire une recherche en largeur au lieu d'une recherche en profondeur, ce qui est probablement plus prévisible. une version TypeScript avec la recherche en largeur (Breadth First Search) . Comme il s'agit d'une question sur JavaScript, je vais mettre la version JS ici :
var TraverseFilter;
(function (TraverseFilter) {
/** prevents the children from being iterated. */
TraverseFilter["reject"] = "reject";
})(TraverseFilter || (TraverseFilter = {}));
function* traverse(o) {
const memory = new Set();
function* innerTraversal(root) {
const queue = [];
queue.push([root, []]);
while (queue.length > 0) {
const [o, path] = queue.shift();
if (memory.has(o)) {
// we've seen this object before don't iterate it
continue;
}
// add the new object to our memory.
memory.add(o);
for (var i of Object.keys(o)) {
const item = o[i];
const itemPath = path.concat([i]);
const filter = yield [i, item, itemPath, o];
if (filter === TraverseFilter.reject)
continue;
if (item !== null && typeof item === "object") {
//going one step down in the object tree!!
queue.push([item, itemPath]);
}
}
}
}
yield* innerTraversal(o);
}
//your object
var o = {
foo: "bar",
arr: [1, 2, 3],
subo: {
foo2: "bar2"
}
};
/// this self-referential property assignment is the only real logical difference
// from the above original example which makes more naive traversals
// non-terminating (i.e. it makes it infinite loop)
o.o = o;
//that's all... no magic, no bloated framework
for (const [key, value, path, parent] of traverse(o)) {
// do something here with each key and value
console.log(key, value, path, parent);
}