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

Test for existence of nested JavaScript object key


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

1
var test = {};


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

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


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


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


我现在正在做这样的事情:

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


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



如果您不想要TypeError,则必须一步一步地执行此操作,因为如果其中一个成员是nullundefined,并且您尝试访问某个成员,则会抛出异常。


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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


更新2019-05-16:


这是一个较短的版本,使用ES6功能和递归(它也适用于尾部调用形式):

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



这是我从Oliver Steele那里得到的模式:

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


事实上,整篇文章讨论了如何在javascript中执行此操作。他习惯于使用上面的语法(一旦你习惯它就不难读)作为一个成语。


更新


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

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


https://lodash.com/docs#get

以前的答案


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

的getPath


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


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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



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

Disclaimer #1 Turning strings into references is unnecessary meta-programming and probably best avoided. Don't lose track of your references to begin with. Read more from this answer to a similar question.

Disclaimer #2 We are talking about millions of operations per millisecond here. It is very unlikely any of these would make much difference in most use cases. Choose whichever makes the most sense knowing the limitations of each. For me I would go with something like reduce out of convenience.


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

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


原始解决方案(建议问题) - 45%

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


checkNested - 50%

1
2
3
4
5
6
7
8
9
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%

1
2
3
4
function get_if_exist(str) {
    try { return eval(str) }
    catch(e) { return undefined }
}


有效链接 - 54%

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


objHasKeys - 63%

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


nestedPropertyExists - 69%

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


_.get - 72%


最深的 - 86%

1
2
3
4
5
6
function deeptest(target, s){
    s= s.split('.')
    var obj= target[s.shift()];
    while(obj && s.length) obj= obj[s.shift()];
    return obj;
}


悲伤的小丑 - 100% - 最慢

1
2
3
4
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);



如果您像字符串一样处理名称,则可以读取任何深度的对象属性:'t.level1.level2.level3'

1
2
3
4
5
6
7
8
9
10
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


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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),那么您可以利用箭头函数语法:

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


关于性能,如果设置了属性,则使用try..catch块不会有性能损失。如果未设置该属性,则会对性能产生影响。


考虑简单地使用_.has

1
2
3
4
5
6
7
8
9
10
var object = { 'a': { 'b': { 'c': 3 } } };

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

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

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



怎么样

1
2
3
4
5
6
try {
   alert(test.level1.level2.level3)
} catch(e) {
 ...whatever

}


ES6答案,经过彻底测试:)

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

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



你也可以使用tc39可选链接提议和babel 7 - tc39-proposal-optional-chaining


代码看起来像这样:

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



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


声明一个函数:

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


然后像这样使用它:

1
2
3
4
if (o(o(o(o(test).level1).level2).level3)
{

}


我把它称为"悲伤的小丑技术",因为它使用的是标志o(


编辑:


这是TypeScript的一个版本


它在编译时提供类型检查(如果你使用像Visual Studio这样的工具,还会提供intellisense)

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


用法是一样的:

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


但这次intellisense工作!


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

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



我尝试了一种递归方法:

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


! keys.length ||启动递归,因此它不会运行该函数而没有剩下的键进行测试。测试:

1
2
3
4
5
6
7
8
9
10
11
12
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视图,例如:

1
2
3
var biosName = objHasKeys(myObj, 'MachineInfo:BiosInfo:Name'.split(':'))
             ? myObj.MachineInfo.BiosInfo.Name
             : 'unknown';


我没有看到有人使用Proxies的例子


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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调用那样异步的东西呢?
你是如何测试的?如果响应不是json时返回500 http错误怎么办?

1
2
3
4
5
6
7
window.fetch('https://httpbin.org/get')
.then(function(response) {
  return response.json()
})
.then(function(json) {
  console.log(json.headers['User-Agent'])
})


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

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


你可能想知道所有的承诺和.then链是...这可能会阻塞所有你知道的...但是使用相同的代理技术和承诺你可以实际测试深层嵌套的复杂路径,而不需要编写单个函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
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



一个简单的方法是:

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


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



根据这个答案,我使用ES2015提出了这个通用函数,可以解决这个问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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 );
}



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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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);
}


或者更简单但稍微不可读的版本:

1
2
3
4
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);
}


或者甚至更短但没有倒退的假旗:

1
2
3
4
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);
}


我测试过:

1
2
3
4
5
6
7
8
9
10
11
const obj = {
    c: {
        a: 2,
        b: {
            c: [1, 2, 3, {a: 15, b: 10}, 15]
        },
        c: undefined,
        d: null
    },
    d: ''
}


以下是一些测试:

1
2
3
4
5
6
7
8
9
10
11
// 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



一个较短的ES5版@CMS的优秀答案:

1
2
3
4
5
6
7
8
9
10
11
// 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;
}


通过类似的测试:

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



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


例如:

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


这是我改进版的Brian的答案


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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;
}});


这是小提琴



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

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

未定义时停止搜索

1
2
3
4
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

确保每个级别逐一

1
2
3
4
5
6
7
8
var $ = function (empty) {
    return function (node) {
        return node || empty;
    };
}({});

$($(o.a).b).c // 1
$($(o.x).y).z // undefined


CMS给出的答案也适用于以下对空检查的修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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;
    }


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

1
2
3
4
5
6
7
8
9
10
11
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;
}});


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

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


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



我认为这是一个小小的改进(变成一线):

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


这是因为和&amp;&amp; operator返回它评估的最终操作数(并且它是短路的)。



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

1
2
3
4
5
6
7
    obj = {
       "l1":"something",
       "l2":[{k:0},{k:1}],
       "l3":{
           "subL":"hello"
        }
    }


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


使用下面的功能,您可以执行deeptest('l2[0].k',obj)


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
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));



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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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;
}


如果属性存在,我正在寻找要返回的值,所以我通过上面的CMS修改了答案。这是我想出的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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



现在我们也可以使用reduce来遍历嵌套键:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// @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)



这是一个在codeabode(safeRead)上的函数,它将以安全的方式执行此操作...即

1
safeRead(test, 'level1', 'level2', 'level3');


如果任何属性为null或未定义,则返回空字符串



根据以前的评论,这里是另一个版本,其中主要对象也无法定义:

1
2
// Supposing that our property is at first.second.third.property:
var property = (((typeof first !== 'undefined' ? first : {}).second || {}).third || {}).property;


我遇到了同样的问题,并希望看看我是否能想出一个自己的解决方案。这将接受您要检查为字符串的路径。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
function checkPathForTruthy(obj, path) {
  if (/\[[a-zA-Z_]/.test(path)) {
    console.log("Cannot resolve variables in property accessors");
    return false;
  }

  path = path.replace(/\[/g,".");
  path = path.replace(/]|'|"/g,"");
  path = path.split(".");

  var steps = 0;
  var lastRef = obj;
  var exists = path.every(key => {
    var currentItem = lastRef[path[steps]];
    if (currentItem) {
      lastRef = currentItem;
      steps++;
      return true;
    } else {
      return false;
    }
  });

  return exists;
}


这是一个包含一些日志记录和测试用例的片段:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
console.clear();
var testCases = [
  ["data.Messages[0].Code", true],
  ["data.Messages[1].Code", true],
  ["data.Messages[0]['Code']", true],
  ['data.Messages[0]["Code"]', true],
  ["data[Messages][0]['Code']", false],
  ["data['Messages'][0]['Code']", true]
];
var path ="data.Messages[0].Code";
var obj = {
  data: {
    Messages: [{
      Code:"0"
    }, {
      Code:"1"
    }]
  }
}

function checkPathForTruthy(obj, path) {
  if (/\[[a-zA-Z_]/.test(path)) {
    console.log("Cannot resolve variables in property accessors");
    return false;
  }

  path = path.replace(/\[/g,".");
  path = path.replace(/]|'|"/g,"");
  path = path.split(".");

  var steps = 0;
  var lastRef = obj;
  var logOutput = [];
  var exists = path.every(key => {
    var currentItem = lastRef[path[steps]];
    if (currentItem) {
      logOutput.push(currentItem);
      lastRef = currentItem;
      steps++;
      return true;
    } else {
      return false;
    }
  });
  console.log(exists, logOutput);
  return exists;
}

testCases.forEach(testCase => {
  if (checkPathForTruthy(obj, testCase[0]) === testCase[1]) {
    console.log("Passed:" + testCase[0]);
  } else {
    console.log("Failed:" + testCase[0] +" expected" + testCase[1]);
  }
});


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
//Just in case is not supported or not included by your framework
//***************************************************
Array.prototype.some = function(fn, thisObj) {
  var scope = thisObj || window;
  for ( var i=0, j=this.length; i < j; ++i ) {
    if ( fn.call(scope, this[i], i, this) ) {
      return true;
    }
  }
  return false;
};
//****************************************************

function isSet (object, string) {
  if (!object) return false;
  var childs = string.split('.');
  if (childs.length > 0 ) {
    return !childs.some(function (item) {
      if (item in object) {
        object = object[item];
        return false;
      } else return true;
    });
  } else if (string in object) {
    return true;
  } else return false;
}

var object = {
  data: {
    item: {
      sub_item: {
        bla: {
          here : {
            iam: true
          }
        }
      }
    }
  }
};

console.log(isSet(object,'data.item')); // true
console.log(isSet(object,'x')); // false
console.log(isSet(object,'data.sub_item')); // false
console.log(isSet(object,'data.item')); // true
console.log(isSet(object,'data.item.sub_item.bla.here.iam')); // true


我编写了自己的函数,它采用了所需的路径,并且具有好的和坏的回调函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function checkForPathInObject(object, path, callbackGood, callbackBad){
    var pathParts = path.split(".");
    var currentObjectPath = object;

    // Test every step to see if it exists in object
    for(var i=0; i<(pathParts.length); i++){
        var currentPathPart = pathParts[i];
        if(!currentObjectPath.hasOwnProperty(pathParts[i])){
            if(callbackBad){
                callbackBad();
            }
            return false;
        } else {
            currentObjectPath = currentObjectPath[pathParts[i]];
        }
    }

    // call full path in callback
    callbackGood();
}


用法:

1
2
3
4
5
6
7
8
9
10
11
12
13
var testObject = {
    level1:{
        level2:{
            level3:{
            }
        }
    }
};


checkForPathInObject(testObject,"level1.level2.level3", function(){alert("good!")}, function(){alert("bad!")}); // good

checkForPathInObject(testObject,"level1.level2.level3.levelNotThere", function(){alert("good!")}, function(){alert("bad!")}); //bad



那么单行在html模板中使用的答案并不是很好,所以我使用ES6 Proxies制作了一个。
您只需将对象或值传递给"遍历"函数,并执行尽可能多的嵌套调用,因为您希望使用函数调用来关闭它们,这将返回值或回退值。
使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
const testObject = {
  deep: {
    nested: {
      obj: {
        closure: () => { return"closure" },
        number: 9,
        boolean: true,
        array: [1, 2, { foo: { bar: true } }]
      }
    }
  }
}

traverse(testObject).deep()
// {nested: {…}}

traverse(testObject).non.existent()
// undefined

traverse(testObject).deep.nested.obj.closure()()
// closure

traverse(testObject).deep.nested.obj.array[5]('fallback')
// fallback

traverse(testObject).deep.nested.obj.array[2]()
// {foo: {…}}

traverse(testObject).deep.nested.obj.array[2].foo.bar()
// true

traverse(testObject).deep.nested.obj.array[2].foo.bar[4]('fallback')
// fallback

traverse(testObject).completely.wrong[3].call().WILL_THROW()
// Uncaught TypeError: Cannot read property 'WILL_THROW' of undefined


功能本身:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
const traverse = (input) => {
    // unique empty object
    const unset = new Object();
    // we need wrapper to ensure we have access to the same unique empty object
    const closure = (input) => {
        // wrap each input into this
        const handler = new Function();
        handler.input = input;    
        // return wrappers proxy
        return new Proxy(handler, {
            // keep traversing
            get: (target, name) => {
                // if undefined supplied as initial input
                if (!target.input) {
                    return closure(unset);
                }
                // otherwise
                if (target.input[name] !== undefined) {
                    // input has that property
                    return closure(target.input[name]);
                } else {
                    return closure(unset);
                }
            },
            // result with fallback
            apply: (target, context, args) => {
                return handler.input === unset ?
                    args[0] : handler.input;
            }
        })
    }
    return closure(input);    
}



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


(在此示例中,我使用下划线作为占位符变量,但任何变量名称都可以使用)

1
2
3
4
5
6
7
8
9
10
11
//the 'test' object
var test = {level1: {level2: {level3: 'level3'}}};

let _ = test;

if ((_=_.level1) && (_=_.level2) && (_=_.level3)) {

  let level3 = _;
  //do stuff with level3

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//you could also use 'stacked' if statements. This helps if your object goes very deep.
//(formatted without nesting or curly braces except the last one)

let _ = test;

if (_=_.level1)
if (_=_.level2)
if (_=_.level3) {

   let level3 = _;
   //do stuff with level3
}


//or you can indent:
if (_=_.level1)
  if (_=_.level2)
    if (_=_.level3) {

      let level3 = _;
      //do stuff with level3
}



轻微编辑此答案以允许路径中的嵌套数组

1
2
3
4
5
6
7
8
9
10
var has = function (obj, key) {
    return key.split(".").every(function (x) {
        if (typeof obj !="object" || obj === null || !x in obj)
            return false;
        if (obj.constructor === Array)
            obj = obj[0];
        obj = obj[x];
        return true;
    });
}


检查用户的链接答案:)



你可以用"。"分隔路径对象和路径。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function checkPathExist(obj, path) {
  var pathArray =path.split(".")
  for (var i of pathArray) {
    if (Reflect.get(obj, i)) {
      obj = obj[i];
       
    }else{
        return false;
    }
  }
    return true;
}

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

console.log('level1.level2.level3 => ',checkPathExist(test, 'level1.level2.level3')); // true
console.log( 'level1.level2.foo => ',checkPathExist(test, 'level1.level2.foo')); // false



如果你碰巧使用AngularJs,你可以使用$ parse服务来检查是否存在深层对象属性,如下所示:

1
2
3
if( $parse('model.data.items')(vm) ) {
    vm.model.data.items.push('whatever');
}


避免这样的陈述:

1
2
3
if(vm.model && vm.model.data && vm.model.data.items) {
    ....
}


不要忘记将$ parse服务注入您的控制器


欲了解更多信息:https://glebbahmutov.com/blog/angularjs-parse-hacks/



这有一点点模式,但有时会变得无法抗拒。我建议你一次使用它嵌套两到三个。

1
2
if (!(foo.bar || {}).weep) return;
// Return if there isn't a 'foo.bar' or 'foo.bar.weep'.


我可能忘了提一下,你也可以进一步扩展。下面的示例显示了对嵌套foo.bar.weep.woop的检查,或者如果没有可用则返回。

1
2
3
if (!((foo.bar || {}).weep || {}).woop) return;
// So, return if there isn't a 'foo.bar', 'foo.bar.weep', or 'foo.bar.weep.woop'.
// More than this would be overwhelming.


我自动化了这个过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
if(isset(object,["prop1","prop2"])){
// YES!

}

function isset(object, props){
    var dump;
    try {
        for(var x in props){
            if(x == 0) {
                dump = object[props[x]];
                return;
            }
            dump = dump[props[x]];
        }
    } catch(e) {
        return false;
    }

    return true;
}


1
2
3
4
5
6
7
getValue (o, key1, key2, key3, key4, key5) {
    try {
      return o[key1][key2][key3][key4][key5]
    } catch (e) {
      return null
    }
}


这是我使用的一个小辅助函数,对我而言,非常简单明了。希望它对某些人有用:)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
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);
}



另一个ES5解决方案:

1
2
3
4
5
6
7
8
9
function hasProperties(object, properties) {
    return !properties.some(function(property){
        if (!object.hasOwnProperty(property)) {
            return true;
        }
        object = object[property];
        return false;
    });
}


另一种解决方法是,例如,具有以下对象:

1
2
3
4
5
var x = {
    a: {
        b: 3
    }
};


然后,我做的是将以下函数添加到此对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
x.getKey = function(k){
        var r ;
        try {
            r = eval('typeof this.'+k+' !=="undefined"');
        }catch(e){
            r = false;
        }
        if(r !== false){
            return eval('this.'+k);
        }else{
            console.error('Missing key: \''+k+'\'');
            return '';
        }
    };


然后你可以测试:

1
x.getKey('a.b');


如果未定义,则函数返回"(空字符串),否则返回现有值。


还请考虑检查链接的其他更复杂的解决方案:JS对象具有属性深层检查

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
Object.prototype.hasOwnNestedProperty = function(propertyPath){
    if(!propertyPath)
        return false;

    var properties = propertyPath.split('.');
    var obj = this;

    for (var i = 0; i < properties.length; i++) {
        var prop = properties[i];

        if(!obj || !obj.hasOwnProperty(prop)){
            return false;
        } else {
            obj = obj[prop];
        }
    }

    return true;
};

// Usage:
var obj = {
   innerObject:{
       deepObject:{
           value:'Here am I'
       }
   }
}

obj.hasOwnNestedProperty('innerObject.deepObject.value');


P.S。:还有一个递归版本。



基于@StephaneLaflèche的回答,我提出了我的替代版本的脚本。


演示JSFiddle

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
var obj = {"a":{"b":{"c":"Hello World"}},"resTest":"potato","success":"This path exists"};
checkForPathInObject = function(object,path,value) {
        var pathParts   = path.split("."),
            result      = false;
        // Check if required parameters are set; if not, return false
        if(!object || typeof object == 'undefined' || !path || typeof path != 'string')
            return false;
        /* Loop through object keys to find a way to the path or check for value
         * If the property does not exist, set result to false
         * If the property is an object, update @object
         * Otherwise, update result */

        for(var i=0;i<pathParts.length;i++){
            var currentPathPart = pathParts[i];
            if(!object.hasOwnProperty( currentPathPart )) {
                result = false;
            } else if (object[ currentPathPart ] && path == pathParts[i]) {
                result = pathParts[i];
                break;
            } else if(typeof object[ currentPathPart ] == 'object') {
                object = object[ currentPathPart ];
            } else {
                result = object[ currentPathPart ];
            }
        }
        /* */
        if(typeof value != 'undefined' && value == result)
            return true;
        return result;
};
// Uncomment the lines below to test the script
// alert( checkForPathInObject(obj,'a.b.c') ); // Results"Hello World"
// alert( checkForPathInObject(obj,'a.success') ); // Returns false
// alert( checkForPathInObject(obj,'resTest', 'potato') ); // Returns true

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function isIn(string, object){
    var arr = string.split(".");
    var notFound = true;
    var length = arr.length;
    for (var i = 0; i < length; i++){
        var key = arr[i];
        if (!object.hasOwnProperty(key)){
            notFound = false;
            break;
        }
        if ((i + length) <= length){
            object = object[key];
        }
    }
    return notFound;
}
var musicCollection = {
    hasslehoff: {
        greatestHits : true
    }
};
console.log(isIn("hasslehoff.greatestHits", musicCollection));
console.log(isIn("hasslehoff.worseHits", musicCollection));


这是我的基于字符串的分隔符版本。



我正在以下列方式使用函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
var a = {};
a.b = {};
a.b.c = {};
a.b.c.d ="abcdabcd";

function isDefined(objectChainString) {
    try {
        var properties = objectChainString.split('.');
        var currentLevel = properties[0];
        if (currentLevel in window) {
            var consolidatedLevel = window[currentLevel];
            for (var i in properties) {
                if (i == 0) {
                    continue;
                } else {
                    consolidatedLevel = consolidatedLevel[properties[i]];
                }
            }
            if (typeof consolidatedLevel != 'undefined') {
                return true;
            } else {
                return false;
            }
        } else {
            return false;
        }
    } catch (e) {
        return false;
    }
}

// defined
console.log(checkUndefined("a.b.x.d"));
//undefined
console.log(checkUndefined("a.b.c.x"));
console.log(checkUndefined("a.b.x.d"));
console.log(checkUndefined("x.b.c.d"));



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

1
2
3
4
5
6
7
8
9
10
11
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



另一个非常紧凑的:

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


所谓:

1
2
3
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快



最好最简单的答案是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var isDefinedPath = function (path) {

    var items = path.split ('.');

    if (!items || items.length < 1 || !(items[0] in window)) { return false; }

    var buffer = [items[0]];
    for (var i = 1, e = items.length; i < e; i ++) {
        buffer.push (items[i]);
        if (eval ('typeof(' + buffer.join ('.') + ') =="undefined"')) {
            return false;
        }
    }

    return true;

}


测试:
isDefinedPath('level1.level2.level3');


第一级不能是数组,其他可以



又一个版本:

1
2
3
4
5
6
7
8
9
function nestedPropertyExists(obj, props) {
    var prop = props.shift();
    return prop === undefined
        ? true
        : obj.hasOwnProperty(prop) ? nestedPropertyExists(obj[prop], props) : false;
}

nestedPropertyExists({a:{b:{c:1}}}, ['a','b','c']); // returns true
nestedPropertyExists({a:{b:{c:1}}}, ['a','b','c','d']); // returns false


CMS解决方案效果很好,但使用/语法可以更方便。
我建议关注

1
2
3
4
5
6
7
8
9
10
11
12
var checkNested = function(obj, structure) {

  var args = structure.split(".");

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


您可以使用点而不是提供多个参数来使用对象表示法

1
2
3
4
var test = {level1:{level2:{level3:'level3'}} };

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


我写了一个名为l33teral的库来帮助测试嵌套属性。你可以像这样使用它:

1
2
var myObj = {/*...*/};
var hasNestedProperties = leet(myObj).probe('prop1.prop2.prop3');


我也喜欢这里的ES5 / 6解决方案。



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

1
2
3
4
5
6
7
8
9
10
11
12
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'));


编辑:仅当对象"test"具有全局范围/范围时才能工作。
否则你必须做以下事情:

1
2
3
4
5
6
// 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种调用方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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');


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/**
 * 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中你可以这样做:

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