หมายเหตุเมื่อไม่นานมานี้:ในขณะที่ฉันรู้สึกปลื้มใจที่คำตอบนี้ได้รับการโหวตมากมาย แต่ฉันก็ค่อนข้างตกใจ ถ้าใครต้องการแปลงสตริงดอทสัญกรณ์อย่าง "xabc" เป็นการอ้างอิงอาจเป็นสัญญาณว่ามีบางอย่างผิดปกติเกิดขึ้น (เว้นแต่คุณอาจกำลังแสดงท่าทีแปลกประหลาด)
กล่าวคือสามเณรที่หาทางไปสู่คำตอบนี้ต้องถามตัวเองว่า "ทำไมฉันถึงทำอย่างนี้?"
เป็นเรื่องปกติที่จะทำเช่นนี้หากกรณีการใช้งานของคุณมีขนาดเล็กและคุณจะไม่พบปัญหาเกี่ยวกับประสิทธิภาพและคุณไม่จำเป็นต้องสร้างสิ่งที่เป็นนามธรรมเพื่อให้ซับซ้อนขึ้นในภายหลัง ในความเป็นจริงถ้าสิ่งนี้จะลดความซับซ้อนของรหัสและทำให้สิ่งต่าง ๆ เป็นเรื่องง่ายคุณควรจะไปข้างหน้าและทำสิ่งที่ OP ขอ อย่างไรก็ตามหากไม่ใช่กรณีดังกล่าวให้พิจารณาว่าสิ่งเหล่านี้มีผลบังคับใช้หรือไม่:
กรณีที่ 1 : เป็นวิธีหลักในการทำงานกับข้อมูลของคุณ (เช่นรูปแบบเริ่มต้นของแอปในการส่งวัตถุไปรอบ ๆ และยกเลิกการลงทะเบียน) เช่นเดียวกับการถาม "ฉันจะค้นหาฟังก์ชันหรือชื่อตัวแปรจากสตริงได้อย่างไร"
- นี่เป็นวิธีปฏิบัติในการเขียนโปรแกรมที่ไม่ดี (โดยเฉพาะการใช้โปรแกรมไม่จำเป็นโดยเฉพาะและชนิดของการละเมิดรูปแบบการเขียนโปรแกรมฟังก์ชั่นที่ไม่มีผลข้างเคียง สามเณรที่พบว่าตัวเองในกรณีนี้ควรพิจารณาการทำงานกับการเป็นตัวแทนอาร์เรย์เช่น ['x', 'a', 'b', 'c'] หรือแม้กระทั่งสิ่งที่ตรงไปตรงมา / ง่าย / ตรงไปตรงมาถ้าเป็นไปได้: เหมือนไม่แพ้ ติดตามการอ้างอิงตัวเองในสถานที่แรก (เหมาะที่สุดถ้าเป็นเพียงฝั่งไคลเอ็นต์หรือฝั่งเซิร์ฟเวอร์เท่านั้น) ฯลฯ (รหัสเฉพาะที่มีอยู่ก่อนจะไม่เหมาะสมที่จะเพิ่ม แต่สามารถใช้ได้ถ้าข้อมูลจำเพาะต้องการเป็นอย่างอื่น การดำรงอยู่โดยไม่คำนึงถึง)
กรณีที่ 2 : การทำงานกับข้อมูลที่ต่อเนื่องกันหรือข้อมูลที่จะแสดงต่อผู้ใช้ เช่นการใช้วันที่เป็นสตริง "1999-12-30" แทนที่จะเป็นวัตถุ Date (ซึ่งอาจทำให้เกิดข้อบกพร่องของเขตเวลาหรือเพิ่มความซับซ้อนในการทำให้เป็นอนุกรมหากไม่ระวัง) หรือคุณรู้ว่าคุณกำลังทำอะไร
- อาจจะไม่เป็นไร ระวังว่าไม่มีสตริงจุด "." ในชิ้นส่วนอินพุตที่ถูกสุขลักษณะของคุณ
หากคุณพบว่าตัวเองใช้คำตอบนี้อยู่ตลอดเวลาและแปลงไปมาระหว่างสตริงและอาร์เรย์คุณอาจอยู่ในสถานการณ์ที่ไม่ดีและควรพิจารณาทางเลือกอื่น
นี่คือหนึ่งซับที่หรูหราสั้นลง 10 เท่าเมื่อเทียบกับโซลูชันอื่น:
function index(obj,i) {return obj[i]}
'a.b.etc'.split('.').reduce(index, obj)
[แก้ไข] หรือใน ECMAScript 6:
'a.b.etc'.split('.').reduce((o,i)=>o[i], obj)
(ไม่ใช่ว่าฉันคิดว่า eval เลวร้ายเสมอเหมือนที่คนอื่นแนะนำว่ามันคือ (แต่มักจะเป็น) แต่คนเหล่านั้นจะพอใจว่าวิธีนี้ไม่ได้ใช้ eval ข้างต้นจะได้obj.a.b.etcรับobjและสตริง"a.b.etc" )
เพื่อตอบสนองต่อผู้ที่ยังกลัวที่reduceจะใช้แม้จะอยู่ในมาตรฐาน ECMA-262 (ฉบับที่ 5) นี่คือการใช้งานแบบเรียกซ้ำสองบรรทัด:
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')
ขึ้นอยู่กับการปรับแต่งที่คอมไพเลอร์ JS กำลังทำอยู่คุณอาจต้องการให้แน่ใจว่าฟังก์ชั่นที่ซ้อนกันไม่ได้ถูกกำหนดใหม่ในการโทรทุกครั้งผ่านวิธีการปกติ (วางไว้ในการปิดวัตถุหรือเนมสเปซส่วนกลาง)
แก้ไข :
ในการตอบคำถามที่น่าสนใจในความคิดเห็น:
คุณจะทำให้สิ่งนี้กลายเป็นตัวตั้งได้อย่างไร ไม่เพียง แต่คืนค่าตามเส้นทาง แต่ยังตั้งค่าหากมีการส่งค่าใหม่เข้าสู่ฟังก์ชัน - Swader 28 มิ.ย. เวลา 21:42 น
(sidenote: น่าเศร้าที่ไม่สามารถส่งคืนวัตถุด้วย Setter ได้เนื่องจากจะเป็นการละเมิดข้อตกลงการเรียก; commenter ดูเหมือนจะอ้างถึงฟังก์ชัน setter-style ทั่วไปที่มีผลข้างเคียงเช่นindex(obj,"a.b.etc", value)ทำobj.a.b.etc = value)
reduceสไตล์ไม่ได้จริงๆเหมาะกับที่ แต่เราสามารถปรับเปลี่ยนการดำเนินงาน recursive:
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);
}
การสาธิต:
> 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
... setIndex(...)แต่ส่วนตัวผมอยากแนะนำให้ทำฟังก์ชั่นที่แยกต่างหาก ฉันต้องการที่จะจบลงด้วยข้อความด้านข้างว่าปัญหาที่ตอบยากของคำถามสามารถ (ควร?) จะทำงานกับอาร์เรย์ของดัชนี (ซึ่งพวกเขาสามารถได้รับจาก.split) มากกว่าสตริง; แม้ว่าจะไม่มีอะไรผิดปกติกับฟังก์ชั่นอำนวยความสะดวก
ผู้วิจารณ์ถามว่า:
แล้วอาร์เรย์ล่ะ บางอย่างเช่น "ab [4] .cd [1] [2] [3]"? -AlexS
Javascript เป็นภาษาที่แปลกมาก ในวัตถุทั่วไปสามารถมีสตริงเป็นคีย์คุณสมบัติของพวกเขาดังนั้นตัวอย่างเช่นถ้าxเป็นวัตถุทั่วไปเช่นx={}นั้นx[1]จะกลายเป็นx["1"]... คุณอ่านถูกต้องว่า ... yup ...
Javascript Arrays (ซึ่งเป็นอินสแตนซ์ของ Object) เป็นตัวส่งเสริมคีย์จำนวนเต็มโดยเฉพาะแม้ว่าคุณสามารถทำอะไรบางอย่างx=[]; x["puppy"]=5;ได้
แต่โดยทั่วไป (และมีข้อยกเว้น) x["somestring"]===x.somestring(เมื่อได้รับอนุญาตคุณไม่สามารถทำได้x.123 )
(โปรดทราบว่าสิ่งใดที่ผู้รวบรวม JS ที่คุณใช้อาจเลือกรวบรวมคอมไพล์เหล่านี้ให้เป็นตัวแทนของ saner ถ้ามันสามารถพิสูจน์ได้ว่ามันจะไม่ละเมิด spec)
ดังนั้นคำตอบสำหรับคำถามของคุณจะขึ้นอยู่กับว่าคุณสมมติว่าวัตถุเหล่านั้นยอมรับเฉพาะจำนวนเต็ม (เนื่องจากข้อ จำกัด ในโดเมนปัญหาของคุณ) หรือไม่ สมมติว่าไม่ จากนั้นการแสดงออกที่ถูกต้องเป็นกำหนดการตัวระบุฐานบวกบาง.identifiers บวกบาง["stringindex"]s
นี่จะเทียบเท่ากับa["b"][4]["c"]["d"][1][2][3]แม้ว่าเราควรจะสนับสนุนa.b["c\"validjsstringliteral"][3]เช่นกัน คุณต้องตรวจสอบส่วนของไวยากรณ์ ecmascript บนตัวอักษรสตริงเพื่อดูวิธีการวิเคราะห์สตริงตัวอักษรที่ถูกต้อง ในทางเทคนิคคุณยังต้องการที่จะตรวจสอบ (เหมือนในคำตอบแรกของฉัน) ที่aเป็นที่ถูกต้องระบุจาวาสคริปต์
คำตอบง่ายๆสำหรับคำถามของคุณหากสตริงของคุณไม่มีเครื่องหมายจุลภาคหรือเครื่องหมายวงเล็บจะเป็นการจับคู่ความยาว 1+ ลำดับอักขระที่ไม่อยู่ในชุด,หรือ[หรือ]:
> "abc[4].c.def[1][2][\"gh\"]".match(/[^\]\[.]+/g)
// ^^^ ^ ^ ^^^ ^ ^ ^^^^^
["abc", "4", "c", "def", "1", "2", ""gh""]
หากสตริงของคุณไม่มีอักขระหรือ"อักขระยกเว้นและเนื่องจาก IdentifierNames เป็นภาษาย่อยของ StringLiterals (ฉันคิดว่า ???) คุณสามารถแปลงจุดเป็น []:
> 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"]
แน่นอนว่าต้องระมัดระวังและไม่เชื่อถือข้อมูลของคุณ วิธีที่ไม่ดีในการทำเช่นนี้ซึ่งอาจใช้ได้ผลกับบางกรณีเช่น:
// 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
แก้ไขพิเศษปี 2018:
Let 's go แบบเต็มรูปแบบวงกลมและจะไม่มีประสิทธิภาพมากที่สุดวิธีการแก้ปัญหาอย่างน่ากลัว-overmetaprogrammed เราสามารถขึ้นมา ... ในความสนใจของการสร้างประโยคบริสุทธิ์ hamfistery ด้วยออบเจ็กต์ ES6 Proxy! ... ลองกำหนดคุณสมบัติบางอย่างที่ (imho นั้นดีและยอดเยี่ยม แต่) อาจทำลายไลบรารี่ที่เขียนไม่ถูกต้อง คุณอาจจะระมัดระวังในการใช้สิ่งนี้หากคุณสนใจในเรื่องของการแสดงความมีสติ (ของคุณหรือของผู้อื่น) งานของคุณ ฯลฯ
// [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);
}
การสาธิต:
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']);
เอาท์พุท:
obj คือ: {"a": {"b": {"c": 1, "d": 2}}}}
(การลบล้างพร็อกซีรับ) objHyper ['abc'] คือ: 1
(ชุดการแทนที่พร็อกซี) objHyper ['abc'] = 3 ตอนนี้ obj คือ: {"a": {"b": {"c": 3, "d": 2}}}
(เบื้องหลัง) objHyper คือ: พร็อกซี {a: {…}}
(ทางลัด) obj.H ['abc'] = 4
(ทางลัด) obj.H ['abc'] is obj ['a'] ['b'] ['c'] คือ: 4
แนวคิดที่ไม่มีประสิทธิภาพ: คุณสามารถแก้ไขข้อมูลข้างต้นเพื่อจัดส่งตามอาร์กิวเมนต์อินพุตได้ ทั้งใช้.match(/[^\]\[.]+/g)วิธีการในการสนับสนุนobj['keys'].like[3]['this']หรือหากinstanceof Arrayแล้วก็ยอมรับอาร์เรย์เป็น input keys = ['a','b','c']; obj.H[keys]เช่น
ตามคำแนะนำที่คุณอาจต้องการจัดการกับดัชนีที่ไม่ได้กำหนดในรูปแบบ 'นุ่ม' NaN สไตล์ (เช่นindex({a:{b:{c:...}}}, 'a.x.c')กลับไม่ได้กำหนดมากกว่าที่ไม่ได้ระบุ TypeError) ... :
1) สิ่งนี้สมเหตุสมผลจากมุมมองของ "เราควรกลับไม่ได้กำหนดมากกว่าโยนข้อผิดพลาด" ในสถานการณ์ดัชนี 1 มิติ ({}) ['เช่น'] == ไม่ได้กำหนดดังนั้น "เราควรกลับไม่ได้กำหนดมากกว่าโยน ข้อผิดพลาด "ในสถานการณ์ N-Dim
2) สิ่งนี้ไม่สมเหตุสมผลจากมุมมองที่เราทำx['a']['x']['c']ซึ่งจะล้มเหลวด้วย TypeError ในตัวอย่างด้านบน
ที่กล่าวว่าคุณจะใช้งานได้โดยเปลี่ยนฟังก์ชั่นการลดลงของคุณด้วย:
(o,i)=>o===undefined?undefined:o[i](o,i)=>(o||{})[i]หรือ
(คุณสามารถทำให้สิ่งนี้มีประสิทธิภาพมากขึ้นโดยใช้ for for loop และ break / return เมื่อใดก็ตามที่ subresult ที่คุณต้องการจัดทำดัชนีถัดไปนั้นไม่ได้กำหนดไว้หรือใช้ try-catch ถ้าคุณคาดว่าความล้มเหลวดังกล่าวจะหายากพอสมควร)
evalเป็นสิ่งที่ชั่วร้าย; อย่าใช้มัน