在我的代码中,我处理了一个数组,其中包含一些条目,其中许多对象彼此嵌套,而有些则没有。它看起来像以下内容:

// 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存在的情况下执行,而不会引发错误吗?

评论

该错误可能是常规Javascript异常,因此请尝试try..catch语句。就是说,包含野生异构元素的数组对我来说似乎是一个设计问题。

如果项目之间的结构不一致,那么检查是否存在问题呢?确实,我会使用if(a.b中的&&“ c”中的“ b”)。这可能是“乏味的”,但这就是您因为不一致而得到的...常规逻辑。

为什么要访问不存在的属性,为什么不知道对象的外观?

我能理解为什么有人不希望错误导致所有故障。您不能总是依靠对象的属性来存在或不存在。如果您有适当的东西可以处理对象格式错误的事件,那么您的代码将更加高效且不那么脆弱。

您会惊讶于现实生活中有多少个对象/数组格式错误

#1 楼

更新:

如果您根据ECMAScript 2020或更高版本使用JavaScript,请参阅可选链接。
TypeScript在3.7版中添加了对可选链接的支持。

 // use it like this
obj?.a?.lot?.of?.properties
 


ECMASCript 2020或早于3.7版的TypeScript之前的JavaScript解决方案:
一种快速的解决方法是使用try / catch具有ES6箭头功能的辅助功能:



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




评论


我喜欢它!我唯一要添加的是catch内的console.warn,这样您就知道错误了,但是错误仍在继续。

–拉比·舒基·古尔
18/12/11在10:31

在不重新抛出的情况下捕获所有异常是不好的,并且通常将异常用作预期执行流的一部分也不太好-即使在这种情况下,异常包含得很好。

–hugo
19年8月25日在11:28

#2 楼

您正在做的事情引发了一个异常(理所当然的)。

您总是可以做

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


但是我不会,而是考虑您的用例。

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

通常,您实际上想验证要处理的对象类型。

另外,在侧面说明中,请勿使用if(a.b)之类的语句,因为如果a.b为0或什至为“ 0”,它将返回false。而是检查a.b !== undefined

评论


关于您的第一次编辑:我正在处理JSON结构化的数据库条目,以便对象可以缩放多个级别的字段(即entry.users.messages.date等,其中并非所有情况下都输入了数据)

–阿里
13年2月8日在22:23

“如果a.b为0,它将返回true”-不。 typeof a.b ===“ undefined” && a.b!= null-不必在第一部分之后进行第二部分,而只做if(“ a”中的“ b”)就更有意义

–伊恩
13年2月8日在22:23



@Ian是的,我显然是相反的意思,即使a.b为“ 0”,它也会返回false。不错的收获

–本杰明·格伦鲍姆(Benjamin Gruenbaum)
13年2月8日在22:24

@BenjaminGruenbaum听起来不错,不确定您是否要那样做。另外,我认为您需要typeof a.b!==“ undefined” && a.b!= null`-注意!==

–伊恩
13年2月8日在22:26

如果您不想沉迷于a.b && a.b.c && console.log(a.b.c),那么这是一致地记录未知数的唯一方法。

–布赖恩·克雷
13年2月8日在22:31

#3 楼

如果使用lodash,则可以使用其“具有”功能。它与本机“ in”相似,但允许路径。

var testObject = {a: {b: {c: 'walrus'}}};
if(_.has(testObject, 'a.b.c')) {
  //Safely access your walrus here
}


评论


最好,我们可以使用带有默认值的_.get()以便于阅读:_.get(object,'a.b.c','default');

–如果不是
18年6月18日在13:53



#4 楼

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

最简单的方法是使用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) { }


不确定是否有帮助。

评论


您无法安全地将其嵌套到所需的深度。如果未定义window.b怎么办?您将收到类型错误:无法使用'in'运算符在未定义的位置搜索'c'

–特雷弗
17年8月28日在23:34



#5 楼

尝试这个。如果未定义a.b,则它将保留if语句而没有任何异常。

if (a.b && a.b.c) {
  console.log(a.b.c);
}


#6 楼

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

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


#7 楼

如果您有lodash,则可以使用其.get方法

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


或为其指定默认值

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


#8 楼

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

评论


类似于lodash _.get

–Filype
16年7月21日在3:32

好喊!如果不需要lodash的其他功能,它仍然很有用。

–马丁斯
16年7月22日在14:04

#9 楼

如果使用Babel,您已经可以在@ babel / plugin-proposal-optional-chaining Babel插件中使用可选的链接语法。这将使您可以替换为:

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


,并替换为:

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


#10 楼

我喜欢曹寿光的回答,但我不喜欢每次执行调用时都将函数作为参数传递给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);
}


评论


它实际上与getSafe(myObj.x.c)不兼容。尝试使用最新版本的Chrome和Firefox。

–RickardElimää
19年1月30日在16:26



#11 楼

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

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

function getSafe(fn, defaultVal) {
    try {
        if (fn() === undefined) {
            return defaultVal
        } else {
            return fn();
        }

    } catch (e) {
        return defaultVal;
    }
}


评论


Hardy Le Roux对我的代码进行的改进版本不适用于let myObj = {} getSafe(()=> myObj.a.b,“ nice”),而我的却有效。有人解释为什么吗?

–曹寿光
7月23日在1:13



#12 楼

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> 




#13 楼

想象一下,当且仅当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同时具有值​​和计算上下文:


monad使值安全并适用函数。
计算上下文是在应用函数之前进行的空检查。

天真的实现将如下所示:

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

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


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




 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

我无法解释什么是monads,因为这不是本文的目的,并且那里的人们对此有更好的了解比我但是,正如Eric Elliot在历史博客文章中所说的,JavaScript Monads变得简单:


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




附录2

以下是我如何使用monetjs的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> 




#14 楼

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

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


#15 楼

我通常这样使用:

 var x = object.any ? object.any.a : 'def';


#16 楼

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



 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)