如何避免“无法读取未定义的属性”错误?


118

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

// 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(如果存在)进行操作,而不会引发错误?


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

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

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

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

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

Answers:


120

更新

  • 如果您使用符合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
getSafe(() => obj.a.lot.of.properties);

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

工作片段:

有关详细信息,请参见本文


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

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

50

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

你总是可以做

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

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

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

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

另外,从侧面说,您不应使用类似这样的语句,if(a.b)因为如果ab为0或即使为“ 0”,它将返回false。而是检查是否a.b !== undefined


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

“如果ab为0,它将返回true”-不。typeof a.b === "undefined" && a.b!=null-不必在第一部分之后再做第二部分,而这样做就更有意义if ("b" in a)
Ian

@Ian是的,我显然是相反的意思,即使ab为“ 0”,它也会返回false。
精彩

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

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

14

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

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

不确定是否有帮助。


9
您无法安全地将其嵌套到所需的深度。如果window.b未定义怎么办?您将收到类型错误:Cannot use 'in' operator to search for 'c' in undefined
Trevor

13

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

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

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

12

试试这个。如果a.b未定义,它将使该if语句无任何异常。

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

5

当使用深度或复杂的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;
}

5

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

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

或给它一个默认值

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

4

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


2
类似于lodash_.get
Filype '16

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


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

它实际上不起作用getSafe(myObj.x.c)。尝试使用最新版本的Chrome和Firefox。
RickardElimää19年

2

在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.ab,“ nice”),而我的却有效。有人解释为什么吗?
曹寿光

2

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>


2

想象一下,x如果且仅当x非null时,我们要对它应用一系列函数:

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。

也许单子同时拥有一个值和计算上下文:

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

天真的实现看起来像这样:

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

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

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

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


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


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)

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.