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

Test for existence of nested JavaScript object key

提问人:user113716 提问时间:4/13/2010 最后编辑:Bergiuser113716 更新时间:1/14/2022 访问量:402824

问:

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

var test = {};

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

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

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

alert(test.level1);产生,但失败。undefinedalert(test.level1.level2.level3);

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

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

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

嵌套的 JavaScript 对象 属性

评论

1赞 Anurag 4/14/2010
您可能想检查最近 stackoverflow.com/questions/2525943/ 提出的一个切线相关问题......
0赞 James McMahon 12/21/2012
另请参阅 stackoverflow.com/questions/10918488/...
0赞 9/7/2013
那里有几个命题:stackoverflow.com/a/18381564/1636522
0赞 GibboK 7/11/2015
如果 level3 属性是 false,您当前的方法存在潜在问题,在这种情况下,即使该属性存在,也会 retur nfalse 看看这个例子,请 jsfiddle.net/maz9bLjx
12赞 Raghavendra 8/12/2015
简单地说,您也可以使用 Try Catch

答:

21赞 user187291 4/14/2010 #1

怎么样

try {
   alert(test.level1.level2.level3)
} catch(e) {
 ...whatever

}

评论

16赞 Sam Dutton 11/9/2010
我不认为 try/catch 是测试对象是否存在的好方法:try/catch 旨在处理异常,而不是像这里的测试这样的正常情况。我认为每一步(typeof foo == “undefined”)更好 - 一般来说,如果您使用这种深度嵌套的属性,可能需要进行一些重构。此外,如果抛出异常,try/catch 将导致 Firebug(以及任何开启了 break-on-error 的浏览器)中断。
0赞 9/2/2013
我对此进行了投票,因为如果您使用其他解决方案,浏览器将检查是否存在两次。假设您要调用 'a.c.b = 2'。浏览器在修改值之前必须检查是否存在(否则将是操作系统捕获的内存错误)。
4赞 9/2/2013
问题仍然存在:女巫一浏览器设置尝试捕获或调用 n 次的速度更快?hasOwnProperty()
15赞 Austin Pray 6/5/2014
为什么这又很糟糕?这在我看来是最干净的。
0赞 robsch 8/9/2016
我会说:如果你期望该属性存在,那么可以将其包装成一个 try 块。如果它不存在,那就是一个错误。但是,如果您只是懒惰并将常规代码放入 catch 块中,以防属性不存在,则 try/catch 被滥用了。这里需要 if/else 或类似的东西。
725赞 Christian C. Salvadó 4/14/2010 #2

如果你不想要一个,你必须一步一步地去做,因为如果其中一个成员是 或 ,并且你尝试访问一个成员,就会抛出一个异常。TypeErrornullundefined

你可以简单地选择异常,也可以创建一个函数来测试多个级别的存在,如下所示: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:

可选链接提案ECMAScript 委员会进程中达到了第 3 阶段,这将允许您通过使用令牌安全地访问深度嵌套的属性,即新的可选链接运算符?.

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

如果访问的任何级别是 或表达式将自行解析。nullundefinedundefined

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

obj?.level1?.method();

上面的表达式将生成 if , , , or are 或 ,否则它将调用该函数。undefinedobjobj.level1obj.level1.methodnullundefined

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

Babel 7.8.0 开始,默认支持 ES2020

在 Babel REPL 上查看此示例

🎉🎉更新时间:2019 🎉🎉 年 12 月

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

评论

4赞 deefour 11/10/2012
arguments实际上不是一个数组。 将其转换为正式数组。学习Array.prototype.slice.call(arguments)
24赞 Claudiu 11/1/2013
这样做和开始会更有效率,而不是复制对象var obj = arguments[0];var i = 1arguments
2赞 netpoetica 11/27/2014
出于紧缩的考虑,我用 try/catch 整理了一个版本,这并不奇怪 - 性能很糟糕(出于某种原因在 Safari 中除外)。下面有一些非常高性能的答案,以及 Claudiu 的修改,它也比选定的答案性能高得多。在此处查看 jsperf jsperf.com/check-if-deep-property-exists-with-willnotthrow
3赞 Vernon 12/28/2016
在 ES6 中,变量声明可以被删除,并且可以用作该方法的第二个参数。developer.mozilla.org/en/docs/Web/JavaScript/Reference/......args...argscheckNested
10赞 Drenai 1/14/2018
这是一个非常难以维护的。如果任何属性键发生变化(它们会改变),项目上的所有开发人员都必须对整个代码库进行“字符串搜索”。这并不是解决问题的真正方法,因为它引入了一个更大的问题
46赞 kennebec 4/14/2010 #3

您可以读取任何深度的对象属性,前提是像处理字符串一样处理名称:。'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');

如果任何段是 ,则返回 。undefinedundefined

评论

3赞 netpoetica 11/27/2014
值得注意的是,这种方法非常有效,至少在 Chrome 中是这样,在某些情况下优于所选答案@Claudiu修改版本。在此处查看性能测试:jsperf.com/check-if-deep-property-exists-with-willnotthrow
381赞 Gabe Moothart 10/27/2010 #4

这是我从奥利弗·斯蒂尔(Oliver Steele)那里学到的一个模式:

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

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

评论

8赞 Gabe Moothart 9/6/2013
@wared 我认为它之所以有趣,主要是因为它的简洁性。链接的帖子中详细讨论了性能特征。是的,它总是执行所有测试,但它避免了创建临时变量,如果你想防止每次创建新的空对象的开销,你可以将别名 {} 别名化为 var。在 99% 的情况下,我不认为速度很重要,而在速度很重要的情况下,分析是无可替代的。
9赞 Joshua Taylor 4/29/2015
@MuhammadUmer 不,关键是,如果测试未定义,那么你正在做.当然,是未定义的,所以这意味着你正在做,依此类推。(test || {})({}.level1 || {}){}.level1{}.level2
3赞 1/3/2016
@JoshuaTaylor:我认为他的意思是,如果没有声明,就会有一个 ReferenceError,但这不是问题,因为如果没有声明,就会有一个错误需要修复,所以错误是一件好事。test
44赞 Sharky 9/30/2016
你说一旦你习惯了它,就不读了”。好吧,这些迹象表明你已经知道这是一团糟。那为什么要建议这个解决方案呢?它容易出现错别字,并且完全没有可读性。看看就知道了!如果我必须写一行丑陋的行,它应该是可读的;所以我会坚持下去if(test.level1 && test.level1.level2 && test.level1.level2.level3)
11赞 T3db0t 11/18/2016
除非我遗漏了一些东西,否则这不适用于可能为假的布尔结束属性......可悲的是。否则我喜欢这个成语。
5赞 jfriend00 10/12/2011 #5

一个简单的方法是:

try {
    alert(test.level1.level2.level3);
} catch(e) {
    alert("undefined");    // this is optional to put any output here
}

捕获未定义任何更高级别的对象(如 test、test.level1、test.level1.level2)的情况。try/catch

4赞 mikemaccana 5/21/2012 #6

一个更短的 ES5 版本,@CMS的出色答案:

// Check the obj has the keys in the order mentioned. Used for checking JSON results.  
var checkObjHasKeys = function(obj, keys) {
  var success = true;
  keys.forEach( function(key) {
    if ( ! obj.hasOwnProperty(key)) {
      success = false;
    }
    obj = obj[key];
  })
  return success;
}

通过类似的测试:

var test = { level1:{level2:{level3:'result'}}};
utils.checkObjHasKeys(test, ['level1', 'level2', 'level3']); // true
utils.checkObjHasKeys(test, ['level1', 'level2', 'foo']); // false

评论

0赞 JKS 7/20/2012
唯一的问题是,如果存在多个级别的未定义键,则会出现 TypeError,例如checkObjHasKeys(test, ['level1', 'level2', 'asdf', 'asdf']);
1赞 RobG 8/12/2015
更合适的方法是 every,其值可以直接返回。
0赞 Wade 11/5/2016
也许改成.一旦你知道它破裂了,你就应该纾困,一旦它是空的或未定义的,就不能再存在更深层次的东西了。这将防止更深的嵌套项上出现错误,因为它们显然也不存在。success = false;return false
3赞 Anand Sunderraman 6/22/2013 #7

CMS给出的答案也可以通过以下修改来进行null检查

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

            for (var i = 0; i < args.length; i++) 
            {
                if (obj == null || !obj.hasOwnProperty(args[i]) ) 
                {
                    return false;
                }
                obj = obj[args[i]];
            }
            return true;
    }
3赞 user1636522 9/7/2013 #8

这个答案开始,详细阐述了以下选项。两者的同一棵树:

var o = { a: { b: { c: 1 } } };

未定义时停止搜索

var u = undefined;
o.a ? o.a.b ? o.a.b.c : u : u // 1
o.x ? o.x.y ? o.x.y.z : u : u // undefined
(o = o.a) ? (o = o.b) ? o.c : u : u // 1

逐一确保每个级别

var $ = function (empty) {
    return function (node) {
        return node || empty;
    };
}({});

$($(o.a).b).c // 1
$($(o.x).y).z // undefined
11赞 jrode 11/8/2013 #9

我尝试了一种递归方法:

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

踢出递归,因此它不会在没有键的情况下运行函数进行测试。测试:! keys.length ||

obj = {
  path: {
    to: {
      the: {
        goodKey: "hello"
      }
    }
  }
}

console.log(objHasKeys(obj, ['path', 'to', 'the', 'goodKey'])); // true
console.log(objHasKeys(obj, ['path', 'to', 'the', 'badKey']));  // undefined

我正在使用它来打印一堆具有未知键/值的对象的友好 html 视图,例如:

var biosName = objHasKeys(myObj, 'MachineInfo:BiosInfo:Name'.split(':'))
             ? myObj.MachineInfo.BiosInfo.Name
             : 'unknown';
290赞 Austin Pray 6/5/2014 #10

更新

看起来 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

评论

0赞 Matthew Payne 1/10/2015
Lodash 确实需要一个 _.isPathDefined(obj, pathString) 方法。
0赞 Vala 5/12/2015
@MatthewPayne也许会很好,但真的没有必要。你可以很容易地自己做function isPathDefined(object, path) { return typeof _.getPath(object, path) !== 'undefined'; }
13赞 Tom 5/16/2015
Lodash 本身具有相同的功能:_.get(countries, 'greece.sparta.playwright', 'default'); // → 'default' _.has(countries, 'greece.spart.playwright') // → false
0赞 Shishir Arora 8/29/2015
更好的是 _.result
0赞 Simon Hutchison 5/4/2017
如果需要确定多个不同的路径,请考虑:var url = _.get(e, 'currentTarget.myurl', null) || _.get(e, 'currentTarget.attributes.myurl.nodeValue', null) || null
28赞 Gajus 11/18/2014 #11
var a;

a = {
    b: {
        c: 'd'
    }
};

function isset (fn) {
    var value;
    try {
        value = fn();
    } catch (e) {
        value = undefined;
    } finally {
        return value !== undefined;
    }
};

// ES5
console.log(
    isset(function () { return a.b.c; }),
    isset(function () { return a.b.c.d.e.f; })
);

如果您在 ES6 环境中编码(或使用 6to5),那么您可以利用箭头函数语法:

// ES6 using the arrow function
console.log(
    isset(() => a.b.c),
    isset(() => a.b.c.d.e.f)
);

关于性能,如果设置了该属性,则使用 block 不会对性能造成损害。如果未设置该属性,则会影响性能。try..catch

考虑简单地使用 _.has

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

_.has(object, 'a');
// → true

_.has(object, 'a.b.c');
// → true

_.has(object, ['a', 'b', 'c']);
// → true

评论

2赞 yangmillstheory 1/31/2016
我认为这种方法是最好的答案。查询对象的类型与假设 API 存在以及如果不存在则相应地失败之间存在哲学上的区别。后者更适合于松散类型的语言。请参见 stackoverflow.com/a/408305/2419669。这种方法也比 .try-catchtry-catchif (foo && foo.bar && foo.bar.baz && foo.bar.baz.qux) { ... }
3赞 Brian Sidebotham 3/24/2015 #12

我知道这个问题很老,但我想通过将其添加到所有对象来提供扩展。我知道人们倾向于不赞成将 Object 原型用于扩展对象功能,但我发现没有什么比这样做更容易的了。此外,现在允许使用 Object.defineProperty 方法。

Object.defineProperty( Object.prototype, "has", { value: function( needle ) {
    var obj = this;
    var needles = needle.split( "." );
    for( var i = 0; i<needles.length; i++ ) {
        if( !obj.hasOwnProperty(needles[i])) {
            return false;
        }
        obj = obj[needles[i]];
    }
    return true;
}});

现在,为了测试任何对象中的任何属性,您可以简单地执行以下操作:

if( obj.has("some.deep.nested.object.somewhere") )

这里有一个 jsfiddle 来测试它,特别是它包含一些 jQuery,如果你直接修改 Object.prototype,因为该属性变得可枚举,它就会中断。这应该适用于第三方库。

3赞 Julius Musseau 7/17/2015 #13

我认为这是一个轻微的改进(成为 1 行):

   alert( test.level1 && test.level1.level2 && test.level1.level2.level3 )

这是有效的,因为&&运算符返回它计算的最终操作数(并且短路)。

评论

0赞 Sean Kendle 6/10/2020
你从字面上复制了他们通常说的,并想避免......
4赞 Noah Stahl 7/25/2015 #14

如果属性存在,我一直在寻找要返回的值,因此我修改了上面CMS的答案。这是我想出的:

function getNestedProperty(obj, key) {
  // Get property array from key string
  var properties = key.split(".");

  // Iterate through properties, returning undefined if object is null or property doesn't exist
  for (var i = 0; i < properties.length; i++) {
    if (!obj || !obj.hasOwnProperty(properties[i])) {
      return;
    }
    obj = obj[properties[i]];
  }

  // Nested property found, so return the value
  return obj;
}


Usage:

getNestedProperty(test, "level1.level2.level3") // "level3"
getNestedProperty(test, "level1.level2.foo") // undefined

3赞 Mike D 11/10/2015 #15

这是我的看法 - 这些解决方案中的大多数都忽略了嵌套数组的情况,如下所示:

    obj = {
        "l1":"something",
        "l2":[{k:0},{k:1}],
        "l3":{
            "subL":"hello"
        }
    }

我可能想检查一下obj.l2[0].k

使用以下功能,您可以做到deeptest('l2[0].k',obj)

如果对象存在,则该函数将返回 true,否则返回 false

function deeptest(keyPath, testObj) {
    var obj;

    keyPath = keyPath.split('.')
    var cKey = keyPath.shift();

    function get(pObj, pKey) {
        var bracketStart, bracketEnd, o;

        bracketStart = pKey.indexOf("[");
        if (bracketStart > -1) { //check for nested arrays
            bracketEnd = pKey.indexOf("]");
            var arrIndex = pKey.substr(bracketStart + 1, bracketEnd - bracketStart - 1);
            pKey = pKey.substr(0, bracketStart);
			var n = pObj[pKey];
            o = n? n[arrIndex] : undefined;

        } else {
            o = pObj[pKey];
        }
        return o;
    }

    obj = get(testObj, cKey);
    while (obj && keyPath.length) {
        obj = get(obj, keyPath.shift());
    }
    return typeof(obj) !== 'undefined';
}

var obj = {
    "l1":"level1",
    "arr1":[
        {"k":0},
        {"k":1},
        {"k":2}
    ],
    "sub": {
       	"a":"letter A",
        "b":"letter B"
    }
};
console.log("l1: " + deeptest("l1",obj));
console.log("arr1[0]: " + deeptest("arr1[0]",obj));
console.log("arr1[1].k: " + deeptest("arr1[1].k",obj));
console.log("arr1[1].j: " + deeptest("arr1[1].j",obj));
console.log("arr1[3]: " + deeptest("arr1[3]",obj));
console.log("arr2: " + deeptest("arr2",obj));

3赞 adutu 1/30/2016 #16

这适用于所有对象和数组:)

前任:

if( obj._has( "something.['deep']['under'][1][0].item" ) ) {
    //do something
}

这是我对布莱恩答案的改进版本

我使用 _has 作为属性名称,因为它可能与现有的 has 属性(例如:地图)冲突

Object.defineProperty( Object.prototype, "_has", { value: function( needle ) {
var obj = this;
var needles = needle.split( "." );
var needles_full=[];
var needles_square;
for( var i = 0; i<needles.length; i++ ) {
    needles_square = needles[i].split( "[" );
    if(needles_square.length>1){
        for( var j = 0; j<needles_square.length; j++ ) {
            if(needles_square[j].length){
                needles_full.push(needles_square[j]);
            }
        }
    }else{
        needles_full.push(needles[i]);
    }
}
for( var i = 0; i<needles_full.length; i++ ) {
    var res = needles_full[i].match(/^((\d+)|"(.+)"|'(.+)')\]$/);
    if (res != null) {
        for (var j = 0; j < res.length; j++) {
            if (res[j] != undefined) {
                needles_full[i] = res[j];
            }
        }
    }

    if( typeof obj[needles_full[i]]=='undefined') {
        return false;
    }
    obj = obj[needles_full[i]];
}
return true;
}});

这是小提琴

10赞 VeganHunter 2/3/2016 #17

我认为以下脚本提供了更具可读性的表示。

声明一个函数:

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

然后像这样使用它:

if (o(o(o(o(test).level1).level2).level3)
{

}

我称它为“悲伤的小丑技术”,因为它使用符号 o(


编辑:

这是 TypeScript 的一个版本

它在编译时提供类型检查(如果使用 Visual Studio 等工具,则提供智能感知)

export function o<T>(someObject: T, defaultValue: T = {} as T) : T {
    if (typeof someObject === 'undefined' || someObject === null)
        return defaultValue;
    else
        return someObject;
}

用法是一样的:

o(o(o(o(test).level1).level2).level3

但这次 IntelliSense 有效了!

另外,您可以设置默认值:

o(o(o(o(o(test).level1).level2).level3, "none")

评论

2赞 Daniel W. 11/17/2016
°0o <°(())))><
2赞 1/8/2019
我喜欢这个,因为它是诚实的,当你不知道自己的类型时,它会在你的脸上抛出一个“未定义”。+1.Object
2赞 Sventies 1/22/2019
只要你把这句话保持在parens中,你也可以称之为快乐小丑技巧(o
0赞 VeganHunter 1/23/2019
谢谢Sventies。我喜欢你的评论.这是一个相当不错的角度 - 这种条件主要用于“ifs”,并且总是用外部括号包围。所以,是的,它确实是一个快乐的小丑:)))
0赞 Bastien7 2/10/2020
你真的需要爱上括号才能选择这个......
6赞 Alex Moldovan 8/15/2016 #18

基于这个答案,我想出了这个通用函数,用它来解决问题ES2015

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

var test = {
  first: {
    second: {
        third: "This is not the key your are looking for"
    }
  }
}

if ( validChain( test, "first", "second", "third" ) ) {
    console.log( test.first.second.third );
}

评论

2赞 James Harrington 11/11/2017
这是我的最后一种方法function validChain (object, path) { return path.split('.').reduce((a, b) => (a || { })[b], object) !== undefined }
235赞 unitario 1/8/2017 #19

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

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

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

对象包装(作者: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;

check嵌套 – 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);

评论

19赞 avalanche1 2/3/2017
应该注意的是,测试的百分比越多 - 速度越慢
2赞 beniutek 5/31/2017
洛达什呢?与这些答案相比,它的性能如何?_.get()
1赞 evilReiko 8/28/2017
根据情况,这些方法中的每种方法都比其他方法慢或快。如果找到所有键,则“对象换行”可能是最快的,但如果找不到其中一个键,则“本机解决方案/原始解决方案”可能会更快。
1赞 unitario 8/30/2017
@evilReiko 如果找不到键,任何方法都会变慢,但彼此成比例,它仍然几乎相同。然而,你是对的——这更像是一种智力锻炼,而不是其他任何事情。我们在这里谈论的是每毫秒一百万次迭代。我看不出它会有太大区别的用例。就我个人而言,我会为了方便或出于方便而去。reducetry/catch
0赞 Lex 11/2/2018
与相比,它的性能如何try { test.level1.level2.level3 } catch (e) { // some logger e }
3赞 Egor Stambakio 5/1/2017 #20

现在,我们还可以使用 to 循环访问嵌套键:reduce

// @params o<object>
// @params path<string> expects 'obj.prop1.prop2.prop3'
// returns: obj[path] value or 'false' if prop doesn't exist

const objPropIfExists = o => path => {
  const levels = path.split('.');
  const res = (levels.length > 0) 
    ? levels.reduce((a, c) => a[c] || 0, o)
    : o[path];
  return (!!res) ? res : false
}

const obj = {
  name: 'Name',
  sys: { country: 'AU' },
  main: { temp: '34', temp_min: '13' },
  visibility: '35%'
}

const exists = objPropIfExists(obj)('main.temp')
const doesntExist = objPropIfExists(obj)('main.temp.foo.bar.baz')

console.log(exists, doesntExist)

8赞 Endless 5/18/2017 #21

我没有看到任何有人使用代理的例子

所以我想出了自己的。 它的好处是你不必插值字符串。你实际上可以返回一个可链接的对象函数,并用它做一些神奇的事情。您甚至可以调用函数并获取数组索引来检查深层对象

function resolve(target) {
  var noop = () => {} // We us a noop function so we can call methods also
  return new Proxy(noop, {
    get(noop, key) {
      // return end result if key is _result
      return key === '_result' 
        ? target 
        : resolve( // resolve with target value or undefined
            target === undefined ? undefined : target[key]
          )
    },

    // if we want to test a function then we can do so alos thanks to using noop
    // instead of using target in our proxy
    apply(noop, that, args) {
      return resolve(typeof target === 'function' ? target.apply(that, args) : undefined)
    },
  })
}

// some modified examples from the accepted answer
var test = {level1: {level2:() => ({level3:'level3'})}}
var test1 = {key1: {key2: ['item0']}}

// You need to get _result in the end to get the final result

console.log(resolve(test).level1.level2().level3._result)
console.log(resolve(test).level1.level2().level3.level4.level5._result)
console.log(resolve(test1).key1.key2[0]._result)
console.log(resolve(test1)[0].key._result) // don't exist

上面的代码适用于同步的东西。但是,您将如何测试像此 ajax 调用这样的异步内容? 你如何测试它?

fetch('https://httpbin.org/get')
.then(function(response) {
  return response.json()
})
.then(function(json) {
  console.log(json.headers['User-Agent'])
})

当然,您可以使用 async/await 来摆脱一些回调。但是,如果你能更神奇地做到这一点呢?看起来像这样的东西:

fetch('https://httpbin.org/get').json().headers['User-Agent']

你可能想知道所有的承诺和链条在哪里......这可能是你所知道的一切的障碍......但是使用相同的代理技术,您实际上可以测试深度嵌套的复杂路径,而无需编写单个函数.then

function resolve(target) { 
  return new Proxy(() => {}, {
    get(noop, key) {
      return key === 'then' ? target.then.bind(target) : resolve(
        Promise.resolve(target).then(target => {
          if (typeof target[key] === 'function') return target[key].bind(target)
          return target[key]
        })
      )
    },

    apply(noop, that, args) {
      return resolve(target.then(result => {
        return result.apply(that, args)
      }))
    },
  })
}

// this feels very much synchronous but are still non blocking :)
resolve(window) // this will chain a noop function until you call then()
  .fetch('https://httpbin.org/get')
  .json()
  .headers['User-Agent']
  .then(console.log, console.warn) // you get a warning if it doesn't exist
  
// You could use this method also for the first test object
// also, but it would have to call .then() in the end



// Another example
resolve(window)
  .fetch('https://httpbin.org/get?items=4&items=2')
  .json()
  .args
  .items
  // nice that you can map an array item without even having it ready
  .map(n => ~~n * 4) 
  .then(console.log, console.warn) // you get a warning if it doesn't exist

评论

0赞 Endless 9/4/2017
如果有人感兴趣,我已经在 npm 上发布了异步版本
3赞 Ankit Arya 5/10/2018 #22

您可以使用递归函数来执行此操作。即使您不知道所有嵌套的对象键名称,这也将起作用。

function FetchKeys(obj) {
    let objKeys = [];
    let keyValues = Object.entries(obj);
    for (let i in keyValues) {
        objKeys.push(keyValues[i][0]);
        if (typeof keyValues[i][1] == "object") {
            var keys = FetchKeys(keyValues[i][1])
            objKeys = objKeys.concat(keys);
        }
    }
    return objKeys;
}

let test = { level1: { level2: { level3: "level3" } } };
let keyToCheck = "level2";
let keys = FetchKeys(test); //Will return an array of Keys

if (keys.indexOf(keyToCheck) != -1) {
    //Key Exists logic;
}
else {
    //Key Not Found logic;
}
20赞 Frank N 5/31/2018 #23

ES6 答案,经过全面测试:)

const propExists = (obj, path) => {
    return !!path.split('.').reduce((obj, prop) => {
        return obj && obj[prop] ? obj[prop] : undefined;
    }, obj)
}

→请参阅具有完整测试覆盖率的 Codepen

评论

1赞 germain 10/2/2018
我使您的测试失败,将平面道具的值设置为 0。你必须关心类型强制。
0赞 Frank N 10/2/2018
@germain 这对你有用吗?(我明确比较了不同的 falsys,并添加了测试。如果您有更好的主意,请告诉我)。===
0赞 germain 10/2/2018
我让你的测试再次失败,将扁平道具的值设置为 .然后你可能希望在你的对象中设置一个值(我知道这很奇怪,但它是 JS)。我将一个正假值设置为:falseundefined'Prop not Found'const hasTruthyProp = prop => prop === 'Prop not found' ? false : true const path = obj => path => path.reduce((obj, prop) => { return obj && obj.hasOwnProperty(prop) ? obj[prop] : 'Prop not found' }, obj) const myFunc = compose(hasTruthyProp, path(obj))
0赞 Frank N 10/4/2018
你能分叉我的代码笔(右上角,简单),更正和添加测试,并将你的URL发送给我吗?谢谢 =)
1赞 Regular Jo 6/11/2021
三年后不编辑是不负责任的。正如@germain所指出的那样,这很容易在虚假值上失败。如果对此进行了彻底测试,则会有一些错误的代码。
23赞 Goran.it 6/14/2018 #24

您也可以将 tc39 可选链接提案与 babel 7 一起使用 - tc39-proposal-optional-chaining

代码如下所示:

  const test = test?.level1?.level2?.level3;
  if (test) alert(test);

评论

0赞 jhpratt 9/24/2018
请注意,这种语法几乎肯定会改变,因为一些 TC39 成员有反对意见。
0赞 Goran.it 9/24/2018
可能,但这会及时以某种形式提供,这是唯一重要的事情......这是我最想念的 JS 功能之一。
0赞 jave.web 10/3/2022
语法没有改变,并且完全支持所有主要浏览器最新版本 - developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/...
6赞 V. Sambor 6/30/2018 #25

我创建了一个小函数来安全地获取嵌套对象属性。

function getValue(object, path, fallback, fallbackOnFalsy) {
    if (!object || !path) {
        return fallback;
    }

    // Reduces object properties to the deepest property in the path argument.
    return path.split('.').reduce((object, property) => {
       if (object && typeof object !== 'string' && object.hasOwnProperty(property)) {
            // The property is found but it may be falsy.
            // If fallback is active for falsy values, the fallback is returned, otherwise the property value.
            return !object[property] && fallbackOnFalsy ? fallback : object[property];
        } else {
            // Returns the fallback if current chain link does not exist or it does not contain the property.
            return fallback;
        }
    }, object);
}

或者一个更简单但有点不可读的版本:

function getValue(o, path, fb, fbFalsy) {
   if(!o || !path) return fb;
   return path.split('.').reduce((o, p) => o && typeof o !== 'string' && o.hasOwnProperty(p) ? !o[p] && fbFalsy ? fb : o[p] : fb, o);
}

甚至更短,但没有回退假旗:

function getValue(o, path, fb) {
   if(!o || !path) return fb;
   return path.split('.').reduce((o, p) => o && typeof o !== 'string' && o.hasOwnProperty(p) ? o[p] : fb, o);
}

我有测试:

const obj = {
    c: {
        a: 2,
        b: {
            c: [1, 2, 3, {a: 15, b: 10}, 15]
        },
        c: undefined,
        d: null
    },
    d: ''
}

以下是一些测试:

// null
console.log(getValue(obj, 'c.d', 'fallback'));

// array
console.log(getValue(obj, 'c.b.c', 'fallback'));

// array index 2
console.log(getValue(obj, 'c.b.c.2', 'fallback'));

// no index => fallback
console.log(getValue(obj, 'c.b.c.10', 'fallback'));

要查看所有带有文档的代码和我尝试过的测试,您可以查看我的 github 要点: https://gist.github.com/vsambor/3df9ad75ff3de489bbcb7b8c60beebf4#file-javascriptgetnestedvalues-js

3赞 estani 7/3/2019 #26

还有另一个非常紧凑的:

function ifSet(object, path) {
  return path.split('.').reduce((obj, part) => obj && obj[part], object)
}

叫:

let a = {b:{c:{d:{e:'found!'}}}}
ifSet(a, 'b.c.d.e') == 'found!'
ifSet(a, 'a.a.a.a.a.a') == undefined

它不会表现得很好,因为它正在拆分一个字符串(但增加了调用的可读性)并遍历所有内容,即使已经很明显什么都找不到(但增加了函数本身的可读性)。

至少比 http://jsben.ch/aAtmc_.get

9赞 kelvin kantaria 12/23/2019 #27

创建全局并在整个项目中使用function

试试这个

function isExist(arg){
   try{
      return arg();
   }catch(e){
      return false;
   }
}

let obj={a:5,b:{c:5}};

console.log(isExist(()=>obj.b.c))
console.log(isExist(()=>obj.b.foo))
console.log(isExist(()=>obj.test.foo))

if 条件

if(isExist(()=>obj.test.foo)){
   ....
}

评论

0赞 Clashsoft 12/9/2021
这将吞下可能发生的任何其他错误。
5赞 lahiru dilshan 10/7/2020 #28

我已经使用这个函数来访问深度嵌套对象的属性,它对我有用......

这是函数

/**
 * get property of object
 * @param obj object
 * @param path e.g user.name
 */
getProperty(obj, path, defaultValue = '-') {
  const value = path.split('.').reduce((o, p) => o && o[p], obj);

  return value ? value : defaultValue;
}

这就是我访问深度嵌套对象属性的方式

{{ getProperty(object, 'passengerDetails.data.driverInfo.currentVehicle.vehicleType') }}
19赞 Daniel Barral 9/12/2021 #29

这个问题很老了。今天,您可以使用可选链接 (?.)

let value = test?.level1?.level2?.level3;

源:

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining

评论

0赞 Leandro Bardelli 7/21/2022
这不是被问到的。
6赞 David 1/13/2022 #30

您可以尝试可选链接(但要注意浏览器兼容性)。

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

let level3 = test?.level1?.level2?.level3;
console.log(level3); // level3

level3 = test?.level0?.level1?.level2?.level3;
console.log(level3); // undefined

有一个 babel 插件(@babel/plugin-proposal-optional-chaining)用于选择性切换。所以,如有必要,请升级你的通天塔。