ChatGPT解决这个技术问题 Extra ChatGPT

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

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

var test = {};

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

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

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

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

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

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

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

您可能想检查最近提出的一个切题相关的问题stackoverflow.com/questions/2525943/…
如果 level3 属性为 false,则您当前的方法存在潜在问题,在这种情况下,即使该属性存在也会返回 false,请查看此示例jsfiddle.net/maz9bLjx
简单地你也可以使用 try catch

C
Christian C. Salvadó

如果您不想要 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 特性和递归(它也是 proper tail call 形式):

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

2019 年 10 月 17 日更新:

optional chaining proposalECMAScript committee process 上达到了第 3 阶段,这将允许您通过使用令牌 ?.(新的可选链操作符)安全地访问深度嵌套的属性:

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

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

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

obj?.level1?.method();

如果 objobj.level1obj.level1.methodnullundefined,上述表达式将产生 undefined,否则将调用该函数。

您可以使用 optional chaining plugin 通过 Babel 开始使用此功能。

Babel 7.8.0 起,默认支持 ES2020

检查 Babel REPL 上的 this example

🎉🎉更新:2019 年 12 月🎉🎉

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


arguments 实际上不是一个数组。 Array.prototype.slice.call(arguments) 将其转换为正式数组。 Learn
这将是 a lot more efficient 执行 var obj = arguments[0]; 并从 var i = 1 开始,而不是复制 arguments 对象
为了紧缩的缘故,我将一个带有 try/catch 的版本放在一起,这并不奇怪 - 性能很糟糕(出于某种原因在 Safari 中除外)。下面有一些非常高效的答案,以及 Claudiu 的修改,它也比选定的答案更高效。在此处查看 jsperf jsperf.com/check-if-deep-property-exists-with-willnotthrow
在 ES6 中,可以删除 args 变量声明,并且可以将 ...args 用作 checkNested 方法的第二个参数。 developer.mozilla.org/en/docs/Web/JavaScript/Reference/…
这是一个非常难以维护的。如果任何属性键更改(它们会更改),项目中的所有开发人员都必须“字符串搜索”整个代码库。这并不是问题的真正解决方案,因为它引入了一个更大的问题
B
Ben Creasy

这是一个模式 I picked up from Oliver Steele

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

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


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

更新

看起来像 lodash has added _.get 可以满足您所有嵌套属性的需求。

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

https://lodash.com/docs#get

上一个答案

lodash 用户可能会喜欢具有 couple methods that mitigate this problemlodash.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

Lodash 确实需要一个 _.isPathDefined(obj, pathString) 方法。
@MatthewPayne也许会很好,但这确实没有必要。您可以自己轻松完成function isPathDefined(object, path) { return typeof _.getPath(object, path) !== 'undefined'; }
Lodash 本身具有相同的功能:_.get(countries, 'greece.sparta.playwright', 'default'); // → 'default' _.has(countries, 'greece.spart.playwright') // → false
更好的是_.result
如果您需要确定多个不同的路径,请考虑:var url = _.get(e, 'currentTarget.myurl', null) || _.get(e, 'currentTarget.attributes.myurl.nodeValue', null) || null
C
Community

我已经针对此问题提出的一些建议完成了 performance tests(感谢 cdMinix 添加 lodash),结果如下所示。

免责声明 #1 将字符串转换为引用是不必要的元编程,最好避免。不要一开始就忘记你的参考资料。从这个答案中阅读更多关于类似问题的信息。免责声明 #2 我们在这里谈论的是每毫秒数百万次操作。在大多数用例中,这些都不太可能产生很大的不同。在了解每个限制的情况下选择最有意义的那个。对我来说,出于方便,我会选择 reduce 之类的东西。

Object Wrap (by Oliver Steele) – 34 % – 最快

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

Original solution (suggested in question) – 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 }
}

validChain – 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%

deeptest – 86%

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

sad clowns – 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() 呢?与这些答案相比,它的性能如何?
根据情况,这些方法中的每种方法都比其他方法慢或快。如果找到所有键,则“对象包装”可能最快,但如果未找到其中一个键,则“本机解决方案/原始解决方案”可能更快。
@evilReiko 如果没有找到任何键,任何方法都会变慢,但它们之间的比例仍然几乎相同。然而,你是对的——这比其他任何事情都更像是一种智力锻炼。我们在这里谈论的是每毫秒一百万次迭代。我看不到任何用例会产生很大的不同。就我个人而言,出于方便,我会选择 reducetry/catch
try { test.level1.level2.level3 } catch (e) { // some logger e } 相比,它的性能如何
k
kennebec

如果您像字符串一样处理名称:'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 中,在某些情况下优于所选答案的 @Claudiu 修改版本。在此处查看性能测试:jsperf.com/check-if-deep-property-exists-with-willnotthrow
G
Gajus
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),那么您可以利用 arrow function 语法:

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

关于性能,如果设置了属性,则使用 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

我认为 try-catch 方法是最好的答案。查询对象的类型与假设 API 存在并且如果不存在则相应地失败之间存在哲学差异。后者更适合松散类型的语言。请参阅stackoverflow.com/a/408305/2419669try-catch 方法也比 if (foo && foo.bar && foo.bar.baz && foo.bar.baz.qux) { ... } 清晰得多。
u
user187291

怎么样

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

}

我不认为 try/catch 是测试对象是否存在的好方法:try/catch 旨在处理异常,而不是正常情况,例如此处的测试。我认为 (typeof foo == "undefined") 在每一步都更好——而且一般来说,如果您使用如此深度嵌套的属性,可能需要进行一些重构。此外,如果抛出异常,try/catch 将导致 Firebug 中断(以及在任何打开错误中断的浏览器中)。
我对此投票,因为如果您使用其他解决方案,浏览器会检查两次是否存在。假设您想调用“acb = 2”。浏览器必须在修改值之前检查是否存在(否则它将是操作系统捕获的内存错误)。
问题仍然存在:浏览器设置 try catch 或调用 hasOwnProperty() n 次会更快吗?
怎么又变坏了?这对我来说看起来最干净。
我会说:如果您期望该属性存在,那么可以将其包装到 try 块中。如果它不存在,那就是一个错误。但是,如果您只是懒惰并在属性不存在的情况下将常规代码放入 catch 块中,则 try/catch 被滥用。这里需要 if/else 或类似的东西。
G
Goran.it

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

代码如下所示:

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

请注意,这种语法几乎肯定会改变,因为一些 TC39 成员有异议。
可能,但这会及时以某种形式提供,这是唯一重要的事情..这是我在 JS 中最怀念的功能之一。
F
Frank Nocke

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

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

→ 参见 Codepen 完整的测试覆盖率


我让你的测试失败,将 flat prop 的值设置为 0。你必须关心类型强制。
@germain this 对您有用吗? (我明确比较了不同 falsys 的 ===,并添加了测试。如果您有更好的想法,请告诉我)。
我让你的测试再次失败,将 flat prop 的值设置为 false。然后您可能希望将对象中的值设置为 undefined (我知道这很奇怪,但它是 JS)。我将正假值设置为 '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))
你能分叉我的代码笔(右上角,简单),更正和添加测试,然后把你的 URL 发给我吗?谢谢 =)
三年后不编辑,这是不负责任的。正如@germain 指出的那样,这很容易在虚假值上失败。如果这是经过彻底测试的,那么您有一些错误的代码。
D
Daniel Barral

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

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

资源:

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


这不是所要求的。
j
jrode

我尝试了一种递归方法:

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';

V
VeganHunter

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

声明一个函数:

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

但这一次智能感知有效!

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

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

°0o <°(())))><
我喜欢这个,因为它很诚实,当你不知道你的 Object 类型时,它会在你脸上抛出一个“未定义”。 +1。
只要您将声明保留在括号中,您也可以将其称为快乐小丑技术(o
谢谢斯文蒂斯。我喜欢你的评论。这是一个很好的视角——这样的条件主要用在“ifs”中,并且总是用外部括号括起来。所以,是的,它确实是一个快乐的小丑:)))
你真的需要爱上括号才能找到这个......
k
kelvin kantaria

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

尝试这个

function isExist(arg){ try{ return arg(); }catch(e){ 返回 false; } } 让 obj={a:5,b:{c:5}}; console.log(isExist(()=>obj.bc)) console.log(isExist(()=>obj.b.foo)) console.log(isExist(()=>obj.test.foo))

如果条件

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

这吞下了可能发生的任何其他错误。
E
Endless

我没有看到有人使用 Proxies 的任何示例

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

function resolve(target) { var noop = () => {} // 我们是一个 noop 函数,所以我们可以调用方法也 return new Proxy(noop, { get(noop, key) { // 如果 key 是则返回最终结果_result return key === '_result' ? target : resolve( // 使用目标值或 undefined 解析 target === undefined ? undefined : target[key] ) }, // 如果我们想测试一个函数,那么我们可以这样做所以还要感谢使用 noop // 而不是在我们的代理中使用 target apply(noop, that, args) { return resolve(typeof target === 'function' ? target.apply(that, args) : undefined) }, } ) } // 已接受答案的一些修改示例 var test = {level1: {level2:() => ({level3:'level3'})}} var test1 = {key1: {key2: ['item0']} } // 最后需要得到_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) // 不存在

上面的代码适用于同步的东西。但是你将如何测试像这个 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) })) }, }) } // 这感觉非常同步,但仍然是非阻塞的 :) resolve(window) // this将链接一个 noop 函数,直到您调用 then() .fetch('https://httpbin.org/get') .json() .headers['User-Agent'] .then(console.log, console.warn) // 如果它不存在,您会收到警告 // 您也可以对第一个测试对象使用此方法 // 也可以,但最后必须调用 .then() // 另一个示例 resolve(window) .fetch('https://httpbin.org/get?items=4&items=2') .json() .args .items // 很高兴你可以在没有准备好的情况下映射一个数组项 .map(n => ~~n * 4) .then(console.log, console.warn) / / 如果它不存在,您会收到警告


如果有人感兴趣,我会在 npm 上发布异步版本
j
jfriend00

一种简单的方法是:

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)的情况。


C
Community

基于 this answer,我使用 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 );
}

这是我的最终方法function validChain (object, path) { return path.split('.').reduce((a, b) => (a || { })[b], object) !== undefined }
V
V. Sambor

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

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

甚至更短但不使用 falsy 标志:

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


m
mikemaccana

@CMS 优秀答案的更短的 ES5 版本:

// 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

唯一的问题是如果有多个级别的未定义键,那么你会得到一个 TypeError,例如 checkObjHasKeys(test, ['level1', 'level2', 'asdf', 'asdf']);
更合适的方法是every,它的值可以直接返回。
也许将 success = false; 更改为 return false。一旦你知道它坏了,你应该退出,一旦它为空或未定义,就没有更深的存在了。这将防止更深的嵌套项上的错误,因为它们显然也不存在。
N
Noah Stahl

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

function getNestedProperty(obj, key) { // 从键字符串中获取属性数组 var properties = key.split("."); // 遍历属性,如果对象为 null 或属性不存在则返回 undefined for (var i = 0; i < properties.length; i++) { if (!obj || !obj.hasOwnProperty(properties[i]) ) { 返回; } obj = obj[属性[i]]; } // 找到嵌套属性,所以返回值 return obj; } 用法:getNestedProperty(test, "level1.level2.level3") // "level3" getNestedProperty(test, "level1.level2.foo") // 未定义


A
Anand Sunderraman

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

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

C
Community

this answer 开始详细阐述了以下选项。两者相同的树:

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

B
Brian Sidebotham

我知道这个问题很老,但我想通过将它添加到所有对象来提供扩展。我知道人们倾向于不赞成使用对象原型来扩展对象功能,但我没有发现比这样做更容易的事情了。另外,现在允许使用 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") )

Here's a jsfiddle 对其进行测试,特别是它包含一些 jQuery,如果您直接修改 Object.prototype 会中断,因为该属性变得可枚举。这应该适用于 3rd 方库。


J
Julius Musseau

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

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

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


你从字面上复制了他们所说的他们通常做的事情,并想避免......
a
adutu

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

前任:

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

这是fiddle


S
Seth

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

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

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

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

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

函数 deeptest(keyPath, testObj) { var obj; keyPath = keyPath.split('.') var cKey = keyPath.shift();函数get(pObj,pKey){var括号开始,括号结束,o;括号开始 = pKey.indexOf("["); if (bracketStart > -1) { //检查嵌套数组bracketEnd = pKey.indexOf("]"); var arrIndex = pKey.substr(bracketStart + 1, 括号结束 - 括号开始 - 1); pKey = pKey.substr(0, 括号开始); var n = pObj[pKey]; o = n? n[arrIndex]:未定义; } 其他 { o = pObj[pKey]; } 返回 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 ":"字母 A", "b":"字母 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));


E
Egor Stambakio

现在我们还可以使用 reduce 循环遍历嵌套键:

// @params o // @params path 期望 'obj.prop1.prop2.prop3' // 返回:obj[path] 值,如果 prop 不存在,则返回 'false' const objPropIfExists = o = > 路径 => { 常量级别 = path.split('.'); const res = (levels.length > 0) ? levels.reduce((a, c) => a[c] || 0, o) : o[path];返回 (!!res) ? res : false } const obj = { name: 'Name', sys: { country: 'AU' }, main: { temp: '34', temp_min: '13' }, visibility: '35%' } const 存在 = objPropIfExists(obj)('main.temp') const doesntExist = objPropIfExists(obj)('main.temp.foo.bar.baz') console.log(exists, doesntExist)


A
Ankit Arya

您可以使用递归函数来做到这一点。即使您不知道所有嵌套的对象键名称,这也将起作用。

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

e
estani

还有一个非常紧凑的:

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


l
lahiru dilshan

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

这是功能

/**
 * 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') }}

D
David

您可以尝试 Optional chaining(但请注意浏览器兼容性)。

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

有一个用于 optinal chaning 的 babel 插件(@babel/plugin-proposal-optional-chaining)。因此,如有必要,请升级您的 babel。