[Перевод] Избегайте неприятных ошибок «невозможно прочитать свойство неопределенного»

внешний интерфейс JavaScript

Uncaught TypeError: Cannot read property 'foo' of undefined.Это ужасная ошибка, с которой мы все сталкивались при разработке JavaScript. Возможно, API вернул неожиданное нулевое значение, а может быть, что-то еще, ошибка настолько распространена и широко распространена, что мы не можем сказать.

Недавно я столкнулся с проблемой, когда переменная среды по какой-то причине не была загружена, что вызвало всевозможные ошибки, смешанные с этой ошибкой передо мной. Какой бы ни была причина, оставить эту ошибку необработанной было бы катастрофой. Итак, как нам предотвратить возникновение этой проблемы в источнике?

Давайте найдем решение вместе.

Библиотека инструментов

Если вы уже используете какую-либо библиотеку инструментов в своем проекте, весьма вероятно, что в библиотеке уже есть функции, предотвращающие возникновение этой проблемы. в ладаше_.get(Документация) или в РамдеR.path(Документация) обеспечит безопасное использование объектов.

Если вы уже используете библиотеку инструментов, это уже кажется самым простым способом. Если вы не используете библиотеку инструментов, читайте дальше!

Используйте && для короткого замыкания

Интересным фактом о логических операторах в JavaScript является то, что они не всегда возвращают логические значения.Согласно описанию, "&&или||Возвращаемое значение оператора не обязательно должно быть логическим. Это одно из двух выражений операции. 』

дать&&Пример оператора, если логическое значение первого выражения ложно, то возвращается это значение. В противном случае используется значение второго выражения. Это показывает, что выражение0 && 1вернусь0(ложное значение), а выражение2 && 3вернусь3. если несколько&&Выражения объединены в цепочку, и они вернут первое значение false или последнее значение. Например,1 && 2 && 3 && null && 4вернусьnull,а также1 && 2 && 3вернусь3.

Так как же безопасно получить свойства вложенных объектов? Логические операторы в JavaScript "короткое замыкание". в этот&&В примере это означает, что выражение остановится, когда достигнет первого ложного значения.

const foo = false && destroyAllHumans();
console.log(foo); // false,人类安全了

В этом примереdestroyAllHumansне будет называться, потому что&&останавливает все операции после false

Это можно использовать для безопасного получения свойств вложенного объекта.

const meals = {
  breakfast: null, // 我跳过了一天中最重要的一餐! :(
  lunch: {
    protein: 'Chicken',
    greens: 'Spinach',
  },
  dinner: {
    protein: 'Soy',
    greens: 'Kale',
  },
};

const breakfastProtein = meals.breakfast && meals.breakfast.protein; // null
const lunchProtein = meals.lunch && meals.lunch.protein; // 'Chicken'

Помимо простоты, основным преимуществом этого подхода является его простота при работе с меньшим количеством вложений. Однако при доступе к глубоким объектам он может стать довольно многословным.

const favorites = {
  video: {
    movies: ['Casablanca', 'Citizen Kane', 'Gone With The Wind'],
    shows: ['The Simpsons', 'Arrested Development'],
    vlogs: null,
  },
  audio: {
    podcasts: ['Shop Talk Show', 'CodePen Radio'],
    audiobooks: null,
  },
  reading: null, // 开玩笑的 — 我热爱阅读
};

const favoriteMovie = favorites.video && favorites.video.movies && favorites.video.movies[0];
// Casablanca
const favoriteVlog = favorites.video && favorites.video.vlogs && favorites.video.vlogs[0];
// null

Чем глубже вложен объект, тем громоздче он становится.

"или единица"

Оливер Стил предлагает этот метод и подробно описывает его в своем блоге.«Блок Глава 1: ИЛИ Блок»Я постараюсь дать краткое объяснение здесь.

const favoriteBook = ((favorites.reading||{}).books||[])[0]; // undefined
const favoriteAudiobook = ((favorites.audio||{}).audiobooks||[])[0]; // undefined
const favoritePodcast = ((favorites.audio||{}).podcasts||[])[0]; // 'Shop Talk Show'

Подобно приведенному выше примеру короткого замыкания, этот метод работает, проверяя, является ли значение ложным. Если значение равно false, он попытается получить свойства пустого объекта. В приведенном выше примере значение Favorites.reading равно null, поэтому свойство books получено из пустого объекта. Это возвращает неопределенный результат, поэтому 0 будет использоваться для получения членов пустого массива.

Этот метод сравнивают с&&Преимущество метода в том, что он позволяет избежать дублирования имен свойств. В глубоко вложенных объектах это может быть значительным преимуществом. Основным недостатком является удобочитаемость — это не обычный шаблон, поэтому читателю может потребоваться некоторое время, чтобы понять, как он работает.

try/catch

в JavaScripttry...catchэто еще один способ безопасного получения свойств.

try {
  console.log(favorites.reading.magazines[0]);
} catch (error) {
  console.log("No magazines have been favorited.");
}

К сожалению, в JavaScripttry...catchОбъявления не являются выражениями, они не оцениваются как значения, как в некоторых языках. Это делает невозможным использование краткого оператора try для установки переменных.

Один из вариантовtry...catchОпределите переменную let раньше.

let favoriteMagazine;
try { 
  favoriteMagazine = favorites.reading.magazines[0]; 
} catch (error) { 
  favoriteMagazine = null; /* 任意默认值都可以被使用 */
};

Хотя это многословно, это работает для установки одной переменной (то есть, если переменная вас уже не пугает), однако записать их все вместе проблематично.

let favoriteMagazine, favoriteMovie, favoriteShow;
try {
  favoriteMovie = favorites.video.movies[0];
  favoriteShow = favorites.video.shows[0];
  favoriteMagazine = favorites.reading.magazines[0];
} catch (error) {
  favoriteMagazine = null;
  favoriteMovie = null;
  favoriteShow = null;
};

console.log(favoriteMovie); // null
console.log(favoriteShow); // null
console.log(favoriteMagazine); // null

Это приведет к тому, что все они вернутся к своим значениям по умолчанию, если какая-либо из попыток получить свойства потерпит неудачу.

Альтернативный подход состоит в том, чтобы обернуть его в повторно используемую служебную функцию.try...catch.

const tryFn = (fn, fallback = null) => {
  try {
    return fn();
  } catch (error) {
    return fallback;
  }
} 

const favoriteBook = tryFn(() => favorites.reading.book[0]); // null
const favoriteMovie = tryFn(() => favorites.video.movies[0]); // "Casablanca"

Обернув действие получения свойств объекта в функцию, вы можете отложить «небезопасный» код и передать его вtry...catch.

Основное преимущество этого метода в том, что он приобретает свойства совершенно естественно. Пока свойство инкапсулировано в функцию, к нему можно безопасно обращаться, возвращая указанное значение по умолчанию для несуществующих путей.

Слияние с объектом по умолчанию

Объединяя объект с объектом «по умолчанию» аналогичной структуры, мы можем гарантировать, что путь к получению свойства безопасен.

const defaults = {
  position: "static",
  background: "transparent",
  border: "none",
};

const settings = {
  border: "1px solid blue",
};

const merged = { ...defaults, ...settings };

console.log(merged); 
/*
  {
    position: "static",
    background: "transparent",
    border: "1px solid blue"
  }
*/

Однако обратите внимание, что будет переопределено не отдельное свойство, а весь вложенный объект.

const defaults = {
  font: {
    family: "Helvetica",
    size: "12px",
    style: "normal",
  },        
  color: "black",
};

const settings = {
  font: {
    size: "16px",
  }
};

const merged = { 
  ...defaults, 
  ...settings,
};

console.log(merged.font.size); // "16px"
console.log(merged.font.style); // undefined

Не делайте! Чтобы исправить это, нам нужно скопировать каждый вложенный объект аналогичным образом.

const merged = { 
  ...defaults, 
  ...settings,
  font: {
    ...defaults.font,
    ...settings.font,
  },
};

console.log(merged.font.size); // "16px"
console.log(merged.font.style); // "normal"

намного лучше!

Этот шаблон распространен для плагинов или компонентов, которые принимают большой настраиваемый объект, содержащий значения по умолчанию.

Дополнительным преимуществом этого подхода является то, что, написав объект по умолчанию, мы ввели документацию для описания этого объекта. К сожалению, в зависимости от размера и структуры данных копирование каждого вложенного объекта для слияния может привести к загрязнению.

будущее: необязательная цепочка

В настоящее время в предложении TC39 есть функция, называемая «необязательная цепочка вызовов». Этот новый оператор выглядит так:

console.log(favorites?.video?.shows[0]); // 'The Simpsons'
console.log(favorites?.audio?.audiobooks[0]); // undefined

?.Операторы работают путем короткого замыкания: если?.Левая часть оператора вычисляетnullилиundefined, все выражение возвращаетundefinedИ правая часть не будет рассчитана.

Чтобы иметь собственное значение по умолчанию, мы можем использовать||оператор для работы с неопределенными случаями.

console.log(favorites?.audio?.audiobooks[0] || "The Hobbit");

Какой метод мы должны использовать?

Ответ, как вы уже догадались, в старой поговорке «это зависит». Вероятно, это лучший вариант, если к языку добавлена ​​необязательная цепочка и получена необходимая поддержка браузера. Однако, если вы не из будущего, вам есть над чем подумать. Вы используете библиотеку инструментов? Насколько глубоко вложены ваши объекты? Вам нужно указать значения по умолчанию? Нам нужно применять разные методы в соответствии с разными сценариями.

Если вы обнаружите ошибки в переводе или в других областях, требующих доработки, добро пожаловать наПрограмма перевода самородковВы также можете получить соответствующие бонусные баллы за доработку перевода и PR. начало статьиПостоянная ссылка на эту статьюЭто ссылка MarkDown этой статьи на GitHub.


Программа перевода самородковэто сообщество, которое переводит высококачественные технические статьи из ИнтернетаНаггетсДелитесь статьями на английском языке на . Охват контентаAndroid,iOS,внешний интерфейс,задняя часть,блокчейн,товар,дизайн,искусственный интеллектЕсли вы хотите видеть более качественные переводы, пожалуйста, продолжайте обращать вниманиеПрограмма перевода самородков,официальный Вейбо,Знай колонку.