остання зауваження: Хоча я лестощів, що ця відповідь отримала багато відгуків, я також дещо жахнувся. Якщо вам потрібно перетворити рядки точкових нотацій на зразок "xabc" у посилання, це може бути (може бути) ознакою того, що відбувається щось дуже не так (якщо, можливо, ви проводите якусь дивну дезаріалізацію).
Тобто новачки, які знаходять дорогу до цієї відповіді, повинні задати собі питання "чому я це роблю?"
Звичайно, це нормально, якщо це є невеликим випадком використання, і ви не зіткнетесь з проблемами продуктивності, і вам не потрібно буде будувати свою абстракцію, щоб згодом це було складніше. Насправді, якщо це зменшить складність коду та спростить речі, вам, ймовірно, варто йти вперед і робити те, про що вимагає ОП. Однак якщо це не так, врахуйте, чи застосовується будь-яке з них:
випадок 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, ви можете переконатися, що будь-які вкладені функції не будуть визначені заново під час кожного виклику звичайними методами (розміщення їх у закритому, об’єктному або глобальному просторі імен).
редагувати :
Щоб відповісти на цікаве запитання в коментарях:
як би ти перетворив це на сетер також? Не тільки повернення значень шляхом, але і встановлення їх, якщо нове значення буде відправлено у функцію? - Спідник 28 червня о 21:42
(sidenote: на жаль, не може повернути об'єкт із сеттером, оскільки це порушить умову виклику; здається, що коментатор замість цього посилається на загальну функцію стилю сеттера з побічними ефектами на зразок index(obj,"a.b.etc", value)виконання obj.a.b.etc = value.)
reduceСтиль не дуже підходить до цього, але ми можемо змінити рекурсивную реалізацію:
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"]... ви читаєте це право ... так ...
Javascript-масиви (які самі є об'єктами Object) спеціально заохочують цілі клавіші, навіть якщо ви можете зробити щось подібне x=[]; x["puppy"]=5;.
Але загалом (і є винятки) x["somestring"]===x.somestring(коли це дозволено; ви не можете цього зробити x.123).
(Майте на увазі, що будь-який компілятор JS, який ви використовуєте, може вибрати, можливо, скомпонувати їх до більш безпечних уявлень, якщо це може довести, що це не порушило б специфікацію.)
Отже, відповідь на ваше запитання залежатиме від того, ви вважаєте, що ці об'єкти приймають лише цілі числа (через обмеження у вашій проблемній області), чи ні. Припустимо, що ні. Тоді правильним виразом є об'єднання базового ідентифікатора плюс деякий .identifiers плюс деякий ["stringindex"]s
Тоді це було б рівнозначно a["b"][4]["c"]["d"][1][2][3], хоча, мабуть, ми також повинні підтримувати a.b["c\"validjsstringliteral"][3]. Вам доведеться перевірити розділ граматики ecmascript на літеральних рядках, щоб побачити, як розібрати дійсний літеральний рядок. Технічно ви також хочете перевірити (на відміну від моєї першої відповіді), що aце дійсний ідентифікатор JavaScript .
Проста відповідь на ваше запитання , хоча, якщо ваші рядки не містять коми або дужки , буде просто відповідати довжині 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 року:
Давайте підемо по колу і зробимо найефективніше, жахливо переграмоване запрограмоване рішення, яке ми можемо придумати ... в інтересах синтаксичної чистоти . З об’єктами проксі-сервера ES6! ... Давайте також визначимо деякі властивості, які (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}}}
(proxy override get) objHyper ['abc'] є: 1
(набір переопределення проксі) objHyper ['abc'] = 3, тепер obj є: {"a": {"b": {"c": 3, "d": 2}}}
(за лаштунками) objHyper: Проксі {a: {…}}
(ярлик) obj.H ['abc'] = 4
(ярлик) obj.H ['abc'] є obj ['a'] ['b'] ['c']: 4
неефективна ідея: Ви можете змінити вищезазначене для відправки на основі вхідного аргументу; або використовувати .match(/[^\]\[.]+/g)метод для підтримки obj['keys'].like[3]['this'], або якщо instanceof Array, тоді просто прийміть масив як вхідний вид keys = ['a','b','c']; obj.H[keys].
На думку, що, можливо, ви хочете обробляти невизначені індекси "м'якшим" NaN-стилем (наприклад, index({a:{b:{c:...}}}, 'a.x.c')повертайте невизначений, а не uncaught TypeError) ...:
1) Це має сенс з точки зору "ми повинні повернутись невизначеним, а не кидати помилку" в ситуації з 1-мірним індексом ({}) ['напр.'] == undefined, тому "ми повинні повертатися невизначеними, а не кидати помилка "в N-мірній ситуації.
2) Це не має сенсу з точки зору, який ми робимо x['a']['x']['c'], що не вдасться з TypeError у наведеному вище прикладі.
Це означає, що ви зробите цю роботу, замінивши функцію скорочення на будь-яку:
(o,i)=>o===undefined?undefined:o[i], або
(o,i)=>(o||{})[i].
(Ви можете зробити це більш ефективним, використовуючи цикл і розбиваючи / повертаючи, коли субрезультат, у який ви наступного індексу, не буде визначений, або використовуючи пробний лов, якщо ви очікуєте, що такі збої будуть досить рідкісними.)
evalзлий; не користуйтеся ним