测试是否存在嵌套的 JavaScript 对象键

IT技术 javascript object properties nested
2020-12-09 00:52:21

如果我有一个对象的引用:

var test = {};

这可能(但不是立即)具有嵌套对象,例如:

{level1: {level2: {level3: "level3"}}};

检查深度嵌套对象中是否存在属性的最佳方法是什么?

alert(test.level1);yield undefined,但alert(test.level1.level2.level3);失败了。

我目前正在做这样的事情:

if(test.level1 && test.level1.level2 && test.level1.level2.level3) {
    alert(test.level1.level2.level3);
}

但我想知道是否有更好的方法。

6个回答

如果您不想要 a,则必须逐步进行,TypeError因为如果其中一个成员是nullundefined,并且您尝试访问某个成员,则会引发异常。

您可以简单地catch排除异常,也可以创建一个函数来测试多个级别的存在,如下所示:

function checkNested(obj /*, level1, level2, ... levelN*/) {
  var args = Array.prototype.slice.call(arguments, 1);

  for (var i = 0; i < args.length; i++) {
    if (!obj || !obj.hasOwnProperty(args[i])) {
      return false;
    }
    obj = obj[args[i]];
  }
  return true;
}

var test = {level1:{level2:{level3:'level3'}} };

checkNested(test, 'level1', 'level2', 'level3'); // true
checkNested(test, 'level1', 'level2', 'foo'); // false

ES6 更新:

这是原始函数的较短版本,使用 ES6 特性和递归(它也是正确的尾调用形式):

function checkNested(obj, level,  ...rest) {
  if (obj === undefined) return false
  if (rest.length == 0 && obj.hasOwnProperty(level)) return true
  return checkNested(obj[level], ...rest)
}

但是,如果您想获取嵌套属性的值而不只是检查其是否存在,这里有一个简单的单行函数:

function getNested(obj, ...args) {
  return args.reduce((obj, level) => obj && obj[level], obj)
}

const test = { level1:{ level2:{ level3:'level3'} } };
console.log(getNested(test, 'level1', 'level2', 'level3')); // 'level3'
console.log(getNested(test, 'level1', 'level2', 'level3', 'length')); // 6
console.log(getNested(test, 'level1', 'level2', 'foo')); // undefined
console.log(getNested(test, 'a', 'b')); // undefined

上面的函数可以让你获取嵌套属性的值,否则会返回undefined.

更新 2019-10-17:

可选的链接建议对达到第3阶段ECMAScript委员会的过程,这将让你安全地访问深度嵌套的性质,通过使用令牌?.,新的可选链接操作

const value = obj?.level1?.level2?.level3 

如果访问的任何级别是nullundefined表达式将undefined自行解析

该提案还允许您安全地处理方法调用:

obj?.level1?.method();

上面的表达式会产生undefinedif obj, obj.level1, or obj.level1.methodare nullor undefined,否则会调用函数。

您可以使用可选的链接插件在 Babel 中开始使用此功能

Babel 7.8.0 开始,默认支持 ES2020

在 Babel REPL 上查看此示例

🎉🎉更新:2019 年 12 月 🎉🎉

可选链提案最终在 2019 年 12 月的 TC39 委员会会议上进入第 4 阶段这意味着此功能将成为ECMAScript 2020标准的一部分。

arguments实际上不是数组。Array.prototype.slice.call(arguments)将其转换为正式数组。学习
2021-02-16 00:52:21
为了紧缩起见,我将一个带有 try/catch 的版本放在一起,这并不奇怪 - 性能很糟糕(由于某种原因在 Safari 中除外)。下面有一些非常高效的答案,以及克劳迪乌的修改,这也比所选答案的性能要高得多。在这里查看 jsperf jsperf.com/check-if-deep-property-exists-with-willnotthrow
2021-02-18 00:52:21
this'd是很多更有效的var obj = arguments[0];,并开始从var i = 1不是复制的arguments对象
2021-02-26 00:52:21
这是一个非常难以维护的。如果任何属性键更改(它们会更改),项目的所有开发人员都必须“字符串搜索”整个代码库。这并不是问题的真正解决方案,因为它引入了一个更大的问题
2021-02-26 00:52:21
在 ES6 中,args变量声明可以被移除,并且...args 可以用作方法的第二个参数checkNesteddeveloper.mozilla.org/en/docs/Web/JavaScript/Reference/...
2021-03-03 00:52:21

这是我从奥利弗斯蒂尔那里学到的一个模式

var level3 = (((test || {}).level1 || {}).level2 || {}).level3;
alert( level3 );

事实上,整篇文章都是关于如何在 javascript 中做到这一点的讨论。他决定使用上述语法(一旦你习惯了它就不难阅读)作为习语。

@MuhammadUmer 不,重点(test || {})是如果测试未定义,那么您正在做({}.level1 || {}). 当然,{}.level1是未定义的,所以这意味着你正在做{}.level2,等等。
2021-02-19 00:52:21
@JoshuaTaylor:我认为他的意思是如果test没有声明,就会有一个ReferenceError,但这不是问题,因为如果没有声明,就会有一个错误需要修复,所以这个错误是一件好事。
2021-02-25 00:52:21
@wared 我认为这主要是因为它的简洁性很有趣。在链接的帖子中有对性能特征的详细讨论。是的,它总是执行所有测试,但它避免创建临时变量,如果您想防止每次创建新的空对象的开销,您可以将 {} 别名为 var。在 99% 的情况下,我不认为速度很重要,而且在这种情况下,分析是无可替代的。
2021-02-26 00:52:21
你说一旦习惯了它就不难了”好吧,这些迹象你已经知道这是一团糟那为什么要提出这个解决方案呢?它很容易出现拼写错误,并且绝对没有任何可读性。看看吧!如果我必须写一个丑陋的行,它也应该是可读的所以我会坚持下去if(test.level1 && test.level1.level2 && test.level1.level2.level3)
2021-02-26 00:52:21
除非我遗漏了什么,否则这不适用于可能为假的布尔最终属性......遗憾的是。否则我喜欢这个习语。
2021-02-27 00:52:21

更新

看起来 lodash已经 _.get为您所有的嵌套属性获取需求添加了。

_.get(countries, 'greece.sparta.playwright')

https://lodash.com/docs#get


上一个答案

lodash用户可能会喜欢lodash.contrib,它有几种方法可以缓解这个问题

获取路径

签名: _.getPath(obj:Object, ks:String|Array)

根据给定的键描述的路径获取嵌套对象中任何深度的值。键可以作为数组或以点分隔的字符串形式给出。undefined如果无法到达路径,则返回

var countries = {
        greece: {
            athens: {
                playwright:  "Sophocles"
            }
        }
    }
};

_.getPath(countries, "greece.athens.playwright");
// => "Sophocles"

_.getPath(countries, "greece.sparta.playwright");
// => undefined

_.getPath(countries, ["greece", "athens", "playwright"]);
// => "Sophocles"

_.getPath(countries, ["greece", "sparta", "playwright"]);
// => undefined
@MatthewPayne 也许会很好,但确实没有必要。你可以很容易地自己做function isPathDefined(object, path) { return typeof _.getPath(object, path) !== 'undefined'; }
2021-02-07 00:52:21
如果您需要确定多个不同的路径,请考虑: var url = _.get(e, 'currentTarget.myurl', null) || _.get(e, 'currentTarget.attributes.myurl.nodeValue', null) || null
2021-02-13 00:52:21
更好的是 _.result
2021-02-21 00:52:21
Lodash 本身也有同样的功能: _.get(countries, 'greece.sparta.playwright', 'default'); // → 'default' _.has(countries, 'greece.spart.playwright') // → false
2021-02-26 00:52:21
Lodash 确实需要一个 _.isPathDefined(obj, pathString) 方法。
2021-03-08 00:52:21

我已经对针对这个问题提出的一些建议进行了性能测试(感谢cdMinix添加 lodash),结果如下所示。

免责声明 #1将字符串转换为引用是不必要的元编程,最好避免。一开始不要忘记您的参考文献。从这个对类似问题的回答中阅读更多内容

免责声明 #2我们在这里讨论的是每毫秒数百万次操作。在大多数用例中,这些中的任何一个都不太可能产生太大的不同。了解每种方法的局限性,选择最有意义的方法。对我来说reduce,为了方便,我会选择类似的东西

Object Wrap(由 Oliver Steele 提供) – 34 % – 最快

var r1 = (((test || {}).level1 || {}).level2 || {}).level3;
var r2 = (((test || {}).level1 || {}).level2 || {}).foo;

原始解决方案(有问题的建议) – 45%

var r1 = test.level1 && test.level1.level2 && test.level1.level2.level3;
var r2 = test.level1 && test.level1.level2 && test.level1.level2.foo;

checkNested – 50%

function checkNested(obj) {
  for (var i = 1; i < arguments.length; i++) {
    if (!obj.hasOwnProperty(arguments[i])) {
      return false;
    }
    obj = obj[arguments[i]];
  }
  return true;
}

get_if_exist – 52%

function get_if_exist(str) {
    try { return eval(str) }
    catch(e) { return undefined }
}

有效链– 54%

function validChain( object, ...keys ) {
    return keys.reduce( ( a, b ) => ( a || { } )[ b ], object ) !== undefined;
}

objHasKeys – 63%

function objHasKeys(obj, keys) {
  var next = keys.shift();
  return obj[next] && (! keys.length || objHasKeys(obj[next], keys));
}

NestedPropertyExists – 69%

function nestedPropertyExists(obj, props) {
    var prop = props.shift();
    return prop === undefined ? true : obj.hasOwnProperty(prop) ? nestedPropertyExists(obj[prop], props) : false;
}

_.get – 72%

深度测试– 86%

function deeptest(target, s){
    s= s.split('.')
    var obj= target[s.shift()];
    while(obj && s.length) obj= obj[s.shift()];
    return obj;
}

悲伤的小丑– 100% – 最慢

var o = function(obj) { return obj || {} };

var r1 = o(o(o(o(test).level1).level2).level3);
var r2 = o(o(o(o(test).level1).level2).foo);
lodash_.get()怎么与这些答案相比,它的性能如何?
2021-02-12 00:52:21
根据情况,这些方法中的每种方法都比其他方法慢或快。如果找到所有键,则“Object Wrap”可能最快,但如果找不到其中一个键,则“本机解决方案/原始解决方案”可能更快。
2021-02-16 00:52:21
应该注意的是,测试的百分比越多 - 它就越慢
2021-02-20 00:52:21
与它相比性能如何 try { test.level1.level2.level3 } catch (e) { // some logger e }
2021-03-03 00:52:21
@evilReiko 如果没有找到任何键,任何方法都会变慢,但彼此成比例,它仍然几乎相同。然而,你是对的——这更像是一种智力锻炼。我们在这里谈论的是每毫秒一百万次迭代。我看不到它会产生很大不同的用例。就我个人而言,我会为了reducetry/catch为了方便而去
2021-03-08 00:52:21

您可以在任何深度阅读的对象属性,如果你处理的名称,如字符串:'t.level1.level2.level3'

window.t={level1:{level2:{level3: 'level3'}}};

function deeptest(s){
    s= s.split('.')
    var obj= window[s.shift()];
    while(obj && s.length) obj= obj[s.shift()];
    return obj;
}

alert(deeptest('t.level1.level2.level3') || 'Undefined');

undefined如果任何段是,则返回undefined

值得注意的是,这种方法的性能非常好,至少在 Chrome 中,在某些情况下,性能优于所选答案的 @Cludiu 修改版。在这里查看性能测试:jsperf.com/check-if-deep-property-exists-with-willnotthrow
2021-02-19 00:52:21