nota recente: Embora eu esteja lisonjeado por esta resposta ter recebido muitos votos positivos, também estou um pouco horrorizado. Se for necessário converter seqüências de notação de ponto como "xabc" em referências, pode (talvez) ser um sinal de que algo está errado (a menos que você esteja executando uma desserialização estranha).
Ou seja, os novatos que encontrarem o caminho para essa resposta devem se perguntar "por que estou fazendo isso?"
É claro que geralmente é bom fazer isso se o seu caso de uso for pequeno e você não tiver problemas de desempenho, E você não precisará desenvolver sua abstração para torná-la mais complicada posteriormente. De fato, se isso reduzir a complexidade do código e simplificar as coisas, você provavelmente deve seguir em frente e fazer o que o OP está pedindo. No entanto, se esse não for o caso, considere se alguma delas se aplica:
caso 1 : como o método principal de trabalhar com seus dados (por exemplo, como a forma padrão do seu aplicativo de passar objetos e retirá-los de referência). Como perguntar "como posso procurar uma função ou nome de variável a partir de uma string".
- Essa é uma prática ruim de programação (metaprogramação desnecessária especificamente, e meio que viola o estilo de codificação sem efeito colateral da função e terá resultados de desempenho). Os novatos que se encontram nesse caso devem considerar trabalhar com representações de array, por exemplo, ['x', 'a', 'b', 'c'] ou até algo mais direto / simples / direto, se possível: como não perder rastrear as próprias referências em primeiro lugar (ideal se for apenas do lado do cliente ou do lado do servidor), etc. (um ID exclusivo pré-existente seria deselegante de adicionar, mas poderia ser usado se a especificação exigir existência independentemente.)
caso 2 : trabalhando com dados serializados, ou dados que serão exibidos para o usuário. Como usar uma data como uma string "1999-12-30" em vez de um objeto Date (que pode causar erros de fuso horário ou adicionar complexidade de serialização, se não for cuidadoso). Ou você sabe o que está fazendo.
- Talvez esteja bom. Cuidado para não haver seqüências de pontos "." em seus fragmentos de entrada higienizados.
Se você se encontra usando essa resposta o tempo todo e convertendo entre string e array, pode estar em um caso ruim e deve considerar uma alternativa.
Aqui está uma linha única elegante que é 10x mais curta que as outras soluções:
function index(obj,i) {return obj[i]}
'a.b.etc'.split('.').reduce(index, obj)
[editar] Ou no ECMAScript 6:
'a.b.etc'.split('.').reduce((o,i)=>o[i], obj)
(Não que eu ache o eval sempre ruim, como outros sugerem), mas essas pessoas ficarão satisfeitas com o fato de esse método não usar o eval. O item acima encontrará obj.a.b.etcdados obje a string "a.b.etc".)
Em resposta àqueles que ainda têm medo de usar, reduceapesar de estar no padrão ECMA-262 (5ª edição), aqui está uma implementação recursiva de duas linhas:
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')
Dependendo das otimizações que o compilador JS está executando, convém garantir que as funções aninhadas não sejam redefinidas em todas as chamadas pelos métodos usuais (colocando-as em um fechamento, objeto ou espaço de nome global).
editar :
Para responder a uma pergunta interessante nos comentários:
como você transformaria isso em um setter também? Não apenas retornando os valores por caminho, mas também definindo-os se um novo valor for enviado para a função? - Swader 28/06 às 21:42
(nota de rodapé: infelizmente não é possível retornar um objeto com um setter, pois isso violaria a convenção de chamada; o comentarista parece estar se referindo a uma função geral do estilo setter com efeitos colaterais como index(obj,"a.b.etc", value)fazer obj.a.b.etc = value.)
O reduceestilo não é realmente adequado para isso, mas podemos modificar a implementação 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);
}
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
... embora pessoalmente eu recomendo fazer uma função separada setIndex(...). Gostaria de terminar com uma nota lateral que o poser original da pergunta poderia (deveria?) Estar trabalhando com matrizes de índices (dos quais eles podem obter .split), em vez de seqüências de caracteres; embora geralmente não haja nada de errado com uma função de conveniência.
Um comentarista perguntou:
e matrizes? algo como "ab [4] .cd [1] [2] [3]"? –AlexS
Javascript é uma linguagem muito estranha; em geral, os objetos só podem ter cadeias de caracteres como suas chaves de propriedade, por exemplo, se xfosse um objeto genérico como x={}, então x[1]se tornaria x["1"]... você leu certo ... sim ...
Matrizes Javascript (que são instâncias do Object) especificamente incentivam chaves inteiras, mesmo que você possa fazer algo assim x=[]; x["puppy"]=5;.
Mas em geral (e há exceções), x["somestring"]===x.somestring(quando é permitido; você não pode fazer x.123).
(Lembre-se de que qualquer compilador JS que você estiver usando possa escolher, talvez, compilá-los em representações mais saudáveis, se puder provar que não violaria as especificações.)
Portanto, a resposta para sua pergunta depende se você está assumindo que esses objetos aceitam apenas números inteiros (devido a uma restrição no domínio do problema) ou não. Vamos supor que não. Então uma expressão válida é uma concatenação de um identificador de base mais alguns .identifiers mais alguns ["stringindex"]s
Isso seria equivalente a a["b"][4]["c"]["d"][1][2][3], embora provavelmente devêssemos também apoiar a.b["c\"validjsstringliteral"][3]. Você precisaria verificar a seção de gramática de ecmascript em literais de string para ver como analisar um literal de string válido. Tecnicamente, você também gostaria de verificar (ao contrário da minha primeira resposta) que aé um identificador javascript válido .
Uma resposta simples para sua pergunta, porém, se suas seqüências de caracteres não contiverem vírgulas ou colchetes , seria apenas corresponder ao comprimento 1 ou mais seqüências de caracteres que não estão no conjunto ,ou [ou ]:
> "abc[4].c.def[1][2][\"gh\"]".match(/[^\]\[.]+/g)
// ^^^ ^ ^ ^^^ ^ ^ ^^^^^
["abc", "4", "c", "def", "1", "2", ""gh""]
Se suas strings não contiverem caracteres de escape ou "caracteres , e porque IdentifierNames são uma sub-linguagem de StringLiterals (acho ???), você pode primeiro converter seus pontos em []:
> 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"]
Obviamente, sempre tenha cuidado e nunca confie em seus dados. Algumas maneiras ruins de fazer isso que podem funcionar em alguns casos de uso também incluem:
// 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
Edição especial de 2018:
Vamos para um círculo completo e fazer a solução mais ineficiente, horrivelmente-overmetaprogrammed podemos chegar a ... no interesse da sintática pureza hamfistery. Com os objetos ES6 Proxy! ... Vamos também definir algumas propriedades que (imho são boas e maravilhosas, mas) podem quebrar bibliotecas gravadas incorretamente. Talvez você deva ter cuidado ao usar isso se se preocupa com desempenho, sanidade (sua ou de outras pessoas), seu trabalho, 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);
}
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']);
Resultado:
obj é: {"a": {"b": {"c": 1, "d": 2}}}
(substituição de proxy get) objHyper ['abc'] é: 1
(conjunto de substituições de proxy) objHyper ['abc'] = 3, agora obj é: {"a": {"b": {"c": 3, "d": 2}}}
(nos bastidores) objHyper é: Proxy {a: {…}}
(atalho) obj.H ['abc'] = 4
(atalho) obj.H ['abc'] é obj ['a'] ['b'] ['c'] é: 4
idéia ineficiente: você pode modificar o item acima para enviar com base no argumento de entrada; use o .match(/[^\]\[.]+/g)método para dar suporte obj['keys'].like[3]['this']ou instanceof Array, se aceitar, apenas uma matriz como entrada keys = ['a','b','c']; obj.H[keys].
Por sugestão de que talvez você queira manipular índices indefinidos de uma maneira 'suave' no estilo NaN (por exemplo, index({a:{b:{c:...}}}, 'a.x.c')retornar indefinido em vez de TypeError não detectado) ...:
1) Isso faz sentido da perspectiva de "devemos retornar indefinidos em vez de gerar um erro" na situação do índice unidimensional ({}) ['eg'] == indefinido, portanto "devemos retornar indefinidos em vez de lançar um erro "na situação N-dimensional.
2) Isso não faz sentido da perspectiva que estamos fazendo x['a']['x']['c'], o que falharia com um TypeError no exemplo acima.
Dito isso, você faria esse trabalho substituindo sua função de redução por:
(o,i)=>o===undefined?undefined:o[i], Ou
(o,i)=>(o||{})[i].
(Você pode tornar isso mais eficiente usando um loop for e interrompendo / retornando sempre que o sub-resultado no qual você indexar o próximo for indefinido ou usando um try-catch se você espera que essas falhas sejam suficientemente raras.)
evalé mau; não usá-lo