Як уникнути помилок "не вдається прочитати властивість невизначених"?


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заявити. Однак, масив, який містить дико гетерогенні елементи, виглядає для мене проблемою дизайну.
мільйозу

3
Якщо ваша структура не відповідає всім елементам, то що не так у тому, щоб перевірити наявність? Дійсно, я б користувався if ("b" in a && "c" in a.b). Це може бути "нудно", але це те, що ви отримуєте за непослідовність ... нормальна логіка.
Ян

2
Чому ви звертаєтесь до неіснуючих властивостей, чому не знаєте, як виглядають об'єкти?
Бергі

9
Я можу зрозуміти, чому хтось не хотів, щоб помилка розбила все. Ви не завжди можете розраховувати на те, що властивості об'єкта існують чи не існують. Якщо у вас є щось на місці, яке може вирішити випадок, коли об'єкт неправильно сформований, то ви код набагато ефективніший і менш крихкий.
SSH Цього

3
Ви здивуєтеся тому, скільки об’єктів / масивів неправильно сформовано в реальних ситуаціях
OneMoreQuestion

Відповіді:


120

Оновлення :

  • Якщо ви використовуєте JavaScript відповідно до ECMAScript 2020 або пізнішої версії, див. Додатковий ланцюжок .
  • TypeScript додав підтримку додаткового ланцюжка у версії 3.7 .
// use it like this
obj?.a?.lot?.of?.properties

Рішення для JavaScript перед ECMASCript 2020 або TypeScript старішою за версію 3.7 :

Швидке вирішення - це функція допоміжної спроби вловлювання за допомогою функції стрілки 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
Я це люблю! Єдине, що я хотів би додати, - це console.warner всередині улову, щоб ви знали про помилку, але вона продовжується.
Раббі Шукі Гур

Ловити всі винятки без повторного кидання - це погано , і, як правило, використання винятків як частина очікуваного потоку виконання також не є великим, хоча в цьому випадку він досить добре міститься.
Гуго

50

Те, що ви робите, викликає виняток (і це справедливо).

Ви завжди можете це зробити

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

Але я не хотів би замість цього думати про ваш випадок використання.

Чому ви отримуєте доступ до даних, 6 рівнів, вкладених вам, незнайомих? Який випадок використання це виправдовує?

Зазвичай ви хочете фактично перевірити, який саме предмет ви маєте справу.

Крім того, в бічній примітці не слід використовувати такі заяви, як, if(a.b)оскільки вона поверне помилкову, якщо ab дорівнює 0 або навіть якщо вона "0". Замість цього перевірте, чиa.b !== undefined


1
Що стосується вашої першої редакції: це виправдано; Я маю справу з записами структуризованих баз даних JSON таким чином, що об’єкти будуть масштабувати кілька рівнів полів (тобто. Entries.users.messages.date тощо), де не у всіх випадках введені дані)
Арі,

"воно повернеться істинним, якщо ab дорівнює 0" - nope. typeof a.b === "undefined" && a.b!=null- зайве робити другу частину після першої, і має сенс просто зробитиif ("b" in a)
Ян

@ Так, я, очевидно, мав на увазі, що це навпаки, воно повернеться помилковим, навіть якщо ab - "0". Хороший улов
Бенджамін Грюнбаум

@BenjaminGruenbaum Звучить добре, не був впевнений, чи ти це мав на увазі. Крім того, я думаю, що ви хочете typeof a.b !== "undefined" && ab! = Null` - зауважте!==
Ian

3
Якщо ви не хочете робити копію ab && abc && console.log (abc), це єдиний спосіб послідовно входити в систему невідомих.
Брайан Крей

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
Тревор,

13

Якщо ви використовуєте lodash , ви можете використовувати їх функцію "has". Він схожий з рідним "в", але дозволяє проходити шляхи.

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, тому я намагаюся уникати спроб / лову чи вбудовування декількох перевірок, які б зробили код нечитабельним, я зазвичай використовую цей маленький фрагмент коду у всіх моїх процедурах, щоб виконувати цю роботу.

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

Якщо у вас є лодаш, ви можете використовувати його .getметод

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

або надати йому значення за замовчуванням

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

4

Я використовую undefsafe релігійно. Він перевіряє кожен рівень вниз у вашому об’єкті, поки він не отримає значення, про яке ви просили, або повернеться "невизначеним". Але ніколи не помилки.


2
що схоже на лодаш_.get
Філіпе

Гарний крик! Ще корисно, якщо вам не потрібні інші особливості лодашу.
понедельник


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.
Рікард Елімяа

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

Удосконалена версія мого коду Харді Ле Ру не працює, нехай myObj = {} getSafe (() => myObj.ab, "приємно"), поки моя працює. Хтось пояснює, чому?
Чао Шугуанг

2

Лодаш має 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 це недійсне значення:

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

Як ви можете бачити без належної абстракції, ми зрештою повторюватимемо дублювання коду. Така абстракція вже існує: Монада Можливо .

The Монада Можливо" містить як значення, так і обчислювальний контекст:

  1. Монада зберігає значення в безпеці і застосовує до нього функції.
  2. Обчислювальний контекст - це нульова перевірка перед застосуванням функції.

Наївна реалізація виглядала б так:

⚠️ Ця реалізація призначена лише для ілюстрації! Це не так, як це слід робити, і неправильно на багатьох рівнях. Однак це повинно дати вам краще уявлення про те, про що я говорю.

Як бачите, нічого не може зламатися:

  1. Ми застосовуємо цілий ряд функцій до нашого значення
  2. Якщо в будь-який момент значення стає нульовим (або невизначеним), ми більше не застосовуємо жодної функції.

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

Я не можу пояснити, що таке монади, оскільки це не мета цієї посади, і люди там кращі, ніж я. Однак, як сказав Ерік Елліот у своєму блозі hist Mostov, зроблений простим :

Незалежно від рівня вашої кваліфікації чи розуміння теорії категорій, використання монад полегшує ваш код. Якщо не скористатися монадами, це може ускладнити роботу вашого коду (наприклад, пекельний зворотний виклик, вкладені умовні гілки, більше багатослів’я).


Додаток 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>


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)

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.