【问题标题】:How to avoid 'cannot read property of undefined' errors?如何避免“无法读取未定义的属性”错误?
【发布时间】:2019-12-28 15:39:28
【问题描述】:

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

// 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 ("b" in a &amp;&amp; "c" in a.b)。这可能是“乏味的”,但这就是你得到的不一致......正常逻辑。
  • 为什么要访问不存在的属性,为什么不知道对象的样子?
  • 我可以理解为什么有人不希望错误导致一切崩溃。您不能总是依赖对象的属性来存在或不存在。如果您有一些东西可以处理对象格式错误的事件,那么您的代码将更加高效且不那么脆弱。
  • 你会惊讶于现实生活中有多少对象/数组格式不正确

标签: javascript


【解决方案1】:

更新

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

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

一个快速的解决方法是在 ES6 arrow function 中使用 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'));

【讨论】:

  • 我喜欢它!我唯一要添加的是 catch 中的 console.warn,这样您就知道错误但它会继续。
  • 捕获所有异常而不重新抛出是不好的,通常使用异常作为预期执行流程的一部分也不是很好——尽管在这种情况下它被很好地控制了。
  • 超级!!!添加“?”工作
【解决方案2】:

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

你总是可以做到的

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

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

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

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

另外,附带说明一下,您不应该使用像 if(a.b) 这样的语句,因为如果 a.b 为 0 或即使它为“0”,它将返回 false。而是检查a.b !== undefined

【讨论】:

  • 关于您的第一次编辑:这是合理的;我正在处理 JSON 结构化数据库条目,这样对象将缩放多个级别的字段(即,entry.users.messages.date 等,并非所有案例都输入了数据)
  • "如果 a.b 为 0,它将返回 true" - 不。 typeof a.b === "undefined" &amp;&amp; a.b!=null - 没有必要在第一部分之后再做第二部分,只做if ("b" in a) 更有意义
  • @Ian 是的,我显然是反过来的,即使 a.b 为“0”,它也会返回 false。不错的收获
  • @BenjaminGruenbaum 听起来不错,但不确定您是不是这个意思。另外,我想你想要typeof a.b !== "undefined" && a.b!=null` - 注意!==
  • 如果您不想让 a.b && a.b.c && console.log(a.b.c) 变得乏味,那么这是持续记录未知数的唯一方法。
【解决方案3】:

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

最简单的方法是使用in operator

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 未定义怎么办?你会得到一个类型错误:Cannot use 'in' operator to search for 'c' in undefined
【解决方案4】:

如果你使用lodash,你可以使用他们的“has”功能。它类似于原生的“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');
【解决方案5】:

试试这个。如果a.b 未定义,则将离开if 语句,没有任何异常。

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

【讨论】:

    【解决方案6】:

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

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

    用这个:

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

    【讨论】:

      【解决方案7】:

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

      【讨论】:

      • 类似于lodash _.get
      • 好声音!如果您不需要 lodash 的其他功能,仍然很有用。
      【解决方案8】:

      这是处理深层或复杂 json 对象时的常见问题,因此我尽量避免 try/catch 或嵌入会使代码不可读的多个检查,我通常在我的所有 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;
      }
      

      【讨论】:

        【解决方案9】:

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

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

        或者给它一个默认值

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

        【讨论】:

          【解决方案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。
          【解决方案11】:

          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
          &lt;script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.js"&gt;&lt;/script&gt;

          【讨论】:

            【解决方案12】:

            假设我们想对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. 如果在任何时候,值变为 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

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

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


            附录 2

            以下是我如何使用来自

            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>

            【讨论】:

              【解决方案13】:

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

              【讨论】:

              • Hardy Le Roux 对我的代码的改进版本不适用于 let myObj ={} getSafe(()=>myObj.a.b, "nice"),而我的工作。有人解释原因吗?
              【解决方案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)

                  【讨论】:

                    【解决方案17】:

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

                    检查您的函数参数。

                    如果你有一个像const x({ a }) =&gt; { } 这样的函数,并且你在没有参数的情况下调用它x();将 = {} 附加到参数:const x({ a } = {}) =&gt; { }


                    我有什么

                    我有这样的功能:

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

                    【讨论】:

                      【解决方案18】:

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

                      a?.b?.c?.d?.func?.()
                      

                      【讨论】:

                        猜你喜欢
                        • 1970-01-01
                        • 2020-10-15
                        • 1970-01-01
                        • 2021-12-31
                        相关资源
                        最近更新 更多