如果我有对象的引用:

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);
}

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


当前回答

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

所以我想出了自己的办法。它的优点是你不必对字符串进行插值。实际上,您可以返回一个可链接的对象函数并用它做一些神奇的事情

函数解析(目标){var noop=()=>{}//我们使用了一个noop函数,因此我们也可以调用方法返回新代理(noop{获取(noop,key){//如果键为_result,则返回最终结果返回键==“_result”? 目标:resolve(//使用目标值或未定义的值进行解析目标==未定义?未定义:目标[key])},//如果我们想测试一个函数,那么由于使用noop,我们可以这么做//而不是在代理中使用target应用(noop,that,args){返回解析(typeof target==“function”?target.apply(that,args):未定义)},})}//接受答案中的一些修改示例var测试={level1:{level2:()=>({level3:'level3'})}}var test1={key1:{key2:['item0‘]}}//您需要在最后获得_result以获得最终结果console.log(解析(测试).level1.level2().level3._result)console.log(解析(测试).level1.level2().level3.level4.level5._result)console.log(解析(test1).key1.key2[0].result)console.log(resolve(test1)[0].key.result)//不存在

以上代码适用于同步内容。但是,如何测试像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']

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

函数解析(目标){返回新代理(()=>{}{获取(noop,key){return key==“then”?target.then.bind(target):解析(承诺.解决(目标).然后(目标=>{if(typeof target[key]==“function”)返回target[key].bind(target)返回目标[键]}))},应用(noop,that,args){返回解析(target.then(result=>{返回result.apply(即,args)}))},})}//这感觉非常同步,但仍然是非阻塞的:)resolve(window)//这将链接一个noop函数,直到您调用then().fetch('https://httpbin.org/get').json().headers[“用户代理”].then(console.log,console.warn)//如果它不存在,则会收到警告//也可以对第一个测试对象使用此方法//也可以,但最后必须调用.then()//另一个例子解析(窗口).fetch('https://httpbin.org/get?items=4&items=2').json().args参数.个项目//很好,您可以在没有准备好的情况下映射数组项.map(n=>~n*4).then(console.log,console.warn)//如果它不存在,则会收到警告

其他回答

今天刚刚编写了这个函数,它对嵌套对象中的属性进行了深入搜索,如果找到了,则返回该属性的值。

/**
 * Performs a deep search looking for the existence of a property in a 
 * nested object. Supports namespaced search: Passing a string with
 * a parent sub-object where the property key may exist speeds up
 * search, for instance: Say you have a nested object and you know for 
 * certain the property/literal you're looking for is within a certain
 * sub-object, you can speed the search up by passing "level2Obj.targetProp"
 * @param {object} obj Object to search
 * @param {object} key Key to search for
 * @return {*} Returns the value (if any) located at the key
 */
var getPropByKey = function( obj, key ) {
    var ret = false, ns = key.split("."),
        args = arguments,
        alen = args.length;

    // Search starting with provided namespace
    if ( ns.length > 1 ) {
        obj = (libName).getPropByKey( obj, ns[0] );
        key = ns[1];
    }

    // Look for a property in the object
    if ( key in obj ) {
        return obj[key];
    } else {
        for ( var o in obj ) {
            if ( (libName).isPlainObject( obj[o] ) ) {
                ret = (libName).getPropByKey( obj[o], key );
                if ( ret === 0 || ret === undefined || ret ) {
                    return ret;
                }
            }
        }
    }

    return false;
}

在typeScript中,您可以执行以下操作:

 if (object.prop1 && object.prop1.prop2 && object.prop1.prop2.prop3) {
    const items = object.prop1.prop2.prop3
    console.log(items);
 }

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

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

我的解决方案,我使用了很长时间(使用字符串不幸,找不到更好的)

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

// way to use
if(get_if_exist('test.level1.level2.level3')) {
    alert(test.level1.level2.level3);
}

// or simply 
alert(get_if_exist('test.level1.level2.level3'));

edit:只有当对象“test”具有全局范围/范围时,这才有效。否则您必须执行以下操作:

// i think it's the most beautiful code I have ever write :p
function get_if_exist(obj){
    return arguments.length==1 || (obj[arguments[1]] && get_if_exist.apply(this,[obj[arguments[1]]].concat([].slice.call(arguments,2))));
}

alert(get_if_exist(test,'level1','level2','level3'));

编辑最终版本以允许2种调用方法:

function get_if_exist(obj){
    var a=arguments, b=a.callee; // replace a.callee by the function name you choose because callee is depreceate, in this case : get_if_exist
    // version 1 calling the version 2
    if(a[1] && ~a[1].indexOf('.')) 
        return b.apply(this,[obj].concat(a[1].split('.')));
    // version 2
    return a.length==1 ? a[0] : (obj[a[1]] && b.apply(this,[obj[a[1]]].concat([].slice.call(a,2))));
}

// method 1
get_if_exist(test,'level1.level2.level3');


// method 2
get_if_exist(test,'level1','level2','level3');

您还可以将tc39可选链接建议与babel 7-tc39建议可选链接一起使用

代码如下所示:

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