如果我有对象的引用:

var test = {};

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

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

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

警报(测试级别1);生成未定义,但警告(test.level1.level2.level3);失败。

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

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

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


当前回答

这是我使用的一个小助手函数,对我来说,它非常简单明了。希望这对一些人有帮助:)。

static issetFromIndices(param, indices, throwException = false) {
    var temp = param;

    try {
        if (!param) {
            throw "Parameter is null.";
        }

        if(!Array.isArray(indices)) {
            throw "Indices parameter must be an array.";
        }

        for (var i = 0; i < indices.length; i++) {
            var index = indices[i];
            if (typeof temp[index] === "undefined") {
                throw "'" + index + "' index is undefined.";
            }


            temp = temp[index];
        }
    } catch (e) {
        if (throwException) {
            throw new Error(e);
        } else {
            return false;
        }
    }

    return temp;
}

var person = {
    hobbies: {
        guitar: {
            type: "electric"
        }
    }
};

var indices = ["hobbies", "guitar", "type"];
var throwException = true;

try {
    var hobbyGuitarType = issetFromIndices(person, indices, throwException);
    console.log("Yay, found index: " + hobbyGuitarType);
} catch(e) {
    console.log(e);
}

其他回答

/**
 * @method getValue
 * @description simplifies checking for existance and getting a deeply nested value within a ceratin context
 * @argument {string} s       string representation of the full path to the requested property 
 * @argument {object} context optional - the context to check defaults to window
 * @returns the value if valid and set, returns undefined if invalid / not available etc.
 */
var getValue = function( s, context ){
    var fn = function(){
        try{
            return eval(s);
        }catch(e){
            return undefined;
        }
    }
    return fn.call(context||window,s);
}

和用法:

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

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

免责声明#1将字符串转换为引用是不必要的元编程,可能最好避免。首先不要忘记你的推荐人。阅读类似问题的答案。免责声明#2我们在这里谈论的是每毫秒数百万次的操作。在大多数用例中,这些都不太可能产生很大的差异。了解每种方法的局限性,选择最有意义的方法。对我来说,我会采取一些类似于出于方便而减少的措施。

物体包裹(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;
}

_.获得–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);

另一个选项(接近这个答案):

function resolve(root, path){
    try {
        return (new Function(
            'root', 'return root.' + path + ';'
        ))(root);
    } catch (e) {}
}

var tree = { level1: [{ key: 'value' }] };
resolve(tree, 'level1[0].key'); // "value"
resolve(tree, 'level1[1].key'); // undefined

更多信息:https://stackoverflow.com/a/18381564/1636522

还有一个非常紧凑的:

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

它的性能不会很好,因为它拆分了一个字符串(但增加了调用的可读性),并遍历所有内容,即使已经很明显找不到任何内容(但提高了函数本身的可读性。

至少比get快http://jsben.ch/aAtmc

我想我应该再加一个我今天想到的。我对这个解决方案感到自豪的原因是它避免了许多解决方案中使用的嵌套括号,例如Object Wrap(Oliver Steele):

(在本例中,我使用下划线作为占位符变量,但任何变量名称都有效)

//“测试”对象var测试={level1:{level2:{level3:‘level3‘}}}};设_=测试;如果((_=_level1)&&(_=.level2)&&{设level3=_;//做3级的事情}

//您也可以使用“stacked”if语句。如果你的物体很深,这会有帮助。//(除最后一个大括号外,不带嵌套或大括号)设_=测试;如果(_=_.level1)如果(_=_.level2)如果(_=_.level3){设level3=_;//做3级的事情}//或者可以缩进:如果(_=_.level1)如果(_=_.level2)如果(_=_.level3){设level3=_;//做3级的事情}