недавняя заметка: хотя я польщен, что этот ответ получил много голосов, я также несколько испуган. Если нужно преобразовать строки с точечными обозначениями, такие как «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 ссылается на общую функцию стиля сеттера с побочными эффектами, такими как 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 Arrays (которые сами являются экземплярами 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""]
Если ваши строки не содержат escape-символов или "символов и поскольку 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 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 is: {"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'], либо if instanceof Array, затем просто примите Array в качестве входных данных keys = ['a','b','c']; obj.H[keys].
Предполагается, что, возможно, вы захотите обработать неопределенные индексы в «более мягком» стиле NaN (например, index({a:{b:{c:...}}}, 'a.x.c')вернуть undefined, а не uncaught TypeError) ...:
1) Это имеет смысл с точки зрения «мы должны вернуть undefined, а не выбросить ошибку» в ситуации с одномерным индексом ({}) ['eg'] == undefined, поэтому «мы должны вернуть undefined вместо того, чтобы бросать ошибка "в N-мерной ситуации.
2) Это не имеет смысла с точки зрения того, что мы делаем x['a']['x']['c'], что приведет к ошибке TypeError в приведенном выше примере.
Тем не менее, вы бы сделали эту работу, заменив функцию сокращения на:
(o,i)=>o===undefined?undefined:o[i]или
(o,i)=>(o||{})[i].
(Вы можете сделать это более эффективным, используя цикл for и прерывая / возвращая всякий раз, когда подрезультат, в который будет добавлен следующий индекс, не определен, или используя try-catch, если вы ожидаете, что такие ошибки будут достаточно редкими.)
evalзлой; не используйте это