catatan baru-baru ini: Walaupun saya tersanjung bahwa jawaban ini telah mendapatkan banyak upvotes, saya juga agak ngeri. Jika seseorang perlu mengubah string notasi titik seperti "xabc" menjadi referensi, itu bisa (mungkin) menjadi tanda bahwa ada sesuatu yang sangat salah terjadi (kecuali mungkin Anda melakukan deserialisasi aneh).
Dengan kata lain, para pemula yang menemukan jalan mereka untuk jawaban ini harus bertanya pada diri sendiri pertanyaan "mengapa saya melakukan ini?"
Tentu saja umumnya baik-baik saja untuk melakukan ini jika use case Anda kecil dan Anda tidak akan mengalami masalah kinerja, DAN Anda tidak perlu membangun berdasarkan abstraksi Anda untuk membuatnya lebih rumit nanti. Bahkan, jika ini akan mengurangi kompleksitas kode dan menjaga hal-hal sederhana, Anda mungkin harus melanjutkan dan melakukan apa yang diminta OP. Namun, jika bukan itu masalahnya, pertimbangkan apakah ini berlaku:
kasus 1 : Sebagai metode utama untuk bekerja dengan data Anda (misalnya sebagai bentuk default aplikasi Anda untuk melewatkan objek di sekitar dan mendereferensi mereka). Seperti bertanya "bagaimana saya bisa mencari fungsi atau nama variabel dari sebuah string".
- Ini adalah praktik pemrograman yang buruk (metaprogramming yang tidak perlu secara khusus, dan semacam melanggar gaya pengkodean fungsi-efek-bebas, dan akan memiliki hit kinerja). Murid yang menemukan diri mereka dalam kasus ini, sebaliknya harus mempertimbangkan bekerja dengan representasi array, misalnya ['x', 'a', 'b', 'c'], atau bahkan sesuatu yang lebih langsung / sederhana / langsung jika mungkin: seperti tidak kehilangan melacak referensi sendiri di tempat pertama (paling ideal jika hanya sisi-klien atau hanya sisi-server), dll. (ID unik yang sudah ada sebelumnya akan tidak dapat ditambahkan, tetapi dapat digunakan jika spesifikasi sebaliknya mensyaratkan keberadaan terlepas.)
kasus 2 : Bekerja dengan data serial, atau data yang akan ditampilkan kepada pengguna. Seperti menggunakan tanggal sebagai string "1999-12-30" daripada objek Tanggal (yang dapat menyebabkan bug zona waktu atau menambah kompleksitas serialisasi jika tidak hati-hati). Atau Anda tahu apa yang Anda lakukan.
- Ini mungkin baik. Berhati - hatilah karena tidak ada string dot "." dalam fragmen input yang telah disanitasi.
Jika Anda menemukan diri Anda menggunakan jawaban ini sepanjang waktu dan mengubah bolak-balik antara string dan array, Anda mungkin berada dalam kasus yang buruk, dan harus mempertimbangkan alternatif.
Berikut ini adalah one-liner elegan yang 10x lebih pendek dari solusi lain:
function index(obj,i) {return obj[i]}
'a.b.etc'.split('.').reduce(index, obj)
[Sunting] Atau dalam ECMAScript 6:
'a.b.etc'.split('.').reduce((o,i)=>o[i], obj)
(Bukannya saya pikir eval selalu buruk seperti yang disarankan orang lain (meskipun biasanya demikian), namun orang-orang akan senang bahwa metode ini tidak menggunakan eval. Di atas akan ditemukan obj.a.b.etcdiberikan objdan string "a.b.etc".)
Menanggapi mereka yang masih takut menggunakan reducemeskipun berada dalam standar ECMA-262 (edisi ke-5), berikut adalah implementasi rekursif dua-baris:
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')
Bergantung pada optimisasi yang dilakukan oleh kompiler JS, Anda mungkin ingin memastikan bahwa fungsi yang disarangkan tidak didefinisikan ulang pada setiap panggilan melalui metode yang biasa (menempatkannya dalam penutupan, objek, atau namespace global).
edit :
Untuk menjawab pertanyaan yang menarik di komentar:
bagaimana Anda mengubahnya menjadi setter juga? Tidak hanya mengembalikan nilai dengan jalur, tetapi juga mengaturnya jika nilai baru dikirim ke fungsi? - Swader 28 Juni pukul 21:42
(sidenote: sayangnya tidak bisa mengembalikan objek dengan Setter, karena itu akan melanggar konvensi pemanggilan; komentator tampaknya merujuk pada fungsi gaya setter umum dengan efek samping seperti index(obj,"a.b.etc", value)melakukan obj.a.b.etc = value.)
The reduceGaya ini tidak benar-benar cocok untuk itu, tapi kita bisa memodifikasi pelaksanaan rekursif:
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
... meskipun secara pribadi saya akan merekomendasikan membuat fungsi terpisah setIndex(...). Saya ingin mengakhiri dengan catatan tambahan bahwa pelamar asli dari pertanyaan bisa (harus?) Bekerja dengan array indeks (yang dapat mereka peroleh .split), daripada string; meskipun biasanya tidak ada yang salah dengan fungsi kenyamanan.
Seorang komentator bertanya:
bagaimana dengan array? sesuatu seperti "ab [4] .cd [1] [2] [3]"? –AlexS
Javascript adalah bahasa yang sangat aneh; pada umumnya objek hanya dapat memiliki string sebagai kunci properti mereka, jadi misalnya jika xitu adalah objek generik seperti x={}, maka x[1]akan menjadi x["1"]... Anda membacanya dengan benar ... ya ...
Array Javascript (yang merupakan turunan dari Object) secara khusus mendorong kunci integer, meskipun Anda dapat melakukan sesuatu seperti x=[]; x["puppy"]=5;.
Tetapi secara umum (dan ada pengecualian), x["somestring"]===x.somestring(ketika diizinkan; Anda tidak bisa melakukannya x.123).
(Perlu diingat bahwa kompiler JS apa pun yang Anda gunakan dapat memilih, mungkin, untuk mengkompilasi ini ke representasi yang lebih waras jika dapat membuktikannya tidak akan melanggar spesifikasi.)
Jadi jawaban untuk pertanyaan Anda akan tergantung pada apakah Anda mengasumsikan objek tersebut hanya menerima bilangan bulat (karena batasan dalam domain masalah Anda), atau tidak. Mari kita anggap tidak. Kemudian ekspresi yang valid adalah gabungan dari pengidentifikasi basis ditambah beberapa .identifiers ditambah beberapa ["stringindex"]s
Ini kemudian akan setara dengan a["b"][4]["c"]["d"][1][2][3], meskipun kita mungkin juga harus mendukung a.b["c\"validjsstringliteral"][3]. Anda harus memeriksa bagian tata bahasa ecmascript pada string literal untuk melihat cara mengurai string literal yang valid. Secara teknis Anda juga ingin memeriksa (tidak seperti dalam jawaban pertama saya) yang amerupakan pengidentifikasi javascript yang valid .
Namun, jawaban sederhana untuk pertanyaan Anda, jika string Anda tidak mengandung koma atau tanda kurung , akan sama dengan mencocokkan panjang 1+ urutan karakter yang tidak ada di set ,atau [atau ]:
> "abc[4].c.def[1][2][\"gh\"]".match(/[^\]\[.]+/g)
// ^^^ ^ ^ ^^^ ^ ^ ^^^^^
["abc", "4", "c", "def", "1", "2", ""gh""]
Jika string Anda tidak mengandung karakter melarikan diri atau "karakter , dan karena IdentifierNames adalah subbahasa dari StringLiterals (saya pikir ???), Anda dapat terlebih dahulu mengonversi titik Anda ke []:
> 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"]
Tentu saja, selalu berhati-hati dan jangan pernah mempercayai data Anda. Beberapa cara buruk untuk melakukan ini yang mungkin berhasil untuk beberapa kasus penggunaan juga termasuk:
// 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
Sunting khusus 2018:
Mari kita mulai dan melakukan solusi terprogram yang paling tidak efisien, mengerikan, dan terlalu terprogram yang dapat kami buat ... untuk kepentingan pemurnian purtas sintaksis . Dengan objek Proxy ES6! ... Mari kita juga mendefinisikan beberapa properti yang (imho baik-baik saja tetapi luar biasa) dapat memecah pustaka yang ditulis secara tidak benar. Anda mungkin harus waspada menggunakan ini jika Anda peduli dengan kinerja, kewarasan (milik Anda atau orang lain), pekerjaan Anda, dll.
// [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']);
Keluaran:
obj adalah: {"a": {"b": {"c": 1, "d": 2}}}
(proxy override get) objHyper ['abc'] adalah: 1
(proxy override set) objHyper ['abc'] = 3, sekarang obj adalah: {"a": {"b": {"c": 3, "d": 2}}}
(di belakang layar) objHyper adalah: Proxy {a: {...}}
(pintas) obj.H ['abc'] = 4
(pintas) obj.H ['abc'] adalah obj ['a'] ['b'] ['c'] adalah: 4
ide yang tidak efisien: Anda dapat memodifikasi hal di atas untuk dikirim berdasarkan argumen input; baik menggunakan .match(/[^\]\[.]+/g)metode untuk mendukung obj['keys'].like[3]['this'], atau jika instanceof Array, maka hanya menerima Array sebagai input keys = ['a','b','c']; obj.H[keys].
Per saran yang mungkin Anda ingin menangani indeks tidak terdefinisi dengan gaya NaN 'lebih lembut' (mis. index({a:{b:{c:...}}}, 'a.x.c')Mengembalikan TypeError yang tidak terdefinisi daripada yang tidak tertangkap) ...::
1) Ini masuk akal dari perspektif "kita harus mengembalikan undefined daripada melemparkan kesalahan" dalam situasi indeks 1-dimensi ({}) ['eg'] == undefined, jadi "kita harus mengembalikan undefined daripada melemparkan kesalahan "dalam situasi dimensi-N.
2) Hal ini tidak masuk akal dari perspektif yang kita lakukan x['a']['x']['c'], yang akan gagal dengan TypeError dalam contoh di atas.
Yang mengatakan, Anda akan membuat pekerjaan ini dengan mengganti fungsi pengurangan Anda dengan:
(o,i)=>o===undefined?undefined:o[i], atau
(o,i)=>(o||{})[i].
(Anda dapat menjadikan ini lebih efisien dengan menggunakan for for dan break / return setiap kali subres yang akan Anda indeks selanjutnya tidak terdefinisi, atau menggunakan try-catch jika Anda berharap kegagalan seperti itu cukup langka.)
evalitu jahat; jangan gunakan itu