nota reciente: Aunque me siento halagado de que esta respuesta haya recibido muchos votos positivos, también estoy algo horrorizado. Si uno necesita convertir cadenas de notación de puntos como "xabc" en referencias, podría (tal vez) ser una señal de que está sucediendo algo muy malo (a menos que tal vez esté realizando una deserialización extraña).
Es decir, los novatos que encuentran su camino hacia esta respuesta deben hacerse la pregunta "¿por qué estoy haciendo esto?"
Por supuesto, generalmente está bien hacer esto si su caso de uso es pequeño y no se encontrará con problemas de rendimiento, Y no necesitará construir sobre su abstracción para complicarla más tarde. De hecho, si esto reducirá la complejidad del código y simplificará las cosas, probablemente debería seguir adelante y hacer lo que OP está pidiendo. Sin embargo, si ese no es el caso, considere si alguno de estos aplica:
caso 1 : como el método principal para trabajar con sus datos (por ejemplo, como la forma predeterminada de su aplicación de pasar objetos y desreferenciarlos). Como preguntar "¿cómo puedo buscar una función o nombre de variable de una cadena".
- Esta es una mala práctica de programación (metaprogramación innecesaria específicamente, y viola el estilo de codificación de la función sin efectos secundarios, y tendrá resultados de rendimiento). Los novatos que se encuentran en este caso, deberían considerar trabajar con representaciones de matriz, por ejemplo ['x', 'a', 'b', 'c'], o incluso algo más directo / simple / directo si es posible: como no perder seguimiento de las referencias mismas en primer lugar (lo más ideal si es solo del lado del cliente o del lado del servidor), etc. (Una identificación única preexistente sería poco elegante para agregar, pero podría usarse si la especificación de lo contrario requiere su existencia independientemente)
caso 2 : Trabajar con datos serializados o datos que se mostrarán al usuario. Como usar una fecha como una cadena "1999-12-30" en lugar de un objeto Date (que puede causar errores de zona horaria o complejidad de serialización adicional si no se tiene cuidado). O sabes lo que estás haciendo.
- Esto tal vez esté bien. Tenga cuidado de que no haya cadenas de puntos "." en sus fragmentos de entrada desinfectados.
Si te encuentras usando esta respuesta todo el tiempo y convirtiendo hacia adelante y hacia atrás entre una cadena y una matriz, puedes estar en el mal caso y deberías considerar una alternativa.
Aquí hay un elegante one-liner que es 10 veces más corto que las otras soluciones:
function index(obj,i) {return obj[i]}
'a.b.etc'.split('.').reduce(index, obj)
[editar] O en ECMAScript 6:
'a.b.etc'.split('.').reduce((o,i)=>o[i], obj)
(No es que piense que eval siempre es malo como otros sugieren que es (aunque generalmente lo es), sin embargo, esas personas estarán complacidas de que este método no use eval. Lo anterior encontrará obj.a.b.etcdado objy la cadena"a.b.etc" ).
En respuesta a aquellos que todavía tienen miedo de usar a reducepesar de estar en el estándar ECMA-262 (5a edición), aquí hay una implementación recursiva de dos líneas:
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')
Dependiendo de las optimizaciones que esté haciendo el compilador JS, es posible que desee asegurarse de que las funciones anidadas no se redefinan en cada llamada a través de los métodos habituales (colocándolas en un cierre, objeto o espacio de nombres global).
editar :
Para responder una pregunta interesante en los comentarios:
¿Cómo convertirías esto en un setter también? ¿No solo devuelve los valores por ruta, sino que también los configura si se envía un nuevo valor a la función? - Swader 28 de junio a las 21:42
(nota al margen: lamentablemente no puede devolver un objeto con un Setter, ya que eso violaría la convención de llamada; el comentarista parece referirse a una función general de estilo setter con efectos secundarios como index(obj,"a.b.etc", value)hacer obj.a.b.etc = value).
El reduceestilo no es realmente adecuado para eso, pero podemos modificar la implementación recursiva:
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);
}
Manifestación:
> 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
... aunque personalmente recomendaría hacer una función separada setIndex(...). Me gustaría terminar con una nota al margen de que el autor original de la pregunta podría (¿debería?) Estar trabajando con conjuntos de índices (que pueden obtener .split), en lugar de cadenas; aunque generalmente no hay nada malo con una función de conveniencia.
Un comentarista preguntó:
¿Qué pasa con las matrices? algo así como "ab [4] .cd [1] [2] [3]"? –AlexS
Javascript es un lenguaje muy extraño; en general, los objetos solo pueden tener cadenas como sus claves de propiedad, por lo que, por ejemplo, si xfuera un objeto genérico como x={}, entonces x[1]se convertiría en x["1"]... usted lo leyó bien ... sí ...
Las matrices de Javascript (que son en sí mismas instancias de Object) fomentan específicamente las claves enteras, aunque podría hacer algo como x=[]; x["puppy"]=5; .
Pero en general (y hay excepciones), x["somestring"]===x.somestring(cuando está permitido; no se puede hacer x.123).
(Tenga en cuenta que cualquier compilador JS que esté usando podría elegir, tal vez, compilarlos en representaciones más sensatas si puede probar que no violaría la especificación).
Entonces, la respuesta a su pregunta dependerá de si está asumiendo que esos objetos solo aceptan enteros (debido a una restricción en su dominio problemático), o no. Asumamos que no. Entonces, una expresión válida es una concatenación de un identificador base más algunos .identifiers más algunos ["stringindex"]s
Esto sería equivalente a a["b"][4]["c"]["d"][1][2][3], aunque probablemente también deberíamos apoyarlo a.b["c\"validjsstringliteral"][3]. Tendría que consultar la sección de gramática ecmascript en literales de cadena para ver cómo analizar un literal de cadena válido. Técnicamente, también querría verificar (a diferencia de mi primera respuesta) que aes un identificador de JavaScript válido .
Una respuesta simple a su pregunta, sin embargo, si sus cadenas no contienen comas o soportes , sería apenas sea para que coincida con la longitud 1+ secuencias de caracteres no en el conjunto ,o [o ]:
> "abc[4].c.def[1][2][\"gh\"]".match(/[^\]\[.]+/g)
// ^^^ ^ ^ ^^^ ^ ^ ^^^^^
["abc", "4", "c", "def", "1", "2", ""gh""]
Si sus cadenas no contienen caracteres de escape o "caracteres , y debido a que IdentifierNames es un sublenguaje de StringLiterals (creo que ???), primero puede convertir sus puntos a []:
> 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"]
Por supuesto, siempre tenga cuidado y nunca confíe en sus datos. Algunas formas malas de hacer esto que podrían funcionar para algunos casos de uso también incluyen:
// 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
Edición especial 2018:
Hagamos un círculo completo y hagamos la solución más ineficiente y terriblemente sobreprogramada que podamos encontrar ... en interés del servicio de pureza sintáctica . ¡Con objetos Proxy ES6! ... Definamos también algunas propiedades que (aunque son buenas y maravillosas) pueden romper bibliotecas mal escritas. Tal vez debería desconfiar de usar esto si le preocupa el rendimiento, la cordura (la suya o la de los demás), su trabajo, 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);
}
Manifestación:
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']);
Salida:
obj es: {"a": {"b": {"c": 1, "d": 2}}}
(proxy override get) objHyper ['abc'] es: 1
(conjunto de anulación de proxy) objHyper ['abc'] = 3, ahora obj es: {"a": {"b": {"c": 3, "d": 2}}}
(detrás de escena) objHyper es: Proxy {a: {…}}
(acceso directo) obj.H ['abc'] = 4
(acceso directo) obj.H ['abc'] es obj ['a'] ['b'] ['c'] es: 4
idea ineficiente: puede modificar lo anterior para despachar en función del argumento de entrada; use el .match(/[^\]\[.]+/g)método para admitir obj['keys'].like[3]['this'], o si instanceof Array, simplemente acepte una matriz como entrada como keys = ['a','b','c']; obj.H[keys].
Por sugerencia de que tal vez desee manejar índices indefinidos de una manera 'más suave' al estilo NaN (por ejemplo, index({a:{b:{c:...}}}, 'a.x.c')devolver Indefinido en lugar de no detectado TypeError) ...:
1) Esto tiene sentido desde la perspectiva de "deberíamos regresar indefinido en lugar de arrojar un error" en la situación del índice unidimensional ({}) ['eg'] == undefined, entonces "deberíamos regresar indefinido en lugar de arrojar un error "en la situación N-dimensional.
2) Esto no tiene sentido desde la perspectiva que estamos haciendo x['a']['x']['c'], lo que fallaría con un TypeError en el ejemplo anterior.
Dicho esto, haría que esto funcionara reemplazando su función reductora con:
(o,i)=>o===undefined?undefined:o[i]O
(o,i)=>(o||{})[i].
(Puede hacer esto más eficiente usando un bucle for y rompiendo / regresando cada vez que el resultado secundario en el que se indexará a continuación no esté definido, o usando un try-catch si espera que tales fallas sean lo suficientemente raras).
evales malvado; no lo use