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

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

// 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,如果它存在,但没有抛出一个错误?

403014 次浏览

你所做的引起了一个例外(理所当然的)。 你可以这样做:

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

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

你为什么访问数据,6层嵌套,你不熟悉? 什么用例证明这一点?

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

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

如果我没有理解错你的问题,你需要最安全的方法来确定一个对象是否包含一个属性。

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

不知道这样有没有用。

如果您使用的是 浪荡,则可以使用它们的 has函数。它类似于本机“ in”,但是允许路径。

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

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

在处理深度或复杂的 json 对象时,这是一个常见的问题,所以我尽量避免 try/catch 或嵌入多个检查,这会使代码不可读,我通常在所有的处理器中使用这段代码来完成这项工作。

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

更新 :

  • 如果根据 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'));

试试这个。如果 a.bundefined,它将毫无例外地保留 if语句。

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

在 str 的回答中,如果属性未定义,则返回值“ unDefinition”,而不是设置的默认值。这有时会导致虫子。下面将确保当属性或对象未定义时始终返回 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;
}
}

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

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

我通常这样说:

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

我喜欢曹寿光的回答,但是我不喜欢每次调用 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);
}

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>

如果使用 Babel,则已经可以在 @ Babel/plugin-建议-可选-链接 Babel 插件中使用可选的链接语法。这样你就可以取代它:

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

用这个:

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

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

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

或者给它一个默认值

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

假设我们想对 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);

正如您所看到的,如果没有适当的抽象,我们最终将一遍又一遍地复制代码。这样的抽象已经存在: 也许吧单子。

也许吧单子包含一个值和一个计算上下文:

  1. 单子保证值的安全性,并对其应用函数。
  2. 计算上下文是应用函数之前的空检查。

一个幼稚的实现应该是这样的:

Something 此实现仅用于演示目的!这不是我们应该做的,而且在许多层面上都是错误的。然而,这应该给你一个更好的想法,我在谈论什么。

正如你所看到的,没有什么是可以打破的:

  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

我不能解释什么是单子,因为这不是这篇文章的目的,有人在这方面比我做得更好。然而,正如埃里克•埃利奥特(Eric Elliot)在其博客文章 JavaScript Monads 变得简单中所言:

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


附录2

以下是我如何使用 中的 也许吧单子解决您的问题

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>

通过在获取属性之前给出默认值,可以避免出现错误

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)

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

检查你的函数参数。

如果您有一个类似于 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();

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

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