'정의되지 않은 속성을 읽을 수 없음'오류를 방지하는 방법은 무엇입니까?


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 2013

3
구조가 항목 전체에서 일관되지 않은 경우 존재 여부를 확인하는 데 문제가 있습니까? 정말, 나는 if ("b" in a && "c" in a.b). 그것은 "지루할"수 있지만, 그것은 당신이 불일치에 대해 얻는 것입니다 ... 정상적인 논리.
— Ian

2
존재하지 않는 속성에 액세스하는 이유는 무엇입니까? 객체가 어떻게 생겼는지 모르십니까?
— Bergi

9
나는 누군가가 모든 것을 충돌시키는 오류를 원하지 않는 이유를 이해할 수 있습니다. 객체의 속성이 존재하거나 존재하지 않을 때 항상 의존 할 수는 없습니다. 개체가 잘못된 이벤트를 처리 할 수있는 무언가가 있다면 코드가 훨씬 더 효율적이고 덜 취약합니다.
— SSH This

3
당신은 / 배열이 실제 상황에서, 부정 얼마나 많은 개체에 놀랄 것
— OneMoreQuestion

답변:


120

업데이트 :

  • 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
getSafe(() => obj.a.lot.of.properties);

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

작업 스 니펫 :

자세한 내용은 이 기사 를 참조하십시오.


2
나는 그것을 사랑한다! 내가 추가 할 유일한 것은 catch 내부의 console.warn이므로 오류를 알지만 계속 진행됩니다.
— Rabbi Shuki Gur

다시 던지지 않고 모든 예외를 포착 하는 것은 좋지 않으며 일반적으로 예상되는 실행 흐름의 일부로 예외를 사용하는 것도 좋지 않습니다.이 경우에는 꽤 잘 포함되어 있습니다.
— hugo

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 구조화 된 데이터베이스 항목을 다루고 있습니다 (예 : 모든 케이스에 데이터가 입력되지 않은 경우 항목 .users.messages.date 등)
— Ari

"ab가 0이면 true를 반환합니다."-아니요. typeof a.b === "undefined" && a.b!=null-첫 번째 부분 이후에 두 번째 부분을 수행 할 필요가 if ("b" in a)
— Ian

@Ian 그래, 나는 분명히 다른 방향으로 그것을 의미했다. ab가 "0"이더라도 false를 반환 할 것이다. 좋은 캐치
— Benjamin Gruenbaum 2013

@BenjaminGruenbaum 좋은 것 같네요, 당신이 그 뜻인지 확신하지 못했습니다. 또한, 나는 당신이 원한다고 생각합니다 typeof a.b !== "undefined"&& ab! = null`!==
— Ian

3
ab && abc && console.log (abc)를 지루하게하고 싶지 않다면 이것이 알려지지 않은 것을 일관되게 기록하는 유일한 방법입니다.
— Brian Cray 2013

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

12

이 시도. a.b정의되지 않은 경우 if예외없이 문을 떠납니다 .

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

5

이것은 깊거나 복잡한 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;
}

5

당신이있는 경우 lodash 당신은 사용할 수있는 .get방법을

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

또는 기본값 제공

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

4

나는 undefsafe를 종교적으로 사용 합니다. 요청한 값을 얻거나 "정의되지 않음"을 반환 할 때까지 각 수준을 개체로 테스트합니다. 그러나 오류는 없습니다.


2
그게 lodash 비슷_.get
— Filype

좋은 소리! lodash의 다른 기능이 필요하지 않은 경우에도 유용합니다.
— martinedwards


3

나는 Cao Shouguang의 대답을 좋아하지만 호출을 할 때마다 getSafe 함수에 매개 변수로 함수를 전달하는 것을 좋아하지 않습니다. 간단한 매개 변수와 순수한 ES5를 허용하도록 getSafe 함수를 수정했습니다.

/**
* 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를 사용해 보았습니다.
— Rickard Elimää

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")에서 작동하지 않지만 내 코드는 작동합니다. 아무도 이유를 설명합니까?
— Cao Shouguang

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

null이 아닌 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 모나드.

어쩌면 모나드는 값과 계산 컨텍스트를 모두 수신을 원하는 분야

  1. 모나드는 값을 안전하게 유지하고 함수를 적용합니다.
  2. 계산 컨텍스트는 함수를 적용하기 전에 null 검사입니다.

순진한 구현은 다음과 같습니다.

⚠️이 구현은 단지 설명을위한 것입니다! 이것은 그것이 행해지는 방법이 아니며 여러 단계에서 잘못되었습니다. 그러나 이것은 내가 말하는 것에 대한 더 나은 아이디어를 줄 것입니다.

보시다시피 아무것도 깨질 수 없습니다.

  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이 hist 블로그 게시물 JavaScript Monads Made Simple 에서 말했듯이 :

기술 수준이나 범주 이론에 대한 이해와 관계없이 모나드를 사용하면 코드 작업이 더 쉬워집니다. 모나드를 활용하지 못하면 코드 작업이 더 어려워 질 수 있습니다 (예 : 콜백 지옥, 중첩 된 조건부 분기, 더 자세한 정보).


부록 2

다음을 사용하여 문제를 해결하는 방법은 다음과 같습니다. Maybe 모나드를 입니다.

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 x = object.any ? object.any.a : 'def';

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)

당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.