ostatnia uwaga: choć pochlebia mi, że ta odpowiedź spotkała się z dużym zainteresowaniem, jestem również nieco przerażona. Jeśli trzeba przekonwertować ciągi notacji kropkowej, takie jak „xabc”, na referencje, może to być (być może) znak, że dzieje się coś bardzo złego (chyba że wykonujesz jakąś dziwną deserializację).
Innymi słowy, nowicjusze, którzy znajdują drogę do tej odpowiedzi, muszą zadać sobie pytanie „dlaczego to robię?”.
Oczywiście dobrze jest to zrobić, jeśli twój przypadek użycia jest niewielki i nie napotkasz problemów z wydajnością, I nie będziesz musiał opierać się na swojej abstrakcji, aby później to skomplikować. W rzeczywistości, jeśli zmniejszy to złożoność kodu i uprości sprawy, prawdopodobnie powinieneś zrobić to, o co prosi OP. Jeśli jednak tak nie jest, zastanów się, czy któreś z nich ma zastosowanie:
przypadek 1 : Jako podstawowa metoda pracy z danymi (np. jako domyślna forma aplikacji do przekazywania i usuwania obiektów). Jak pytanie „jak mogę wyszukać nazwę funkcji lub zmiennej z ciągu znaków”.
- Jest to zła praktyka programistyczna (szczególnie niepotrzebne metaprogramowanie i rodzaj narusza styl kodowania bez efektów ubocznych i będzie miał negatywny wpływ na wydajność). Nowicjusze, którzy znajdą się w tym przypadku, powinni zamiast tego rozważyć pracę z reprezentacjami tablic, np. ['X', 'a', 'b', 'c'], a nawet coś bardziej bezpośredniego / prostego / prostego, jeśli to możliwe: na przykład nie tracić śledzenie samych referencji w pierwszej kolejności (najbardziej idealne, jeśli jest to tylko po stronie klienta lub tylko po stronie serwera) itp. (Wcześniej istniejący unikalny identyfikator nie byłby dodawany, ale mógłby zostać użyty, jeśli specyfikacja wymaga jego istnienie niezależnie.)
przypadek 2 : Praca z serializowanymi danymi lub danymi, które będą wyświetlane użytkownikowi. Na przykład użycie daty jako ciągu „1999-12-30” zamiast obiektu Date (co może powodować błędy strefy czasowej lub zwiększać złożoność serializacji, jeśli nie jest to ostrożne). Lub wiesz, co robisz.
- To może być w porządku. Uważaj, aby nie było ciągów kropek „”. w odkażonych fragmentach wejściowych.
Jeśli cały czas używasz tej odpowiedzi i konwertujesz w przód i w tył między łańcuchem a tablicą, możesz mieć zły przypadek i powinieneś rozważyć alternatywę.
Oto elegancki jednowarstwowy, który jest 10 razy krótszy niż inne rozwiązania:
function index(obj,i) {return obj[i]}
'a.b.etc'.split('.').reduce(index, obj)
[edytuj] Lub w ECMAScript 6:
'a.b.etc'.split('.').reduce((o,i)=>o[i], obj)
(Nie to, że myślę, że eval zawsze jest zły, jak sugerują inni (choć zwykle tak jest), ale ci ludzie będą zadowoleni, że ta metoda nie używa eval. Powyższe znajdzie obj.a.b.etcdane obji ciąg "a.b.etc".)
W odpowiedzi na tych, którzy nadal boją się używać, reducemimo że są w standardzie ECMA-262 (wydanie piąte), oto dwutorowa rekurencyjna implementacja:
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')
W zależności od optymalizacji wykonywanych przez kompilator JS, możesz chcieć upewnić się, że żadne zagnieżdżone funkcje nie zostaną ponownie zdefiniowane przy każdym wywołaniu zwykłymi metodami (umieszczając je w zamknięciu, obiekcie lub globalnej przestrzeni nazw).
edycja :
Aby odpowiedzieć na interesujące pytanie w komentarzach:
jak zmieniłbyś to również w setera? Nie tylko zwraca wartości według ścieżki, ale także ustawia je, jeśli nowa funkcja zostanie wysłana do funkcji? - Swader 28 czerwca o 21:42
(sidenote: niestety nie może zwrócić obiektu za pomocą Settera, ponieważ naruszałoby to konwencję wywoływania; komentator wydaje się raczej odwoływać do ogólnej funkcji w stylu Setera z efektami ubocznymi, takimi jak index(obj,"a.b.etc", value)robienie obj.a.b.etc = value.)
reduceStyl jest naprawdę nie nadaje się do tego, ale możemy zmodyfikować rekurencyjną realizacji:
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);
}
Próbny:
> 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
... choć osobiście poleciłbym osobną funkcję setIndex(...). Chciałbym zakończyć na marginesie, że oryginalny poser pytania mógł (powinien?) Pracować z tablicami indeksów (z których mogą uzyskać .split), a nie z łańcuchami; chociaż zwykle nie ma nic złego w funkcji wygody.
Komentator zapytał:
co z tablicami? coś w stylu „ab [4] .cd [1] [2] [3]”? –Aleksy
JavaScript jest bardzo dziwnym językiem; ogólnie rzecz biorąc, obiekty mogą mieć tylko łańcuchy jako klucze właściwości, więc na przykład, jeśli byłby to xobiekt ogólny x={}, to x[1]stałby się x["1"]... czytasz to dobrze ... tak ...
Tablice JavaScript (które same są instancjami Object) szczególnie zachęcają do wprowadzania liczb całkowitych, nawet jeśli można coś takiego zrobić x=[]; x["puppy"]=5;.
Ale ogólnie (i są wyjątki), x["somestring"]===x.somestring(gdy jest to dozwolone; nie można tego zrobić x.123).
(Należy pamiętać, że dowolny kompilator JS, którego używasz, może wybrać kompilację tych danych w celu uzyskania lepszej reprezentacji, jeśli może udowodnić, że nie naruszy specyfikacji).
Tak więc odpowiedź na twoje pytanie zależy od tego, czy zakładasz, że te obiekty akceptują tylko liczby całkowite (z powodu ograniczenia w Twojej problematycznej dziedzinie), czy nie. Załóżmy, że nie. Zatem poprawnym wyrażeniem jest konkatenacja identyfikatora podstawowego plus niektóre .identifiers plus niektóre ["stringindex"]s
Byłoby to wtedy równoważne a["b"][4]["c"]["d"][1][2][3], choć prawdopodobnie powinniśmy również wspierać a.b["c\"validjsstringliteral"][3]. Musisz sprawdzić sekcję gramatyki ekmascript na literałach łańcuchów, aby zobaczyć, jak parsować poprawne literały łańcuchowe. Technicznie chciałbyś również sprawdzić (inaczej niż w mojej pierwszej odpowiedzi), czy ajest to prawidłowy identyfikator javascript .
Prosta odpowiedź na pytanie jednak, czy struny nie zawierają przecinków lub nawiasów , byłoby po prostu dopasować długość 1+ sekwencje znaków nie w zestawie ,lub [lub ]:
> "abc[4].c.def[1][2][\"gh\"]".match(/[^\]\[.]+/g)
// ^^^ ^ ^ ^^^ ^ ^ ^^^^^
["abc", "4", "c", "def", "1", "2", ""gh""]
Jeśli twoje ciągi nie zawierają znaków specjalnych ani "znaków , a ponieważ IdentifierNames są podjęzykiem StringLiterals (myślę, że?), Możesz najpierw przekonwertować swoje kropki na []:
> 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"]
Oczywiście zawsze bądź ostrożny i nigdy nie ufaj swoim danym. Niektóre złe sposoby na zrobienie tego, które mogą działać w niektórych przypadkach użycia, obejmują również:
// 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
Edycja specjalna 2018:
Okrążmy koło i przygotujmy najbardziej nieefektywne, okropnie zaprogramowane rozwiązanie, jakie możemy wymyślić ... w interesie syntaktycznej czystości . Z obiektami ES6 Proxy! ... Zdefiniujmy również niektóre właściwości, które (imho są w porządku i cudowne, ale) mogą uszkodzić niepoprawnie napisane biblioteki. Być może powinieneś być ostrożny z korzystaniem z tego, jeśli zależy Ci na wydajności, zdrowiu psychicznym (swoim lub innych), pracy itp.
// [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);
}
Próbny:
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']);
Wynik:
obj to: {"a": {"b": {"c": 1, "d": 2}}}
(przesłanianie proxy get) objHyper ['abc'] to: 1
(zestaw zastępowania proxy) objHyper ['abc'] = 3, teraz obj to: {"a": {"b": {"c": 3, "d": 2}}}
(za kulisami) objHyper to: Proxy {a: {…}}
(skrót) obj.H ['abc'] = 4
(skrót) obj.H ['abc'] to obj ['a'] ['b'] ['c'] to: 4
nieefektywny pomysł: Możesz zmodyfikować powyższe do wysyłki w oparciu o argument wejściowy; albo użyj tej .match(/[^\]\[.]+/g)metody do obsługi obj['keys'].like[3]['this'], albo jeśli instanceof Array, a następnie po prostu zaakceptuj tablicę jako dane wejściowe, takie jak keys = ['a','b','c']; obj.H[keys].
Zgodnie z sugestią, że być może chcesz obsługiwać niezdefiniowane wskaźniki w „bardziej miękki” sposób w stylu NaN (np. index({a:{b:{c:...}}}, 'a.x.c')Zwracaj niezdefiniowane, a nie wyłapane TypeError) ...:
1) Ma to sens z punktu widzenia „powinniśmy zwrócić niezdefiniowany zamiast rzucać błąd” w sytuacji indeksu jednowymiarowego ({}) [„np.”] == niezdefiniowany, więc „powinniśmy zwrócić niezdefiniowany zamiast rzucać błąd ”w sytuacji N-wymiarowej.
2) Nie ma to sensu z perspektywy, którą robimy x['a']['x']['c'], co zawiódłoby w przypadku błędu TypeError w powyższym przykładzie.
Powiedziałbyś, że sprawisz, że to zadziała, zastępując swoją funkcję redukującą:
(o,i)=>o===undefined?undefined:o[i]Lub
(o,i)=>(o||{})[i].
(Możesz to uczynić bardziej wydajnym, używając pętli for i przerywania / zwracania za każdym razem, gdy wynik, w którym będziesz indeksować dalej, jest niezdefiniowany lub używając try-catch, jeśli spodziewacie się, że takie niepowodzenia będą wystarczająco rzadkie.)
evaljest zły; nie używaj tego