将点表示法中的 JavaScript 字符串转换为对象引用
给定一个 JavaScript 对象,
var obj = { a: { b: '1', c: '2' } }
和一个字符串
"a.b"
如何将字符串转换为点表示法,以便我可以去
var val = obj.a.b
如果字符串只是,我可以使用.但这更复杂。我想有一些直接的方法,但它目前逃脱了我。'a'
obj[a]
给定一个 JavaScript 对象,
var obj = { a: { b: '1', c: '2' } }
和一个字符串
"a.b"
如何将字符串转换为点表示法,以便我可以去
var val = obj.a.b
如果字符串只是,我可以使用.但这更复杂。我想有一些直接的方法,但它目前逃脱了我。'a'
obj[a]
最近的笔记:虽然我对这个答案得到了许多支持感到受宠若惊,但我也有些惊恐。如果需要将像“x.a.b.c”这样的点符号字符串转换为引用,它可能(也许)表明发生了一些非常错误的事情(除非你正在执行一些奇怪的反序列化)。
也就是说,找到这个答案的新手必须问自己一个问题:“我为什么要这样做?
当然,如果你的用例很小,并且你不会遇到性能问题,并且你不需要在你的抽象基础上进行构建,使其以后变得更加复杂,那么这样做通常是可以的。事实上,如果这将降低代码复杂性并保持简单,那么你应该继续做OP所要求的事情。但是,如果不是这种情况,请考虑以下任何一项是否适用:
案例 1:作为处理数据的主要方法(例如,作为应用传递对象并取消引用对象的默认形式)。就像问“如何从字符串中查找函数或变量名称”一样。
- 这是糟糕的编程实践(特别是不必要的元编程,并且有点违反函数副作用的编码风格,并且会达到性能)。在这种情况下,新手应该考虑使用数组表示,例如['x','a','b','c'],或者如果可能的话,甚至可以更直接/简单/直接地使用数组表示:例如,首先不要丢失对引用本身的跟踪(如果它只是客户端或仅服务器端,则最理想)等(预先存在的唯一ID将无法添加, 但是,如果规范要求无论如何都存在,则可以使用。
案例 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"
为了回应那些仍然害怕使用它的人,尽管它在ECMA-262标准(第5版)中,这里有一个两行递归实现:reduce
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 Jun 28 at 21:42
(旁注:遗憾的是,不能返回带有 Setter 的对象,因为这会违反调用约定;commenter 似乎指的是一个具有副作用的通用 setter 样式函数,就像在做一样。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
一位评论者问道:
数组呢?类似于“a.b[4].c.d[1][2][3]”?–亚历克斯
Javascript是一种非常奇怪的语言;一般来说,对象只能有字符串作为其属性键,所以例如,如果是一个通用对象,如,那么就会变成...你没看错...是的。。。x
x={}
x[1]
x["1"]
Javascript数组(它们本身就是Object的实例)特别鼓励整数键,即使你可以做类似的事情。x=[]; x["puppy"]=5;
但总的来说(也有例外),(当它被允许时;你不能这样做)。x["somestring"]===x.somestring
x.123
(请记住,无论你使用什么JS编译器,如果它能证明它不会违反规范,可能会选择将这些编译成更合理的表示形式。
因此,您的问题的答案将取决于您是否假设这些对象只接受整数(由于您的问题域中的限制)。让我们假设不是。然后,有效表达式是基本标识符加上一些 s 加上一些 s 的串联。.identifier
["stringindex"]
让我们暂时忽略一下,我们当然可以在语法中合法地做其他事情,例如;整数不是“特殊”的。identifier[0xFA7C25DD].asdf[f(4)?.[5]+k][false][null][undefined][NaN]
然后,注释者的声明将等效于 ,尽管我们可能也应该支持 。您必须检查字符串文本上的 ecmascript 语法部分,以查看如何分析有效的字符串文本。从技术上讲,你还想检查(不像我的第一个答案)这是一个有效的javascript标识符。a["b"][4]["c"]["d"][1][2][3]
a.b["c\"validjsstringliteral"][3]
a
但是,如果您的字符串不包含逗号或括号,则只需匹配长度为1 +的字符序列(不在集合中)或:,
[
]
> "abc[4].c.def[1][2][\"gh\"]".match(/[^\]\[.]+/g)
// ^^^ ^ ^ ^^^ ^ ^ ^^^^^
["abc", "4", "c", "def", "1", "2", ""gh""]
如果你的字符串不包含转义字符或“字符,
并且因为标识符名称是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 代理对象!...让我们也定义一些属性(恕我直言,这些属性很好,很棒,但)可能会破坏编写不当的库。如果你关心性能,理智(你或别人的),你的工作等,你也许应该谨慎使用它。
// [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}}}
(proxy override get) objHyper['a.b.c'] is: 1
(proxy override set) objHyper['a.b.c']=3, now obj is: {“a”:{“b”:{“c”:3,“d”:2}}}
(幕后)objHyper is: Proxy {a: {...}}
(快捷方式) obj.H['a.b.c']=4
(快捷方式) obj.H['a.b.c'] is obj['a']['b']['c'] is: 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')
从1维索引情况({})['例如']==未定义的角度来看,这是有道理的,因此在N维情况下“我们应该返回未定义而不是抛出错误”。
从我们正在做的角度来看,这是没有意义的,在上面的例子中,如果TypeError会失败。x['a']['x']['c']
也就是说,您可以通过将约化函数替换为以下任一值来完成这项工作:
(o,i)=> o===undefined?undefined:o[i]
或。(o,i)=> (o||{})[i]
(您可以通过使用 for 循环并在未定义下一个索引的子结果时中断/返回,或者如果您预计此类故障非常罕见,则可以使用 try-catch 来提高效率。