nev*_*evf 188 javascript
给定一个JS对象
var obj = { a: { b: '1', c: '2' } }
Run Code Online (Sandbox Code Playgroud)
和一个字符串
"a.b"
Run Code Online (Sandbox Code Playgroud)
如何将字符串转换为点符号,以便我可以去
var val = obj.a.b
Run Code Online (Sandbox Code Playgroud)
如果字符串只是'a'我可以使用,'a'但这更复杂.我想有一些简单的方法,但它目前逃脱了.
nin*_*cko 390
最近的一点:虽然我很高兴这个答案得到了很多赞成,但我也有点害怕.如果需要将诸如"xabc"之类的点符号字符串转换为引用,则可能表明存在一些非常错误(除非您执行某些奇怪的反序列化).它是矫枉过正,因为它是不必要的元编程,也有点违反功能性无副作用编码风格.此外,如果您执行的操作超出了您的需要,也可以获得大量的性能命中(例如,作为应用程序的默认形式,即传递对象并取消引用它们).如果由于某种原因这是服务器端的js,通常用于输入的清理.找到回答这个问题的新手应该考虑使用数组表示法,例如['x','a','b','c'],或者甚至是更直接/简单/直接的东西,如果可能的话,不要丢失跟踪引用本身,或者可能是一些预先存在的唯一ID等.
这是一款优雅的单线,比其他解决方案短10倍:
function index(obj,i) {return obj[i]}
'a.b.etc'.split('.').reduce(index, obj)
Run Code Online (Sandbox Code Playgroud)
[编辑]或在ECMAScript 6中:
'a.b.etc'.split('.').reduce((o,i)=>o[i], obj)
Run Code Online (Sandbox Code Playgroud)
(并不是说我认为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')
Run Code Online (Sandbox Code Playgroud)
根据JS编译器正在进行的优化,您可能希望确保通过常用方法(将它们放在闭包,对象或全局命名空间中)不会在每次调用时重新定义任何嵌套函数.
编辑:
在评论中回答一个有趣的问题:
你怎么会把它变成一个二传手呢?不仅要按路径返回值,还要在将新值发送到函数时设置它们? - Swader 6月28日21:42
(旁注:遗憾的是不能与二传手返回一个对象,因为这将违反调用约定;评论者似乎改为参照与像副作用一般二传手式的功能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);
}
Run Code Online (Sandbox Code Playgroud)
演示:
> 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
Run Code Online (Sandbox Code Playgroud)
...虽然我个人推荐单独的功能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""]
Run Code Online (Sandbox Code Playgroud)
如果你的字符串不包含转义字符或"字符,并且因为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"]
Run Code Online (Sandbox Code Playgroud)
当然,要小心,永远不要相信你的数据.执行此操作的一些不良方法可能适用于某些用例还包括:
// 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
Run Code Online (Sandbox Code Playgroud)
特别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);
}
Run Code Online (Sandbox Code Playgroud)
演示:
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']);
Run Code Online (Sandbox Code Playgroud)
输出:
obj是:{"a":{"b":{"c":1,"d":2}}}
(代理覆盖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')返回undefined而不是未捕获的TypeError)...:
1)从"我们应该返回未定义而不是抛出错误"的角度来看,这在1维索引情况中是合理的({})['eg'] == undefined,所以"我们应该返回undefined而不是抛出一个错误"在N维情况下.
2)这并不会从我们正在做的角度看来有意义的x['a']['x']['c'],这将失败,并在上面的例子中一个类型错误.
也就是说,你可以通过以下任一方式替换你的还原功能来完成这项工作:
(o,i)=>o===undefined?undefined:o[i]或者
(o,i)=>(o||{})[i].
(您可以通过使用for循环并在下次索引的子结构未定义时断开/返回来提高效率,或者使用try-catch,如果您认为这样的故障非常罕见.)
Pet*_*nov 57
如果你可以使用lodash,那么有一个函数,它正是这样做的:
_.get(object,path,[defaultValue])
var val = _.get(obj, "a.b");
Run Code Online (Sandbox Code Playgroud)
Tha*_*you 34
2023年
每次您希望在程序中获得新功能时,您不需要引入另一个依赖项。现代 JS 非常强大,可选链运算符 ?.现在得到了广泛的支持,使得这种任务变得非常简单。
通过一行代码,我们可以编写get接受输入对象t和 string 的代码path。它适用于任何嵌套级别的对象和数组 -
const get = (t, path) =>
path.split(".").reduce((r, k) => r?.[k], t)
const mydata =
{ a: { b: [ 0, { c: { d: [ "hello", "world" ] } } ] } }
console.log(get(mydata, "a.b.1.c.d.0"))
console.log(get(mydata, "a.b.1.c.d.1"))
console.log(get(mydata, "a.b.x.y.z"))Run Code Online (Sandbox Code Playgroud)
"hello"
"world"
undefined
Run Code Online (Sandbox Code Playgroud)
放
set当您考虑对象的值可能是其他对象或数组时,该操作是一个不平凡的函数。我们应该如何处理不存在的对象或数组的深度集?我们应该一路创造它们吗?那么如何解决这样的冲突呢?
set(mydata, "a", 1) // { "a": 1 }
set(mydata, "a.b", 2) // Error: cannot set "b" property on number
Run Code Online (Sandbox Code Playgroud)
我们将set在下面写一个简单的 -
const get = (t, path) =>
path.split(".").reduce((r, k) => r?.[k], t)
const set = (t, path, value) => {
if (typeof t != "object") throw Error("non-object")
if (path == "") throw Error("empty path")
const pos = path.indexOf(".")
return pos == -1
? (t[path] = value, value)
: set(t[path.slice(0, pos)], path.slice(pos + 1), value)
}
// build data from previous example
const mydata = {}
set(mydata, "a", {})
set(mydata, "a.b", [])
set(mydata, "a.b.0", 0)
set(mydata, "a.b.1", {})
set(mydata, "a.b.1.c", {})
set(mydata, "a.b.1.c.d", [])
set(mydata, "a.b.1.c.d.0", "hello")
set(mydata, "a.b.1.c.d.1", "world")
// read by path
console.log(get(mydata, "a.b.1.c.d.0"))
console.log(get(mydata, "a.b.1.c.d.1"))
console.log(get(mydata, "a.b.x.y.z"))Run Code Online (Sandbox Code Playgroud)
设置(高级)
但是,如果我们想要set自动创建对象和数组(如果它们尚不存在)怎么办?我们也能做到——
const get = (t, path) =>
path.split(".").reduce((r, k) => r?.[k], t)
const set = (t, path, value) => {
if (path == "") return value
const [k, next] = path.split({
[Symbol.split](s) {
const i = s.indexOf(".")
return i == -1 ? [s, ""] : [s.slice(0, i), s.slice(i + 1)]
}
})
if (t !== undefined && typeof t !== "object")
throw Error(`cannot set property ${k} of ${typeof t}`)
return Object.assign(
t ?? (/^\d+$/.test(k) ? [] : {}),
{ [k]: set(t?.[k], next, value) },
)
}
// build data from previous example
const mydata = set({}, "a.b", [
0,
set({}, "c.d", ["hello", "world"])
])
// print checkpoint
console.log(JSON.stringify(mydata, null, 2))
// set additional fields
set(mydata, "a.b.1.c.d.1", "moon")
set(mydata, "a.b.1.w", "x.y.z")
// ensure changes
console.log(JSON.stringify(mydata, null, 2))Run Code Online (Sandbox Code Playgroud)
.as-console-wrapper { min-height: 100%; top: 0; }Run Code Online (Sandbox Code Playgroud)
如果我们尝试在已设置的非对象值上设置键,则会引发运行时错误 -
const mydata = { a: 1 }
set(mydata, "a.foo", "bar")
// Error: cannot set property "foo" of number
Run Code Online (Sandbox Code Playgroud)
joe*_*arl 18
一个更复杂的递归示例.
function recompose(obj,string){
var parts = string.split('.');
var newObj = obj[parts[0]];
if(parts[1]){
parts.splice(0,1);
var newString = parts.join('.');
return recompose(newObj,newString);
}
return newObj;
}
var obj = { a: { b: '1', c: '2', d:{a:{b:'blah'}}}};
alert(recompose(obj,'a.d.a.b')); //blah
Run Code Online (Sandbox Code Playgroud)
小智 13
你也可以使用lodash.get
你只需安装这个软件包(npm i --save lodash.get),然后像这样使用它:
const get = require('lodash.get');
const myObj = { user: { firstName: 'Stacky', lastName: 'Overflowy' }, id: 123 };
console.log(get(myObj, 'user.firstName')); // prints Stacky
console.log(get(myObj, 'id')); //prints 123
//You can also update values
get(myObj, 'user').firstName = John;
Run Code Online (Sandbox Code Playgroud)
如果您希望多次取消引用相同的路径,那么为每个点符号路径构建函数实际上具有迄今为止最好的性能(扩展了在上面的注释中与James Wilkins相关的性能测试).
var path = 'a.b.x';
var getter = new Function("obj", "return obj." + path + ";");
getter(obj);
Run Code Online (Sandbox Code Playgroud)
在安全性和最坏情况性能方面,使用Function构造函数与eval()有一些相同的缺点,但对于需要极端动态和高性能组合的情况,IMO是一个使用不当的工具.我使用这种方法来构建数组过滤器函数,并在AngularJS摘要循环中调用它们.我的配置文件始终显示array.filter()步骤花费不到1毫秒来取消引用和过滤约2000个复杂对象,使用3-4级深度的动态定义路径.
当然,可以使用类似的方法来创建setter函数:
var setter = new Function("obj", "newval", "obj." + path + " = newval;");
setter(obj, "some new val");
Run Code Online (Sandbox Code Playgroud)
自原帖以来多年.现在有一个很棒的库叫做"对象路径". https://github.com/mariocasciaro/object-path
可在NPM和BOWER上使用 https://www.npmjs.com/package/object-path
它很简单:
objectPath.get(obj, "a.c.1"); //returns "f"
objectPath.set(obj, "a.j.0.f", "m");
Run Code Online (Sandbox Code Playgroud)
适用于深层嵌套的属性和数组.
小智 6
您可以使用 npm 提供的库,这可以简化此过程。https://www.npmjs.com/package/dot-object
var dot = require('dot-object');
var obj = {
some: {
nested: {
value: 'Hi there!'
}
}
};
var val = dot.pick('some.nested.value', obj);
console.log(val);
// Result: Hi there!
Run Code Online (Sandbox Code Playgroud)
var a = { b: { c: 9 } };
function value(layer, path, value) {
var i = 0,
path = path.split('.');
for (; i < path.length; i++)
if (value != null && i + 1 === path.length)
layer[path[i]] = value;
layer = layer[path[i]];
return layer;
};
value(a, 'b.c'); // 9
value(a, 'b.c', 4);
value(a, 'b.c'); // 4
Run Code Online (Sandbox Code Playgroud)
与更简单的eval方法相比,这是很多代码,但正如 Simon Willison 所说,你永远不应该使用 eval。
此外,JSFiddle。
其他建议有点神秘,所以我想我会做出贡献:
Object.prop = function(obj, prop, val){
var props = prop.split('.')
, final = props.pop(), p
while(p = props.shift()){
if (typeof obj[p] === 'undefined')
return undefined;
obj = obj[p]
}
return val ? (obj[final] = val) : obj[final]
}
var obj = { a: { b: '1', c: '2' } }
// get
console.log(Object.prop(obj, 'a.c')) // -> 2
// set
Object.prop(obj, 'a.c', function(){})
console.log(obj) // -> { a: { b: '1', c: [Function] } }
Run Code Online (Sandbox Code Playgroud)
请注意,如果您已经在使用Lodash,则可以使用property或get函数:
var obj = { a: { b: '1', c: '2' } };
_.property('a.b')(obj); // => 1
_.get(obj, 'a.b'); // => 1
Run Code Online (Sandbox Code Playgroud)
下划线也具有property功能,但不支持点表示法。
我建议拆分路径并对其进行迭代,并减少所拥有的对象。该建议使用缺少属性的默认值。
const getValue = (object, keys) => keys.split('.').reduce((o, k) => (o || {})[k], object);
console.log(getValue({ a: { b: '1', c: '2' } }, 'a.b'));
console.log(getValue({ a: { b: '1', c: '2' } }, 'foo.bar.baz'));Run Code Online (Sandbox Code Playgroud)
Mar*_*ich -1
目前尚不清楚你的问题是什么。给定你的对象,obj.a.b会给你“2”,就像它一样。如果你想操作字符串以使用括号,你可以这样做:
var s = 'a.b';
s = 'obj["' + s.replace(/\./g, '"]["') + '"]';
alert(s); // displays obj["a"]["b"]
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
104121 次 |
| 最近记录: |