154

在我的代码中,我处理一个数组,其中包含一些条目,其中许多对象相互嵌套,而有些则没有。它看起来像下面这样:

// where this array is hundreds of entries long, with a mix
// of the two examples given
var test = [{'a':{'b':{'c':"foo"}}}, {'a': "bar"}];

这给我带来了问题,因为我有时需要遍历数组,并且不一致会给我带来如下错误:

for (i=0; i<test.length; i++) {
    // ok on i==0, but 'cannot read property of undefined' on i==1
    console.log(a.b.c);
}

我知道我可以这么说if(a.b){ console.log(a.b.c)},但是在最多有 5 或 6 个对象相互嵌套的情况下,这是非常乏味的。有没有其他(更简单的)方法可以让我只在 console.log 存在的情况下执行它,但不会引发错误?

4

18 回答 18

198

更新

  • 如果您根据 ECMAScript 2020 或更高版本使用 JavaScript,请参阅可选链接
  • TypeScript 在3.7版本中增加了对可选链的支持。
// use it like this
obj?.a?.lot?.of?.properties

ECMASCript 2020 之前的 JavaScript 或 3.7 版之前的 TypeScript 的解决方案

一个快速的解决方法是使用带有 ES6箭头函数的 try/catch 辅助函数

function getSafe(fn, defaultVal) {
  try {
    return fn();
  } catch (e) {
    return defaultVal;
  }
}

// use it like this
console.log(getSafe(() => obj.a.lot.of.properties));

// or add an optional default value
console.log(getSafe(() => obj.a.lot.of.properties, 'nothing'));

于 2017-02-20T16:23:14.003 回答
53

你正在做的事情引发了一个例外(这是理所当然的)。

你总能做到

try{
   window.a.b.c
}catch(e){
   console.log("YO",e)
}

但我不会,而是想想你的用例。

你为什么要访问数据,6 个你不熟悉的嵌套级别?什么用例证明了这一点?

通常,您希望实际验证您正在处理的对象类型。

另外,在旁注中,您不应该使用这样的语句,if(a.b)因为如果 ab 为 0 或即使它为“0”,它将返回 false。而是检查是否a.b !== undefined

于 2013-02-08T22:18:19.537 回答
15

如果我正确理解您的问题,您需要最安全的方法来确定对象是否包含属性。

最简单的方法是使用in操作符

window.a = "aString";
//window should have 'a' property
//lets test if it exists
if ("a" in window){
    //true
 }

if ("b" in window){
     //false
 }

当然你可以把它嵌套到你想要的深度

if ("a" in window.b.c) { }

不确定这是否有帮助。

于 2013-02-08T23:16:33.530 回答
14

试试这个。如果a.b未定义,它将if毫无例外地离开该语句。

if (a.b && a.b.c) {
  console.log(a.b.c);
}
于 2017-06-28T11:15:00.573 回答
13

如果你使用lodash,你可以使用他们的“has”功能。它类似于原生的“in”,但允许路径。

var testObject = {a: {b: {c: 'walrus'}}};
if(_.has(testObject, 'a.b.c')) {
  //Safely access your walrus here
}
于 2015-08-07T19:05:23.983 回答
7

如果你使用 Babel,你已经可以通过@babel/plugin-proposal-optional-chaining Babel plugin使用可选链语法。这将允许您替换它:

console.log(a && a.b && a.b.c);

有了这个:

console.log(a?.b?.c);
于 2019-02-24T23:33:20.320 回答
5

我虔诚地使用undefsafe。它测试每个级别到您的对象,直到它获得您要求的值,或者它返回“未定义”。但绝不会出错。

于 2016-05-12T16:32:19.287 回答
5

这是处理深度或复杂 json 对象时的常见问题,因此我尽量避免尝试/捕获或嵌入会使代码不可读的多项检查,我通常在我的所有 procect 中使用这段小代码来完成这项工作。

/* ex: getProperty(myObj,'aze.xyz',0) // return myObj.aze.xyz safely
 * accepts array for property names: 
 *     getProperty(myObj,['aze','xyz'],{value: null}) 
 */
function getProperty(obj, props, defaultValue) {
    var res, isvoid = function(x){return typeof x === "undefined" || x === null;}
    if(!isvoid(obj)){
        if(isvoid(props)) props = [];
        if(typeof props  === "string") props = props.trim().split(".");
        if(props.constructor === Array){
            res = props.length>1 ? getProperty(obj[props.shift()],props,defaultValue) : obj[props[0]];
        }
    }
    return typeof res === "undefined" ? defaultValue: res;
}
于 2016-09-22T13:53:13.627 回答
5

如果你有lodash你可以使用它的.get方法

_.get(a, 'b.c.d.e')

或者给它一个默认值

_.get(a, 'b.c.d.e', default)
于 2019-05-17T19:33:28.477 回答
3

我喜欢曹寿光的回答,但我不喜欢每次调用时将函数作为参数传递给getSafe函数。我已经修改了 getSafe 函数以接受简单的参数和纯 ES5。

/**
* Safely get object properties.    
* @param {*} prop The property of the object to retrieve
* @param {*} defaultVal The value returned if the property value does not exist
* @returns If property of object exists it is returned, 
*          else the default value is returned.
* @example
* var myObj = {a : {b : 'c'} };
* var value;
* 
* value = getSafe(myObj.a.b,'No Value'); //returns c 
* value = getSafe(myObj.a.x,'No Value'); //returns 'No Value'
* 
* if (getSafe(myObj.a.x, false)){ 
*   console.log('Found')
* } else {
*  console.log('Not Found') 
* }; //logs 'Not Found'
* 
* if(value = getSafe(myObj.a.b, false)){
*  console.log('New Value is', value); //logs 'New Value is c'
* }
*/
function getSafe(prop, defaultVal) {
  return function(fn, defaultVal) {
    try {
      if (fn() === undefined) {
        return defaultVal;
      } else {
        return fn();
      }
    } catch (e) {
      return defaultVal;
    }
  }(function() {return prop}, defaultVal);
}
于 2019-01-09T16:15:06.390 回答
3

Lodash 有一个get方法允许将默认值作为可选的第三个参数,如下所示:

const myObject = {
  has: 'some',
  missing: {
    vars: true
  }
}
const path = 'missing.const.value';
const myValue = _.get(myObject, path, 'default');
console.log(myValue) // prints out default, which is specified above
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.js"></script>

于 2019-01-30T13:30:31.647 回答
2

在 str 的答案中,如果属性未定义,则将返回值“未定义”而不是设置的默认值。这有时会导致错误。以下将确保在未定义属性或对象时始终返回 defaultVal。

const temp = {};
console.log(getSafe(()=>temp.prop, '0'));

function getSafe(fn, defaultVal) {
    try {
        if (fn() === undefined || fn() === null) {
            return defaultVal
        } else {
            return fn();
        }
        
    } catch (e) {
        return defaultVal;
    }
}
于 2018-06-28T06:37:19.853 回答
2

x想象一下,我们想对当且仅当x非空时应用一系列函数:

if (x !== null) x = a(x);
if (x !== null) x = b(x);
if (x !== null) x = c(x);

现在假设我们需要对以下内容做同样的事情y

if (y !== null) y = a(y);
if (y !== null) y = b(y);
if (y !== null) y = c(y);

和同样的z

if (z !== null) z = a(z);
if (z !== null) z = b(z);
if (z !== null) z = c(z);

如您所见,如果没有适当的抽象,我们最终会一遍又一遍地重复代码。这样的抽象已经存在:Maybe monad。

Maybe monad 拥有一个值和一个计算上下文:

  1. monad 保持值安全并对其应用函数。
  2. 计算上下文是应用函数之前的空检查。

一个简单的实现应该是这样的:

⚠️ 此实现仅用于说明目的!这不是应该做的,而且在很多层面上都是错误的。但是,这应该让您更好地了解我在说什么。

如您所见,没有什么可以破坏:

  1. 我们将一系列功能应用于我们的价值
  2. 如果在任何时候,值变为空(或未定义),我们就不再应用任何函数。

const abc = obj =>
  Maybe
    .of(obj)
    .map(o => o.a)
    .map(o => o.b)
    .map(o => o.c)
    .value;

const values = [
  {},
  {a: {}},
  {a: {b: {}}},
  {a: {b: {c: 42}}}
];

console.log(

  values.map(abc)

);
<script>
function Maybe(x) {
  this.value = x; //-> container for our value
}

Maybe.of = x => new Maybe(x);

Maybe.prototype.map = function (fn) {
  if (this.value == null) { //-> computational context
    return this;
  }
  return Maybe.of(fn(this.value));
};
</script>


附录1

我无法解释 monad 是什么,因为这不是这篇文章的目的,而且还有比我更擅长这方面的人。然而,正如 Eric Elliot 在他的博客文章JavaScript Monads Made Simple中所说:

无论您的技能水平或对类别理论的理解如何,使用 monad 都会使您的代码更易于使用。未能利用 monad 可能会使您的代码更难处理(例如,回调地狱、嵌套条件分支、更冗长)。


附录二

以下是我如何使用来自Maybe monad解决您的问题

const prop = key => obj => Maybe.fromNull(obj[key]);

const abc = obj =>
  Maybe
    .fromNull(obj)
    .flatMap(prop('a'))
    .flatMap(prop('b'))
    .flatMap(prop('c'))
    .orSome('')
    
const values = [
  {},
  {a: {}},
  {a: {b: {}}},
  {a: {b: {c: 42}}}
];

console.log(

  values.map(abc)

);
<script src="https://www.unpkg.com/monet@0.9.0/dist/monet.js"></script>
<script>const {Maybe} = Monet;</script>

于 2019-08-25T22:49:53.230 回答
1

您可以使用 ECMAScript 标准中的可选链接。像这样:

a?.b?.c?.d?.func?.()
于 2021-12-17T13:04:47.270 回答
0

我之前回答过这个问题,今天碰巧也在做类似的检查。检查嵌套点属性是否存在的简化。您可以修改它以返回值,或者一些默认值来实现您的目标。

function containsProperty(instance, propertyName) {
    // make an array of properties to walk through because propertyName can be nested
    // ex "test.test2.test.test"
    let walkArr = propertyName.indexOf('.') > 0 ? propertyName.split('.') : [propertyName];

    // walk the tree - if any property does not exist then return false
    for (let treeDepth = 0, maxDepth = walkArr.length; treeDepth < maxDepth; treeDepth++) {

        // property does not exist
        if (!Object.prototype.hasOwnProperty.call(instance, walkArr[treeDepth])) {
            return false;
        }

        // does it exist - reassign the leaf
        instance = instance[walkArr[treeDepth]];

    }

    // default
    return true;

}

在您的问题中,您可以执行以下操作:

let test = [{'a':{'b':{'c':"foo"}}}, {'a': "bar"}];
containsProperty(test[0], 'a.b.c');
于 2018-07-20T21:12:04.057 回答
0

我通常这样使用:

 var x = object.any ? object.any.a : 'def';
于 2018-12-29T23:07:35.503 回答
0

您可以通过在获取属性之前提供默认值来避免出现错误

var test = [{'a':{'b':{'c':"foo"}}}, {'a': "bar"}];

for (i=0; i<test.length; i++) {
    const obj = test[i]
    // No error, just undefined, which is ok
    console.log(((obj.a || {}).b || {}).c);
}

这也适用于数组:

const entries = [{id: 1, name: 'Scarllet'}]
// Giving a default name when is empty
const name = (entries.find(v => v.id === 100) || []).name || 'no-name'
console.log(name)

于 2020-08-05T21:37:26.040 回答
0

与问题的实际问题无关,但可能对来此问题寻找答案的人有用。

检查你的函数参数。

如果你有一个类似的函数const x({ a }) => { },并且你在没有参数的情况下调用它x();附加 = {}到参数:const x({ a } = {}) => { }.


我所拥有的

我有这样的功能:

const x = ({ a }) => console.log(a);
// This one works as expected
x({ a: 1 });
// This one errors out
x();

这导致"Uncaught TypeError: Cannot destructure property 'a' of 'undefined' as it is undefined."


我将其切换为(现在有效)。

const x = ({ a } = {}) => console.log(a);
// This one works as expected
x({ a: 1 });
// This now works too!
x();

于 2021-03-10T01:24:59.477 回答