note récente: Bien que je sois flatté que cette réponse ait suscité de nombreux votes positifs, je suis également quelque peu horrifié. Si l'on a besoin de convertir des chaînes de notation à points comme "xabc" en références, cela pourrait (peut-être) être un signe qu'il se passe quelque chose de très mal (sauf si vous effectuez une désérialisation étrange).
C'est-à-dire que les novices qui trouvent leur chemin vers cette réponse doivent se poser la question "pourquoi est-ce que je fais ça?"
Il est bien sûr généralement bien de le faire si votre cas d'utilisation est petit et que vous ne rencontrerez pas de problèmes de performances, ET vous n'aurez pas besoin de vous appuyer sur votre abstraction pour la rendre plus compliquée plus tard. En fait, si cela réduit la complexité du code et simplifie les choses, vous devriez probablement aller de l'avant et faire ce que demande OP. Cependant, si ce n'est pas le cas, considérez si l'une de ces conditions s'applique:
cas 1 : comme méthode principale de travail avec vos données (par exemple comme forme par défaut de votre application pour passer des objets et les déréférencer). Comme demander "comment puis-je rechercher un nom de fonction ou de variable dans une chaîne".
- Il s'agit d'une mauvaise pratique de programmation (métaprogrammation inutile en particulier, et en quelque sorte enfreint le style de codage sans effet secondaire de la fonction, et aura des résultats de performance). Les novices qui se trouvent dans ce cas, devraient plutôt envisager de travailler avec des représentations de tableaux, par exemple ['x', 'a', 'b', 'c'], ou même quelque chose de plus direct / simple / simple si possible: comme ne pas perdre suivre les références elles-mêmes en premier lieu (idéal si ce n'est que du côté client ou uniquement du côté serveur), etc. (Un identifiant unique préexistant serait inélégant à ajouter, mais pourrait être utilisé si la spécification requiert son existence indépendamment.)
cas 2 : Travailler avec des données sérialisées ou des données qui seront affichées pour l'utilisateur. Comme utiliser une date comme une chaîne "1999-12-30" plutôt qu'un objet Date (ce qui peut entraîner des bogues de fuseau horaire ou ajouter de la complexité de sérialisation si cela n'est pas prudent). Ou vous savez ce que vous faites.
- C'est peut-être bien. Veillez à ce qu'il n'y ait pas de chaîne de points "." dans vos fragments d'entrée aseptisés.
Si vous vous retrouvez à utiliser cette réponse tout le temps et à effectuer des conversions entre chaîne et tableau, vous pouvez être dans le mauvais cas et devriez envisager une alternative.
Voici une élégante doublure 10 fois plus courte que les autres solutions:
function index(obj,i) {return obj[i]}
'a.b.etc'.split('.').reduce(index, obj)
[modifier] Ou dans ECMAScript 6:
'a.b.etc'.split('.').reduce((o,i)=>o[i], obj)
(Ce n'est pas que je pense qu'eval soit toujours mauvais comme d'autres le suggèrent (bien que ce soit généralement le cas), néanmoins ces personnes seront ravies que cette méthode n'utilise pas eval. Ce qui précède trouvera la obj.a.b.etcdonnée objet la chaîne "a.b.etc".)
En réponse à ceux qui ont encore peur d'utiliser reducemalgré le fait qu'il soit dans la norme ECMA-262 (5ème édition), voici une implémentation récursive à deux lignes:
function multiIndex(obj,is) { // obj,['1','2','3'] -> ((obj['1'])['2'])['3']
return is.length ? multiIndex(obj[is[0]],is.slice(1)) : obj
}
function pathIndex(obj,is) { // obj,'1.2.3' -> multiIndex(obj,['1','2','3'])
return multiIndex(obj,is.split('.'))
}
pathIndex('a.b.etc')
En fonction des optimisations effectuées par le compilateur JS, vous pouvez vous assurer que les fonctions imbriquées ne sont pas redéfinies à chaque appel via les méthodes habituelles (en les plaçant dans une fermeture, un objet ou un espace de noms global).
modifier :
Pour répondre à une question intéressante dans les commentaires:
Comment transformeriez-vous cela en setter? Non seulement renvoyer les valeurs par chemin, mais aussi les définir si une nouvelle valeur est envoyée dans la fonction? - Swader 28 juin à 21:42
(Sidenote: malheureusement, ne peut pas retourner un objet avec un Setter, car cela violerait la convention d'appel; le commentateur semble plutôt se référer à une fonction de style setter générale avec des effets secondaires comme le index(obj,"a.b.etc", value)faire obj.a.b.etc = value.)
Le reducestyle n'est pas vraiment adapté à cela, mais on peut modifier l'implémentation récursive:
function index(obj,is, value) {
if (typeof is == 'string')
return index(obj,is.split('.'), value);
else if (is.length==1 && value!==undefined)
return obj[is[0]] = value;
else if (is.length==0)
return obj;
else
return index(obj[is[0]],is.slice(1), value);
}
Démo:
> obj = {a:{b:{etc:5}}}
> index(obj,'a.b.etc')
5
> index(obj,['a','b','etc']) #works with both strings and lists
5
> index(obj,'a.b.etc', 123) #setter-mode - third argument (possibly poor form)
123
> index(obj,'a.b.etc')
123
... mais personnellement, je recommanderais de créer une fonction distincte setIndex(...). Je voudrais terminer sur une note secondaire que le poseur original de la question pourrait (devrait?) Travailler avec des tableaux d'indices (dont ils peuvent obtenir .split), plutôt qu'avec des chaînes; bien qu'il n'y ait généralement rien de mal avec une fonction pratique.
Un intervenant a demandé:
qu'en est-il des tableaux? quelque chose comme "ab [4] .cd [1] [2] [3]"? –AlexS
Javascript est un langage très étrange; en général, les objets ne peuvent avoir que des chaînes comme clés de propriété, par exemple, si xc'était un objet générique comme x={}, alors x[1]deviendrait x["1"]... vous lisez bien ... ouais ...
Les tableaux Javascript (qui sont eux-mêmes des instances d'Object) encouragent spécifiquement les clés entières, même si vous pouvez faire quelque chose comme x=[]; x["puppy"]=5; .
Mais en général (et il y a des exceptions), x["somestring"]===x.somestring(quand c'est autorisé; vous ne pouvez pas le faire x.123).
(Gardez à l'esprit que le compilateur JS que vous utilisez peut choisir, peut-être, de les compiler en représentations plus saines s'il peut prouver qu'il ne violerait pas les spécifications.)
Ainsi, la réponse à votre question dépendra du fait que vous supposez que ces objets acceptent uniquement des entiers (en raison d'une restriction dans votre domaine problématique), ou non. Supposons que non. Alors une expression valide est une concaténation d'un identifiant de base plus quelques .identifiers plus quelques ["stringindex"]s
Ce serait alors équivalent à a["b"][4]["c"]["d"][1][2][3], même si nous devrions probablement également soutenir a.b["c\"validjsstringliteral"][3]. Vous devez vérifier la section de grammaire ecmascript sur les littéraux de chaîne pour voir comment analyser un littéral de chaîne valide. Techniquement, vous voudriez également vérifier (contrairement à ma première réponse) qui aest un identifiant javascript valide .
Cependant, une réponse simple à votre question, si vos chaînes ne contiennent pas de virgules ou de crochets , serait simplement de faire correspondre des séquences de caractères de longueur 1+ et non dans l'ensemble ,ou [ou ]:
> "abc[4].c.def[1][2][\"gh\"]".match(/[^\]\[.]+/g)
// ^^^ ^ ^ ^^^ ^ ^ ^^^^^
["abc", "4", "c", "def", "1", "2", ""gh""]
Si vos chaînes ne contiennent pas de caractères d'échappement ou de "caractères , et parce que IdentifierNames sont un sous-langage de StringLiterals (je pense ???), vous pouvez d'abord convertir vos points en []:
> var R=[], demoString="abc[4].c.def[1][2][\"gh\"]";
> for(var match,matcher=/^([^\.\[]+)|\.([^\.\[]+)|\["([^"]+)"\]|\[(\d+)\]/g;
match=matcher.exec(demoString); ) {
R.push(Array.from(match).slice(1).filter(x=>x!==undefined)[0]);
// extremely bad code because js regexes are weird, don't use this
}
> R
["abc", "4", "c", "def", "1", "2", "gh"]
Bien sûr, soyez toujours prudent et ne faites jamais confiance à vos données. Certaines mauvaises façons de le faire qui pourraient fonctionner pour certains cas d'utilisation incluent également:
// hackish/wrongish; preprocess your string into "a.b.4.c.d.1.2.3", e.g.:
> yourstring.replace(/]/g,"").replace(/\[/g,".").split(".")
"a.b.4.c.d.1.2.3" //use code from before
Édition spéciale 2018:
Faisons le tour complet et faisons la solution la plus inefficace et horriblement surprogrammée que nous pouvons trouver ... dans l'intérêt du hamfistery de pureté syntaxique . Avec les objets proxy ES6! ... Définissons également certaines propriétés qui (à mon humble avis sont belles et merveilleuses mais) peuvent casser des bibliothèques mal écrites. Vous devriez peut-être vous méfier de l'utiliser si vous vous souciez de la performance, de la raison (la vôtre ou celle des autres), de votre travail, etc.
// [1,2,3][-1]==3 (or just use .slice(-1)[0])
if (![1][-1])
Object.defineProperty(Array.prototype, -1, {get() {return this[this.length-1]}}); //credit to caub
// WARNING: THIS XTREME™ RADICAL METHOD IS VERY INEFFICIENT,
// ESPECIALLY IF INDEXING INTO MULTIPLE OBJECTS,
// because you are constantly creating wrapper objects on-the-fly and,
// even worse, going through Proxy i.e. runtime ~reflection, which prevents
// compiler optimization
// Proxy handler to override obj[*]/obj.* and obj[*]=...
var hyperIndexProxyHandler = {
get: function(obj,key, proxy) {
return key.split('.').reduce((o,i)=>o[i], obj);
},
set: function(obj,key,value, proxy) {
var keys = key.split('.');
var beforeLast = keys.slice(0,-1).reduce((o,i)=>o[i], obj);
beforeLast[keys[-1]] = value;
},
has: function(obj,key) {
//etc
}
};
function hyperIndexOf(target) {
return new Proxy(target, hyperIndexProxyHandler);
}
Démo:
var obj = {a:{b:{c:1, d:2}}};
console.log("obj is:", JSON.stringify(obj));
var objHyper = hyperIndexOf(obj);
console.log("(proxy override get) objHyper['a.b.c'] is:", objHyper['a.b.c']);
objHyper['a.b.c'] = 3;
console.log("(proxy override set) objHyper['a.b.c']=3, now obj is:", JSON.stringify(obj));
console.log("(behind the scenes) objHyper is:", objHyper);
if (!({}).H)
Object.defineProperties(Object.prototype, {
H: {
get: function() {
return hyperIndexOf(this); // TODO:cache as a non-enumerable property for efficiency?
}
}
});
console.log("(shortcut) obj.H['a.b.c']=4");
obj.H['a.b.c'] = 4;
console.log("(shortcut) obj.H['a.b.c'] is obj['a']['b']['c'] is", obj.H['a.b.c']);
Production:
obj est: {"a": {"b": {"c": 1, "d": 2}}}
(proxy override get) objHyper ['abc'] est: 1
(proxy override set) objHyper ['abc'] = 3, maintenant obj est: {"a": {"b": {"c": 3, "d": 2}}}
(dans les coulisses) objHyper est: Proxy {a: {…}}
(raccourci) obj.H ['abc'] = 4
(raccourci) obj.H ['abc'] est obj ['a'] ['b'] ['c'] est: 4
idée inefficace: vous pouvez modifier ce qui précède pour répartir en fonction de l'argument d'entrée; soit utiliser la .match(/[^\]\[.]+/g)méthode pour prendre en charge obj['keys'].like[3]['this'], ou si instanceof Array, alors il suffit d'accepter un tableau comme entrée comme keys = ['a','b','c']; obj.H[keys].
Par suggestion que vous souhaitiez peut-être gérer les indices non définis d'une manière plus douce de style NaN (par exemple, index({a:{b:{c:...}}}, 'a.x.c')renvoyer unError non défini plutôt que non saisi) ...:
1) Cela a du sens du point de vue "nous devrions retourner indéfini plutôt que de jeter une erreur" dans la situation d'index à 1 dimension ({}) ['eg'] == indéfini, donc "nous devons retourner indéfini plutôt que de jeter un erreur "dans la situation à N dimensions.
2) Cela n'a pas de sens du point de vue que nous faisons x['a']['x']['c'], ce qui échouerait avec une TypeError dans l'exemple ci-dessus.
Cela dit, vous feriez cela en remplaçant votre fonction de réduction par:
(o,i)=>o===undefined?undefined:o[i], ou
(o,i)=>(o||{})[i] .
(Vous pouvez rendre cela plus efficace en utilisant une boucle for et en cassant / retournant chaque fois que le sous-résultat dans lequel vous souhaitez indexer la prochaine fois n'est pas défini, ou en utilisant un try-catch si vous vous attendez à ce que de tels échecs soient suffisamment rares.)
evalest mauvais; ne l'utilisez pas