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


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.