Anmerkung der letzten Zeit: Obwohl ich geschmeichelt bin, dass diese Antwort viele positive Stimmen erhalten hat, bin ich auch etwas entsetzt. Wenn man Punktnotationszeichenfolgen wie "xabc" in Referenzen konvertieren muss, könnte dies (vielleicht) ein Zeichen dafür sein, dass etwas sehr falsch ist (es sei denn, Sie führen eine seltsame Deserialisierung durch).
Das heißt, Anfänger, die den Weg zu dieser Antwort finden, müssen sich die Frage stellen: "Warum mache ich das?"
Es ist natürlich im Allgemeinen in Ordnung, dies zu tun, wenn Ihr Anwendungsfall klein ist und Sie nicht auf Leistungsprobleme stoßen, UND Sie müssen nicht auf Ihrer Abstraktion aufbauen, um sie später komplizierter zu machen. Wenn dies die Komplexität des Codes verringert und die Dinge einfach hält, sollten Sie wahrscheinlich das tun, was OP verlangt. Wenn dies jedoch nicht der Fall ist, prüfen Sie, ob eine der folgenden Bedingungen erfüllt ist:
Fall 1 : Als primäre Methode zum Arbeiten mit Ihren Daten (z. B. als Standardform Ihrer App zum Weitergeben und Dereferenzieren von Objekten). Zum Beispiel die Frage "Wie kann ich einen Funktions- oder Variablennamen aus einer Zeichenfolge nachschlagen?".
- Dies ist eine schlechte Programmierpraxis (unnötige Metaprogrammierung speziell und verstößt gegen den funktionsnebenwirkungsfreien Codierungsstil und führt zu Leistungseinbußen). Anfänger, die sich in diesem Fall befinden, sollten stattdessen in Betracht ziehen, mit Array-Darstellungen zu arbeiten, z. B. ['x', 'a', 'b', 'c'] oder wenn möglich sogar direkter / einfacher / unkomplizierter: wie nicht zu verlieren Verfolgen Sie in erster Linie die Referenzen selbst (am besten, wenn sie nur clientseitig oder nur serverseitig sind) usw. (Eine bereits vorhandene eindeutige ID wäre unelegant zum Hinzufügen, könnte jedoch verwendet werden, wenn die Spezifikation dies anderweitig erfordert Existenz unabhängig.)
Fall 2 : Arbeiten mit serialisierten Daten oder Daten, die dem Benutzer angezeigt werden. Verwenden Sie beispielsweise ein Datum als Zeichenfolge "1999-12-30" anstelle eines Datumsobjekts (was zu Zeitzonenfehlern führen oder die Komplexität der Serialisierung erhöhen kann, wenn Sie nicht vorsichtig sind). Oder Sie wissen, was Sie tun.
- Das ist vielleicht in Ordnung. Achten Sie darauf, dass keine Punktzeichenfolgen "" vorhanden sind. in Ihren bereinigten Eingabefragmenten.
Wenn Sie diese Antwort ständig verwenden und zwischen Zeichenfolge und Array hin und her konvertieren, sind Sie möglicherweise im schlechten Fall und sollten eine Alternative in Betracht ziehen.
Hier ist ein eleganter Einzeiler, der 10x kürzer ist als die anderen Lösungen:
function index(obj,i) {return obj[i]}
'a.b.etc'.split('.').reduce(index, obj)
[Bearbeiten] Oder in ECMAScript 6:
'a.b.etc'.split('.').reduce((o,i)=>o[i], obj)
(Nicht, dass ich denke, dass eval immer schlecht ist, wie andere vermuten, dass es ist (obwohl es normalerweise so ist), dennoch werden sich diese Leute freuen, dass diese Methode eval nicht verwendet. Die oben genannten finden obj.a.b.etcgegeben objund die Zeichenfolge "a.b.etc".)
Als Antwort auf diejenigen, die immer noch Angst vor der Verwendung haben, reduceobwohl sie im ECMA-262-Standard (5. Ausgabe) enthalten sind, gibt es hier eine zweizeilige rekursive Implementierung:
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')
Abhängig von den Optimierungen, die der JS-Compiler vornimmt, möchten Sie möglicherweise sicherstellen, dass verschachtelte Funktionen nicht bei jedem Aufruf über die üblichen Methoden neu definiert werden (Platzieren in einem Abschluss, Objekt oder globalen Namespace).
bearbeiten :
Um eine interessante Frage in den Kommentaren zu beantworten:
Wie würden Sie daraus auch einen Setter machen? Die Werte nicht nur nach Pfad zurückgeben, sondern auch festlegen, wenn ein neuer Wert an die Funktion gesendet wird? - Swader 28. Juni um 21:42 Uhr
(Nebenbemerkung: Leider kann ein Objekt nicht mit einem Setter zurückgegeben werden, da dies gegen die aufrufende Konvention verstoßen würde. Der Kommentator scheint sich stattdessen auf eine allgemeine Setter-Funktion mit Nebenwirkungen wie " index(obj,"a.b.etc", value)Doing" zu beziehen obj.a.b.etc = value.)
Der reduceStil ist dafür nicht wirklich geeignet, aber wir können die rekursive Implementierung ändern:
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);
}
Demo:
> 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
... obwohl ich persönlich empfehlen würde, eine separate Funktion zu erstellen setIndex(...). Ich möchte mit einer Randnotiz enden, dass der ursprüngliche Poser der Frage eher mit Arrays von Indizes (von denen sie stammen können .split) als mit Strings arbeiten könnte (sollte?) ; obwohl an einer Komfortfunktion normalerweise nichts auszusetzen ist.
Ein Kommentator fragte:
Was ist mit Arrays? so etwas wie "ab [4] .cd [1] [2] [3]"? –AlexS
Javascript ist eine sehr seltsame Sprache; Im Allgemeinen können Objekte nur Zeichenfolgen als Eigenschaftsschlüssel haben. Wenn xes sich beispielsweise um ein generisches Objekt handelt x={}, wird x[1]dies zu x["1"]... Sie lesen das richtig ... yup ...
Javascript-Arrays (die selbst Instanzen von Object sind) fördern speziell Ganzzahlschlüssel, obwohl Sie so etwas tun könnten x=[]; x["puppy"]=5;.
Aber im Allgemeinen (und es gibt Ausnahmen) x["somestring"]===x.somestring(wenn es erlaubt ist; können Sie nicht x.123).
(Denken Sie daran, dass jeder JS-Compiler, den Sie verwenden, diese möglicherweise zu vernünftigeren Darstellungen kompilieren kann, wenn dies beweisen kann, dass sie nicht gegen die Spezifikation verstoßen.)
Die Antwort auf Ihre Frage hängt also davon ab, ob Sie davon ausgehen, dass diese Objekte nur Ganzzahlen akzeptieren (aufgrund einer Einschränkung in Ihrer Problemdomäne) oder nicht. Nehmen wir nicht an. Dann ist ein gültiger Ausdruck eine Verkettung einer Basiskennung plus einiger .identifiers plus einiger ["stringindex"]s
Dies wäre dann gleichbedeutend mit a["b"][4]["c"]["d"][1][2][3], obwohl wir wahrscheinlich auch unterstützen sollten a.b["c\"validjsstringliteral"][3]. Sie müssten den Abschnitt zur Ecmascript-Grammatik für Zeichenfolgenliterale überprüfen, um zu sehen, wie ein gültiges Zeichenfolgenliteral analysiert wird. Technisch gesehen möchten Sie auch (anders als in meiner ersten Antwort) überprüfen, ob aes sich um eine gültige Javascript-Kennung handelt .
Eine einfache Antwort auf Ihre Frage aber, wenn die Saiten Kommas oder Klammern nicht enthalten , würde sein gerade Länge 1+ Sequenzen von Zeichen in der Menge nicht übereinstimmen ,oder [oder ]:
> "abc[4].c.def[1][2][\"gh\"]".match(/[^\]\[.]+/g)
// ^^^ ^ ^ ^^^ ^ ^ ^^^^^
["abc", "4", "c", "def", "1", "2", ""gh""]
Wenn Ihre Zeichenfolgen keine Escape-Zeichen oder "Zeichen enthalten und IdentifierNames eine Subsprache von StringLiterals sind (glaube ich ???), können Sie Ihre Punkte zuerst in [] konvertieren:
> 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"]
Seien Sie natürlich immer vorsichtig und vertrauen Sie niemals Ihren Daten. Einige schlechte Methoden, die für einige Anwendungsfälle funktionieren könnten, umfassen auch:
// 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
Special 2018 bearbeiten:
Lassen Sie uns den Kreis schließen und die ineffizienteste, schrecklich übermetaprogrammierte Lösung finden, die wir finden können ... im Interesse der Hamfisterie mit syntaktischer Reinheit . Mit ES6-Proxy-Objekten! ... Definieren wir auch einige Eigenschaften, die (imho sind in Ordnung und wunderbar, aber) möglicherweise falsch geschriebene Bibliotheken beschädigen. Sie sollten vielleicht vorsichtig sein, wenn Sie sich um Leistung, geistige Gesundheit (Ihre oder die anderer), Ihren Job usw. kümmern.
// [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);
}
Demo:
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']);
Ausgabe:
obj ist: {"a": {"b": {"c": 1, "d": 2}}}
(Proxy Override get) objHyper ['abc'] ist: 1
(Proxy-Override-Set) objHyper ['abc'] = 3, jetzt ist obj: {"a": {"b": {"c": 3, "d": 2}}}
(hinter den Kulissen) objHyper ist: Proxy {a: {…}}
(Abkürzung) obj.H ['abc'] = 4
(Abkürzung) obj.H ['abc'] ist obj ['a'] ['b'] ['c'] ist: 4
ineffiziente Idee: Sie können das oben Gesagte basierend auf dem Eingabeargument so ändern, dass es versendet. Verwenden Sie entweder die .match(/[^\]\[.]+/g)Methode zur Unterstützung obj['keys'].like[3]['this']oder wenn instanceof Array, dann akzeptieren Sie einfach ein Array als Eingabe wie keys = ['a','b','c']; obj.H[keys].
Per Vorschlag, dass Sie möglicherweise undefinierte Indizes in einem "weicheren" NaN-Stil behandeln möchten (z. B. index({a:{b:{c:...}}}, 'a.x.c')undefinierte anstelle von nicht erfassten TypeError zurückgeben) ...:
1) Dies ist unter dem Gesichtspunkt "Wir sollten undefiniert zurückgeben, anstatt einen Fehler zu werfen" in der eindimensionalen Indexsituation ({}) ['eg'] == undefiniert sinnvoll. "Wir sollten also undefiniert zurückgeben, anstatt einen Fehler zu werfen." Fehler "in der N-dimensionalen Situation.
2) Dies ist aus der Perspektive, die wir machen , nicht sinnvoll x['a']['x']['c'], was mit einem TypeError im obigen Beispiel fehlschlagen würde.
Das heißt, Sie würden diese Arbeit machen, indem Sie Ihre Reduktionsfunktion durch Folgendes ersetzen:
(o,i)=>o===undefined?undefined:o[i]oder
(o,i)=>(o||{})[i].
(Sie können dies effizienter gestalten, indem Sie eine for-Schleife verwenden und abbrechen / zurückgeben, wenn das Unterergebnis, in das Sie als nächstes indizieren möchten, nicht definiert ist, oder einen Try-Catch verwenden, wenn Sie erwarten, dass solche Fehler ausreichend selten sind.)
evalist böse; benutze es nicht