Access object child properties using a dot notation string

我暂时陷入了一个貌似非常简单的 JavaScript 问题,但也许我只是遗漏了正确的搜索关键字!

假设我们发现了一个物体

var r = { a:1, b: {b1:11, b2: 99}};

有几种方法可以访问99:

r.b.b2
r['b']['b2']

What I want is to be able to define a string

var s = "b.b2";

然后使用

r.s or r[s] //(which of course won't work)

一种方法是为它编写一个函数,该函数按点分割字符串,并可能递归/迭代地获取该属性。但是有没有更简单/更有效的方法呢?在这里的 jQuery API 中有什么有用的东西吗?

73217 次浏览

如果在您的场景中可以将所需的整个数组变量放入一个字符串中,则可以使用 eval()函数。

var r = { a:1, b: {b1:11, b2: 99}};
var s = "r.b.b2";
alert(eval(s)); // 99

I can feel people reeling in horror

我不知道支持什么 jQuery API 函数,但我有这个函数:

    var ret = data; // Your object
var childexpr = "b.b2"; // Your expression


if (childexpr != '') {
var childs = childexpr.split('.');
var i;
for (i = 0; i < childs.length && ret != undefined; i++) {
ret = ret[childs[i]];
}
}


return ret;

你也可以这么做

var s = "['b'].b2";
var num = eval('r'+s);

简短的回答: 不,没有像您希望的那样的本机 .access函数。正如您正确提到的,您必须定义您自己的函数,该函数分割字符串并循环/检查它的各个部分。

当然,您总是可以使用 eval()(即使它被认为是不好的做法)。

喜欢

var s = 'b.b2';


eval('r.' + s); // 99

下面是我前段时间写的一个简单的函数,但它适用于基本的对象属性:

function getDescendantProp(obj, desc) {
var arr = desc.split(".");
while(arr.length && (obj = obj[arr.shift()]));
return obj;
}


console.log(getDescendantProp(r, "b.b2"));
//-> 99

尽管有一些答案可以将其扩展为“允许”数组索引访问,但这并不是真正必要的,因为您可以使用点表示法使用这种方法指定数值索引:

getDescendantProp({ a: [ 1, 2, 3 ] }, 'a.2');
//-> 3

这是我能做的最简单的事:

var accessProperties = function(object, string){
var explodedString = string.split('.');
for (i = 0, l = explodedString.length; i<l; i++){
object = object[explodedString[i]];
}
return object;
}
var r = { a:1, b: {b1:11, b2: 99}};


var s = "b.b2";
var o = accessProperties(r, s);
alert(o);//99

我已经扩展了 Andy E 的答案,所以它也可以处理数组:

function getDescendantProp(obj, desc) {
var arr = desc.split(".");


//while (arr.length && (obj = obj[arr.shift()]));


while (arr.length && obj) {
var comp = arr.shift();
var match = new RegExp("(.+)\\[([0-9]*)\\]").exec(comp);
if ((match !== null) && (match.length == 3)) {
var arrayData = { arrName: match[1], arrIndex: match[2] };
if (obj[arrayData.arrName] != undefined) {
obj = obj[arrayData.arrName][arrayData.arrIndex];
} else {
obj = undefined;
}
} else {
obj = obj[comp]
}
}


return obj;
}

可能有更有效的方法来实现正则表达式,但它是紧凑的。

You can now do stuff like:

var model = {
"m1": {
"Id": "22345",
"People": [
{ "Name": "John", "Numbers": ["07263", "17236", "1223"] },
{ "Name": "Jenny", "Numbers": ["2", "3", "6"] },
{ "Name": "Bob", "Numbers": ["12", "3333", "4444"] }
]
}
}


// Should give you "6"
var x = getDescendantProp(model, "m1.People[1].Numbers[2]");

这里有一个比 @ Andy’s答案好一点的方法,其中 obj(上下文)是 可以选择,如果没有提供,它会回到 window。.

function getDescendantProp(desc, obj) {
obj = obj || window;
var arr = desc.split(".");
while (arr.length && (obj = obj[arr.shift()]));
return obj;
};

扩展@JohnB 的回答,我也添加了 setter 值

http://plnkr.co/edit/lo0thC?p=preview

enter image description here

function getSetDescendantProp(obj, desc, value) {
var arr = desc ? desc.split(".") : [];


while (arr.length && obj) {
var comp = arr.shift();
var match = new RegExp("(.+)\\[([0-9]*)\\]").exec(comp);


// handle arrays
if ((match !== null) && (match.length == 3)) {
var arrayData = {
arrName: match[1],
arrIndex: match[2]
};
if (obj[arrayData.arrName] !== undefined) {
if (typeof value !== 'undefined' && arr.length === 0) {
obj[arrayData.arrName][arrayData.arrIndex] = value;
}
obj = obj[arrayData.arrName][arrayData.arrIndex];
} else {
obj = undefined;
}


continue;
}


// handle regular things
if (typeof value !== 'undefined') {
if (obj[comp] === undefined) {
obj[comp] = {};
}


if (arr.length === 0) {
obj[comp] = value;
}
}


obj = obj[comp];
}


return obj;
}

下面是 Andy E 代码的一个扩展,它递归到数组中并返回所有值:

function GetDescendantProps(target, pathString) {
var arr = pathString.split(".");
while(arr.length && (target = target[arr.shift()])){
if (arr.length && target.length && target.forEach) { // handle arrays
var remainder = arr.join('.');
var results = [];
for (var i = 0; i < target.length; i++){
var x = this.GetDescendantProps(target[i], remainder);
if (x) results = results.concat(x);
}
return results;
}
}
return (target) ? [target] : undefined; //single result, wrap in array for consistency
}

因此,根据 target:

var t =
{a:
{b: [
{'c':'x'},
{'not me':'y'},
{'c':'z'}
]
}
};

We get:

GetDescendantProps(t, "a.b.c") === ["x", "z"]; // true

You can use Loash get ()和 set ()方法.

得到

var object = { 'a': [{ 'b': { 'c': 3 } }] };


_.get(object, 'a[0].b.c');
// → 3

Setting

var object = { 'a': [{ 'b': { 'c': 3 } }] };


_.set(object, 'a[0].b.c', 4);
console.log(object.a[0].b.c);
// → 4

对 Andy E、 Jason More 和我自己的解决方案的性能测试可以在 http://jsperf.com/propertyaccessor上找到。请随意使用您自己的浏览器运行测试以添加到收集的数据中。

The prognosis is clear, Andy E's solution is the fastest by far!

For anyone interested, here is the code for my solution to the original question.

function propertyAccessor(object, keys, array) {
/*
Retrieve an object property with a dot notation string.
@param  {Object}  object   Object to access.
@param  {String}  keys     Property to access using 0 or more dots for notation.
@param  {Object}  [array]  Optional array of non-dot notation strings to use instead of keys.
@return  {*}
*/
array = array || keys.split('.')


if (array.length > 1) {
// recurse by calling self
return propertyAccessor(object[array.shift()], null, array)
} else {
return object[array]
}
}

将对象作为 initalValue传递时,将 减少分开

Update (感谢 TeChn4K 的评论)

使用 ES6语法,它甚至更短

var r = { a:1, b: {b1:11, b2: 99}};
var s = "b.b2";


var value = s.split('.').reduce((a, b) => a[b], r);


console.log(value);

老版本

var r = { a:1, b: {b1:11, b2: 99}};
var s = "b.b2";


var value = s.split('.').reduce(function(a, b) {
return a[b];
}, r);


console.log(value);