使用动态计算的名称访问对象属性

967

我正在尝试使用动态名称访问对象的属性。 这是可能的吗?

const something = { bar: "Foobar!" };
const foo = 'bar';
something.foo; // The idea is to access something.bar, getting "Foobar!"

20个回答

1286

访问对象的属性有两种方式:点符号表示法和括号表示法:

  • 点符号表示法:something.bar
  • 括号表示法:something['bar']

方括号中的值可以是任何表达式。因此,如果属性名称存储在变量中,则必须使用括号表示法:

var something = {
  bar: 'foo'
};
var foo = 'bar';

// both x = something[foo] and something[foo] = x work as expected
console.log(something[foo]);
console.log(something.bar)


46
注意:JavaScript编译器会报错,因为它们不会重命名字符串,但会重命名对象属性。 - chacham15
9
为什么能够这样做的更多信息:JS对象是关联数组,这就是为什么。进一步阅读:http://www.quirksmode.org/js/associative.html https://dev59.com/6mYr5IYBdhLWcg3wAFg1 - Sudhanshu Mishra
3
@VanquishedWombat 不确定你反对的是什么?我没有说JS对象是数组? - Sudhanshu Mishra
1
有没有可能进一步获取基础对象的属性中的对象属性?我有一个“汽车”对象,我想知道司机的名字。我的汽车有一个驾驶员属性。该驾驶员对象具有名称属性。例如:car['Driver.Name']?<-这对我不起作用。谢谢。 - jgritten
2
请以后不要使用foo-bar... 这个解释太晦涩了。 - Cornelius
显示剩余7条评论

193

这是我的解决方案:

function resolve(path, obj) {
    return path.split('.').reduce(function(prev, curr) {
        return prev ? prev[curr] : null
    }, obj || self)
}

使用示例:

resolve("document.body.style.width")
// or
resolve("style.width", document.body)
// or even use array indexes
// (someObject has been defined in the question)
resolve("part.0.size", someObject) 
// returns null when intermediate properties are not defined:
resolve('properties.that.do.not.exist', {hello:'world'})

4
很好的答案,另请参阅:https://dev59.com/Apffa4cB1Zd3GeqP9IZt - Julian Knight
7
你启发了我创造了一个增强版,它允许使用括号表示法和带空格的属性名称,并验证输入:https://it.knightnet.org.uk/kb/node-js/get-properties/ - Julian Knight
3
我喜欢这个解决方案。但是,当我试图修改原始对象中的值时,似乎你的函数返回了对象的子副本。是否可能将其更改为修改返回的对象也会修改原始对象? - Eagle1
3
我希望你能提供“设定数值”的版本,谢谢。 - GaryO
2
太棒了!它可以处理深层嵌套的属性。 - NeNaD
显示剩余3条评论

64

在 JavaScript 中,我们可以使用以下方式进行访问:

  • 点表示法 - foo.bar
  • 方括号表示法 - foo[someVar]foo["string"]

但只有第二种情况允许动态访问属性:

var foo = { pName1 : 1, pName2 : [1, {foo : bar }, 3] , ...}

var name = "pName"
var num  = 1;

foo[name + num]; // 1

// -- 

var a = 2;
var b = 1;
var c = "foo";

foo[name + a][b][c]; // bar

13
我正在盯着2000行if语句,因为之前的开发者没有使用方括号,而是通过点符号静态访问对象属性。这是一个批准流程应用程序,有7个不同的批准者,而且步骤都是相同的。 /抱歉 - Chad

41

以下是一个ES6示例,演示了如何使用通过连接两个字符串动态生成的属性名称来访问对象的属性。

var suffix = " name";

var person = {
    ["first" + suffix]: "Nicholas",
    ["last" + suffix]: "Zakas"
};

console.log(person["first name"]);      // "Nicholas"
console.log(person["last name"]);       // "Zakas"

这被称为计算属性名


27

你可以用很多不同的方式来实现这个目标。

let foo = {
    bar: 'Hello World'
};

foo.bar;
foo['bar'];

方括号符号非常强大,因为它允许您基于变量访问属性:
let foo = {
    bar: 'Hello World'
};

let prop = 'bar';

foo[prop];

可以将此扩展到遍历对象的每个属性。这似乎是多余的,因为有了新的JavaScript构造,比如for ... of ...,但它有助于说明用例:

let foo = {
    bar: 'Hello World',
    baz: 'How are you doing?',
    last: 'Quite alright'
};

for (let prop in foo.getOwnPropertyNames()) {
    console.log(foo[prop]);
}

点符号和方括号符号在处理嵌套对象时也能按预期工作:

let foo = {
    bar: {
        baz: 'Hello World'
    }
};

foo.bar.baz;
foo['bar']['baz'];
foo.bar['baz'];
foo['bar'].baz;

对象解构

我们也可以考虑使用对象解构来访问对象中的属性,方法如下:

let foo = {
    bar: 'Hello World',
    baz: 'How are you doing?',
    last: 'Quite alright'
};

let prop = 'last';
let { bar, baz, [prop]: customName } = foo;

// bar = 'Hello World'
// baz = 'How are you doing?'
// customName = 'Quite alright'

18
您可以使用 Lodash get 这样来完成。
_.get(object, 'a[0].b.c');

有许多情况,例如深度嵌套的对象查找,这是唯一的选择。 - Jonathan Kempf
jQuery不足。 - user3840170

15

已更新

通过obj[variable]可以轻松访问对象中的根属性,但是获取嵌套属性会更加复杂。为了避免重复编写已有的代码,建议使用lodash.get

示例

// Accessing root property
var rootProp = 'rootPropert';
_.get(object, rootProp, defaultValue);

// Accessing nested property
var listOfNestedProperties = [var1, var2];
_.get(object, listOfNestedProperties);

Lodash get 可以以不同的方式使用,有关文档请参阅 lodash.get


5
尽可能避免使用 eval 是最好的选择。https://dev59.com/c3VD5IYBdhLWcg3wGXpI - Luke
9
仅仅为了访问属性而使用 eval 是过度杀鸡,不建议在任何情况下都这样做。如果你想访问嵌套对象中的属性,请使用obj['nested']['test'],这非常有效,并且不需要在字符串中嵌入代码。"麻烦"是什么? - Kyll
4
eval的速度可能会比较慢,是平常不建议给新手使用的,因为这样可能会养成不好的习惯。我会使用"obj['nested']['value']"方式来操作 - 记住,eval是邪恶的! - jaggedsoft
2
@Luke现在是唯一一个将Lodash _.get引入讨论的人。我认为这个答案现在应该得到赞而不是踩。虽然可能有些过度,但知道它的存在还是很好的。 - Emile Bergeron
2
谢谢你介绍了lodash。我通过谷歌来到这里,寻找一种在对象深处设置值的方法,并使用了他们的_.set方法(与上面相同,但有额外的参数用于设置值)。 - TPHughes
显示剩余4条评论

11

要动态访问属性,只需使用方括号 [][]即可:

const something = { bar: "Foobar!" };
const userInput = 'bar';
console.log(something[userInput])

问题

这种解决方案存在一个重大的问题!(我很惊讶其他答案尚未提及此问题)。通常,您只想访问自己放置在对象上的属性,而不想获取继承的属性。

以下是此问题的示例。这里有一个看起来无害的程序,但它有一个微妙的错误-你能发现它吗?

const agesOfUsers = { sam: 16, sally: 22 }
const username = prompt('Enter a username:')
if (agesOfUsers[username] !== undefined) {
  console.log(`${username} is ${agesOfUsers[username]} years old`)
} else {
  console.log(`${username} is not found`)
}

当提示输入用户名时,如果您提供“toString”作为用户名,它将给您以下消息:“toString is function toString() { [native code] } years old”。问题在于agesOfUsers是一个对象,因此自动继承基本Object类的某些属性,如.toString()。您可以在这里查看所有对象继承的完整属性列表。

解决方案

  1. 使用Map数据结构代替。映射的存储内容不会受到原型问题的影响,因此它们为此问题提供了干净的解决方案。

const agesOfUsers = new Map()
agesOfUsers.set('sam', 16)
agesOfUsers.set('sally', 2)
console.log(agesOfUsers.get('sam')) // 16

  

  1. 使用一个原型为空的对象,而不是默认的原型。你可以使用Object.create(null)来创建这样的对象。这种类型的对象不会受到这些原型问题的困扰,因为你明确地创建了它,使其不继承任何东西。

const agesOfUsers = Object.create(null)
agesOfUsers.sam = 16
agesOfUsers.sally = 22;
console.log(agesOfUsers['sam']) // 16
console.log(agesOfUsers['toString']) // undefined - toString was not inherited

  

  1. 你可以使用 Object.hasOwn(yourObj, attrName) 先检查你想要访问的动态键是否直接在对象上而不是继承而来(了解更多请点击 这里)。这是一个相对较新的特性,因此在将其放入代码之前,请先检查兼容性表。在 Object.hasOwn(yourObj, attrName) 出现之前,您可以通过 Object.prototype.hasOwnProperty.call(yourObj, attrName) 实现同样的效果。有时候,你可能会看到代码使用 yourObj.hasOwnProperty(attrName),它有时候也能工作,但是它有一些缺陷,你可以在 这里 读到相关信息。

// Try entering the property name "toString",
// you'll see it gets handled correctly.
const user = { name: 'sam', age: 16 }
const propName = prompt('Enter a property name:')
if (Object.hasOwn(user, propName)) {
  console.log(`${propName} = ${user[propName]}`)
} else {
  console.log(`${propName} is not found`)
}

  1. 如果您知道您正在尝试使用的键永远不会是继承属性的名称(例如,它们可能是数字,或者它们都具有相同的前缀等),那么您可以选择使用原始解决方案。

6
我遇到了一个案例,我想将对象属性的“地址”作为数据传递给另一个函数,并使用AJAX填充对象,从地址数组中查找,并在另一个函数中显示。由于需要进行字符串操作,我无法使用点符号。因此,我认为可以使用数组传递。最终,我还是采取了不同的方法,但似乎与此帖子有关。
以下是一个类似我所需数据的语言文件对象示例:
const locs = {
  "audioPlayer": {
    "controls": {
      "start": "start",
      "stop": "stop"
    },
    "heading": "Use controls to start and stop audio."
  }
}

我希望能够传递数组,例如:["audioPlayer", "controls", "stop"]以访问语言文本,在这种情况下是“stop”。
我创建了这个小函数来查找“最不具体”(第一个)地址参数,并将返回的对象重新分配给它自己。然后,如果存在下一个最具体的地址参数,则准备好查找它。
function getText(selectionArray, obj) {
  selectionArray.forEach(key => {
    obj = obj[key];
  });
  return obj;
}

使用方法:

/* returns 'stop' */
console.log(getText(["audioPlayer", "controls", "stop"], locs)); 

/* returns 'use controls to start and stop audio.' */
console.log(getText(["audioPlayer", "heading"], locs)); 

3

ES5 // 检查深度嵌套的变量

这段简单的代码可以检查深度嵌套变量/值的存在性,而无需沿途检查每个变量...

var getValue = function( s, context ){
    return Function.call( context || null, 'return ' + s )();
}

例如:- 一个深度嵌套的对象数组:

a = [ 
    {
      b : [
          {
             a : 1,
             b : [
                 {
                    c : 1,
                    d : 2   // we want to check for this
                 }
             ]
           }
      ]
    } 
]

替代方案:

if(a && a[0] && a[0].b && a[0].b[0] && a[0].b[0].b && a[0].b[0].b[0] && a[0].b[0].b[0].d && a[0].b[0].b[0].d == 2 )  // true

现在我们可以:
if( getValue('a[0].b[0].b[0].d') == 2 ) // true

干杯!


1
如果解决方案是使用eval,那么你刚刚又创建了一百万个其他问题。 - Rodrigo Leite
@RodrigoLeite 好的,给出至少一种方法应该不是问题... - user4602228
请看这里 :) https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/eval#Never_use_eval! - Rodrigo Leite
1
@RodrigoLeite 我已经阅读了它,并更新了解决方案,使用 Function 代替。 - user4602228
2
在ECMAScript 2020中,正确的处理方式是使用可选链if(a?.[0]?.b?.[0]?.b?.[0]?.d === 2){ … } - Sebastian Simon
@user4602228 - Function.call() 仍然是一种 eval 形式(您仍然将 JavaScript 作为字符串进行评估,只是没有显式调用 eval 函数),这并没有解决任何问题。 - Scotty Jamison

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接