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

use*_*716 616 javascript nested properties object

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

var test = {};
Run Code Online (Sandbox Code Playgroud)

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

{level1: {level2: {level3: "level3"}}};
Run Code Online (Sandbox Code Playgroud)

在最深层嵌套的对象中测试密钥是否存在的最佳方法是什么?

alert(test.level1);收益率undefined,但alert(test.level1.level2.level3);失败了.

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

if(test.level1 && test.level1.level2 && test.level1.level2.level3) {
    alert(test.level1.level2.level3);
}
Run Code Online (Sandbox Code Playgroud)

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

CMS*_*CMS 383

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

您可以简单地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
Run Code Online (Sandbox Code Playgroud)

  • 这将是[更高效](http://jsperf.com/checknested-efficiency)做`var obj = arguments [0];`并从`var i = 1`开始而不是复制`参数`对象 (22认同)
  • 仅供参考,您也可以使用lodash中的_.get()来获取此https://lodash.com/docs#get (21认同)
  • 这是一个非常难以维护的.如果任何属性键发生变化(他们会),项目中的所有开发人员都必须对整个代码库进行"字符串搜索".这不是问题的真正解决方案,因为它引入了一个更大的问题 (5认同)
  • `arguments`实际上不是一个数组.`Array.prototype.slice.call(arguments)`将它转换为正式数组.**[了解](https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Functions_and_function_scope/arguments)** (4认同)
  • 在ES6中,可以删除`args`变量声明,并且``args`可以用作`checkNested`方法的第二个参数.https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Operators/Spread_operator (3认同)
  • 为了紧缩起见,我将一个带有 try/catch 的版本放在一起,这并不奇怪 - 性能很糟糕(由于某种原因在 Safari 中除外)。下面有一些非常高效的答案,以及克劳迪乌的修改,这也比所选答案的性能要高得多。在此处查看 jsperf http://jsperf.com/check-if-deep-property-exists-with-willnotthrow (2认同)

Gab*_*art 347

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

var level3 = (((test || {}).level1 || {}).level2 || {}).level3;
alert( level3 );
Run Code Online (Sandbox Code Playgroud)

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

  • 你说*"这不是很难********************"*.嗯,**这些**是你知道的标志**已经这是一个**混乱**.为什么建议这个解决方案?它容易出现拼写错误,并且绝对没有任何**可读性.看看吧!如果我不得不**写一条丑陋的线条,它应该是**可读**; 所以我要坚持使用`if(test.level1 && test.level1.level2 && test.level1.level2.level3) (24认同)
  • @MuhammadUmer不,`(test || {})`的意思是,如果测试未定义,那么你正在做`({}.level1 || {})`.当然,`{} .level1`是未定义的,这意味着你正在做`{}.level.2`,依此类推. (9认同)
  • 这既难以阅读又难以打字. (9认同)
  • @wared我觉得它很有趣,主要是因为它简洁.对链接帖子中的性能特征进行了详细讨论.是的,它始终执行所有测试,但它避免创建临时变量,如果要防止每次创建新空对象的开销,可以将{}别名变为var.在99%的情况下,我不希望速度变得重要,并且在它发生的情况下,没有替代分析. (8认同)
  • 除非我遗漏了某些内容,否则这对于可能错误的布尔结束属性不起作用......遗憾的是.否则我喜欢这个成语. (7认同)
  • 如果测试未定义,则会出错. (3认同)
  • @JoshuaTaylor:我认为他的意思是如果`test`没有声明,那就会有一个*ReferenceError*,但这不是问题,因为如果没有声明,就有一个bug需要修复,所以错误是件好事. (3认同)

Aus*_*ray 250

更新

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

_.get(countries, 'greece.sparta.playwright')
Run Code Online (Sandbox Code Playgroud)

https://lodash.com/docs#get


以前的答案

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

的getPath

签名: _.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
Run Code Online (Sandbox Code Playgroud)

  • Lodash本身具有相同的功能:`_. get(countries,'greece.sparta.playwright','default'); //→'default'_.has(countries,'greece.spart.playwright')//→false` (11认同)

uni*_*rio 194

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

免责声明#1将字符串转换为引用是不必要的元编程,最好避免使用.不要忘记开头的参考资料.从这个问题的答案中了解更多.

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

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

var r1 = (((test || {}).level1 || {}).level2 || {}).level3;
var r2 = (((test || {}).level1 || {}).level2 || {}).foo;
Run Code Online (Sandbox Code Playgroud)

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

var r1 = test.level1 && test.level1.level2 && test.level1.level2.level3;
var r2 = test.level1 && test.level1.level2 && test.level1.level2.foo;
Run Code Online (Sandbox Code Playgroud)

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;
}
Run Code Online (Sandbox Code Playgroud)

get_if_exist - 52%

function get_if_exist(str) {
    try { return eval(str) }
    catch(e) { return undefined }
}
Run Code Online (Sandbox Code Playgroud)

有效链接 - 54%

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

objHasKeys - 63%

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

nestedPropertyExists - 69%

function nestedPropertyExists(obj, props) {
    var prop = props.shift();
    return prop === undefined ? true : obj.hasOwnProperty(prop) ? nestedPropertyExists(obj[prop], props) : false;
}
Run Code Online (Sandbox Code Playgroud)

_.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;
}
Run Code Online (Sandbox Code Playgroud)

悲伤的小丑 - 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);
Run Code Online (Sandbox Code Playgroud)

  • 应该注意的是,测试的百分比越多 - 它就是SLOWER (14认同)
  • lodash `_.get()` 怎么样?与这些答案相比,它的性能如何? (2认同)

ken*_*bec 44

如果你像字符串一样处理名称,你可以读取任何深度的对象属性:'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');
Run Code Online (Sandbox Code Playgroud)

undefined如果有任何段,则返回undefined.

  • 值得注意的是,这种方法非常有效,至少在Chrome中,在某些情况下优于@Claudiu修改版本的选定答案.请参阅此处的性能测试:http://jsperf.com/check-if-deep-property-exists-with-willnotthrow (3认同)

Gaj*_*jus 27

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; })
);
Run Code Online (Sandbox Code Playgroud)

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

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

关于性能,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
Run Code Online (Sandbox Code Playgroud)

  • 我认为`try-catch`方法是最好的答案.查询对象的类型与假设API存在之间存在哲学上的区别,如果不存在则相应失败.后者更适合松散类型的语言.请参见http://stackoverflow.com/a/408305/2419669.`try-catch`方法也比`if(foo && foo.bar && foo.bar.baz && foo.bar.baz.qux){...}`更清晰. (2认同)

use*_*291 23

怎么样

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

}
Run Code Online (Sandbox Code Playgroud)

  • 我不认为try/catch是测试对象存在的好方法:try/catch用于处理异常,而不是正常条件,例如测试.我认为(typeof foo =="undefined")在每一步都更好 - 而且一般来说,如果你正在使用这种深层嵌套的属性,可能需要进行一些重构.此外,如果抛出异常,try/catch将导致Firebug(以及任何打开了break-on-error的浏览器)中断. (15认同)
  • 为什么这又坏了?这看起来对我来说最干净. (14认同)
  • 问题仍然存在:对于浏览器来说,设置try catch或调用`hasOwnProperty()`n次更快? (4认同)

Dan*_*iel 19

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

let value = test?.level1?.level2?.level3;
Run Code Online (Sandbox Code Playgroud)

来源:

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


Fra*_*cke 18

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

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

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


Gor*_*.it 14

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

代码看起来像这样:

  const test = test?.level1?.level2?.level3;
  if (test) alert(test);
Run Code Online (Sandbox Code Playgroud)


jro*_*ode 10

我尝试了一种递归方法:

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

! 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
Run Code Online (Sandbox Code Playgroud)

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

var biosName = objHasKeys(myObj, 'MachineInfo:BiosInfo:Name'.split(':'))
             ? myObj.MachineInfo.BiosInfo.Name
             : 'unknown';
Run Code Online (Sandbox Code Playgroud)


Veg*_*ter 10

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

声明一个函数:

var o = function(obj) { return obj || {};};
Run Code Online (Sandbox Code Playgroud)

然后像这样使用它:

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

}
Run Code Online (Sandbox Code Playgroud)

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


编辑:

这是TypeScript的一个版本

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

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

用法是一样的:

o(o(o(o(test).level1).level2).level3
Run Code Online (Sandbox Code Playgroud)

但这次intellisense工作!

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

o(o(o(o(o(test).level1).level2).level3, "none")
Run Code Online (Sandbox Code Playgroud)

  • `°0o &lt;°(())))&gt;&lt;` (2认同)
  • 我喜欢这个,因为它很诚实,当你不知道你的 `Object` 类型时,它会在你的脸上抛出一个“未定义”。+1。 (2认同)
  • 只要您将语句保留在括号中,您也可以将其称为快乐小丑技术(o (2认同)

kel*_*ria 9

创建全局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))
Run Code Online (Sandbox Code Playgroud)

如果条件

if(isExist(()=>obj.test.foo)){
   ....
}
Run Code Online (Sandbox Code Playgroud)


jfr*_*d00 6

一种简单的方法是这样的:

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

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


End*_*ess 6

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

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

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
Run Code Online (Sandbox Code Playgroud)

上面的代码适用于同步的东西.但是你会如何测试像ajax调用那样异步的东西呢?你是如何测试的?如果响应不是json时返回500 http错误怎么办?

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

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

fetch('https://httpbin.org/get').json().headers['User-Agent']
Run Code Online (Sandbox Code Playgroud)

你可能想知道所有的承诺和.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
Run Code Online (Sandbox Code Playgroud)


Dav*_*vid 6

你可以尝试一下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
Run Code Online (Sandbox Code Playgroud)

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


Ale*_*van 5

基于此答案,我想出了此通用函数,可用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 );
}
Run Code Online (Sandbox Code Playgroud)

  • 这是我的最终方法 `function validChain (object, path) { return path.split('.').reduce((a, b) =&gt; (a || { })[b], object) !== undefined }` (2认同)

V. *_*bor 5

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

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);
}
Run Code Online (Sandbox Code Playgroud)

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

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);
}
Run Code Online (Sandbox Code Playgroud)

甚至更短,但不会在虚假标志上回退:

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);
}
Run Code Online (Sandbox Code Playgroud)

我测试了:

const obj = {
    c: {
        a: 2,
        b: {
            c: [1, 2, 3, {a: 15, b: 10}, 15]
        },
        c: undefined,
        d: null
    },
    d: ''
}
Run Code Online (Sandbox Code Playgroud)

这是一些测试:

// 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'));
Run Code Online (Sandbox Code Playgroud)

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


小智 5

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

这是函数

/**
 * 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;
}
Run Code Online (Sandbox Code Playgroud)

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

{{ getProperty(object, 'passengerDetails.data.driverInfo.currentVehicle.vehicleType') }}
Run Code Online (Sandbox Code Playgroud)


归档时间:

查看次数:

211063 次

最近记录:

5 年,9 月 前