7 порад щодо керування undefined у JavaScript

31 хв. читання

Коли я почав вивчати JavaScript вісім років тому, для мене було дивним існування undefined та null. Яка ж між ними різниця? Вони обидва дорівнюють порожнім значенням і, крім того, порівняння null == undefined має значення true.

Більшість сучасних мов, таких як Ruby, Python або Java мають одне значення null (nil або null), і це здається розумним підходом.

У JavaScript, інтерпретатор повертає undefined при зверненні до змінної або властивості об'єкта, яка ще не ініціалізована. Наприклад:

let company;  
company;    // => undefined  
let person = { name: 'John Smith' };  
person.age; // => undefined  

З іншого боку, null є посиланням на відсутній об'єкт. JavaScript сам по собі не встановлює значення змінних або властивості об'єкта як null.

Деякі нативні методи, такі як String.prototype.match() можуть повернути null, щоб позначити відсутність об'єкту. Подивіться на зразок:

let array = null;  
array;                // => null  
let movie = { name: 'Starship Troopers',  musicBy: null };  
movie.musicBy;        // => null  
'abc'.match(/[0-9]/); // => null  

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

Часто такі ризиковані дії породжують помилки типу undefined. Ось повідомлення загальних помилок:

  • TypeError: 'undefined' is not a function
  • TypeError: Cannot read property '' of undefined
  • і схожі type errors.

JavaScript-розробник зможе зрозуміти іронію цього жарту:

function undefined() {  
  // Проблему вирішено
}

Для того, щоб знизити ризик виникнення таких помилок, ви повинні знати випадки, коли undefined генерується. Важливо попередити їх появу та поширення всередині програми, адже це підвищує стійкість коду.

Детально дослідимо undefined і його вплив на безпеку коду.

1. Що таке undefined

JavaScript має 6 примітивних типів:

-Boolean: true або false

-Number: 1, 6.7, 0xFF

-String: "Gorilla and banana"

-Symbol: Symbol("name") (ES2015)

-Null: null

-Undefined: undefined.

І відокремлений object type: {name: "Dmitri"}, ["apple", "orange"].

З цих 6-ти примітивних типів, undefined є особливим значенням з власним типом Undefined. Згідно зі специфікацією ECMAScript:

Undefined value - примітивне значення, яке використовується тоді, коли змінній не було присвоєно значення.

Стандарт чітко визначає, що ви отримаєте невизначене значення при доступі до неініціалізованих змінних, відсутніх властивостей об'єкта, відсутніх елементів масиву. Наприклад:

let number;  
number;     // => undefined  
let movie = { name: 'Interstellar' };  
movie.year; // => undefined  
let movies = ['Interstellar', 'Alexander'];  
movies[3];  // => undefined

В прикладі показано доступ до:

-неініціалізованої змінної number

-відсутньої властивості об'єкта movie.year

-відсутнього елементу масиву movies[3]

Всі вони прирівнюються до undefined.

Специфікація ECMAScript визначає тип undefined:

Undefined type є типом з єдиним значенням – undefined.

У цьому коді, оператор typeof повертає рядок 'undefined' для значення undefined:

typeof undefined === 'undefined'; // => true 

Звичайно, typeof працює добре. Щоб перевірити, чи містить змінна значення undefined:

let nothing;  
typeof nothing === 'undefined';   // => true

2. Загальні сценарії, які створюють undefined

2.1 Неініціалізовані змінні

Оголошена змінна, якій ще не призначено значення (неініціалізована), за замовчуванням є undefined.

Дуже просто:

let myVariable;  
myVariable; // => undefined 

myVariable оголошується і ще не ініціалізується. Доступ до змінної поверне значення undefined.

Ефективний підхід до вирішення проблеми неініціалізованих змінних: коли це можливо привласнити початкове значення. Чим менше змінна існує в неініціалізованому стані, тим краще. В ідеалі ви повинні привласнити значення відразу після оголошення const myVariable = 'Initial value', але це не завжди можливо.

Порада №1: Надавайте перевагу const, в інших випадках використовуйте let, але скажіть до побачення var

На мій погляд, одна з кращих можливостей ECMAScript 2015 – новий спосіб оголошення змінних, використовуючи const і let. Це великий крок вперед, адже ці оголошення є блоками області видимості (на відміну від старої функції var).

Коли змінна приймає значення один раз і назавжди, я рекомендую використовувати оголошення const. Це створює незмінне зв'язування.

Однією з приємних особливостей const є те, що ви повинні привласнити початкове значення для змінної const myVariable = 'initial'. Змінна не піддається впливу неініціалізованого стану, і, таким чином, доступ до undefined стає просто неможливим.

Перевіримо функцію, яка перевіряє чи є слово паліндромом:

function isPalindrome(word) {  
  const length = word.length;
  const half = Math.floor(length / 2);
  for (let index = 0; index < half; index++) {
    if (word[index] !== word[length - index - 1]) {
      return false;
    }
  }
  return true;
}
isPalindrome('madam'); // => true  
isPalindrome('hello'); // => false  

Змінні length і half отримують значення один раз. Тому розумно оголосити їх const, оскільки ці змінні не зміняться.

Якщо вам необхідно переприсвоїти значення змінній, застосовуйте декларацію let. Кожного разу, коли це можливо, привласнюйте початкове значення змінній відразу ж, наприклад let index = 0.

Як щодо var? З точки зору ES2015, моя пропозиція відмовитися від його використання взагалі.

Ви можете оголосити змінну var десь в кінці області видимості функції, але все ж доступ до змінної може бути виконаний перед оголошенням: і ви отримаєте undefined.

function bigFunction() {  
  // code...
  myVariable; // => undefined
  // code...
  var myVariable = 'Initial value';
  // code...
  myVariable; // => 'Initial value'
}
bigFunction();  

myVariable доступна і містить undefined ще до оголошення: var myVariable = 'Initial value'.

А ось змінна let (в тому числі й const) не може бути доступна ще до рядка оголошення. Це відбувається тому, що змінна знаходиться в мертвій зоні (temporal dead zone) перед оголошенням. І це добре, тому що у вас менше шансів отримати undefined.

Наведений вище приклад перепишемо через let (замість var). Він викидає помилку ReferenceError, оскільки змінна в мертвій зоні не доступна.

function bigFunction() {  
  // code...
  myVariable; // => Throws 'ReferenceError: myVariable is not defined'
  // code...
  let myVariable = 'Initial value';
  // code...
  myVariable; // => 'Initial value'
}
bigFunction();  

Використовуючи const для сталих змінних або let для інших змінних, ви отримаєте набагато менше неініціалізованих змінних.

Порада 2: Зростання пов'язаності

Пов'язаність характеризує ступінь, в якій елементи модуля (простір імен, клас, метод, блок коду) пов'язані один з одним. Вимірювання пов'язаності зазвичай описується як висока пов'язаність або низька пов'язаність.

Висока пов'язаність є кращою, оскільки вона пропонує розробити елементи модуля так, щоб зосередитися виключно на одній меті. Це робить модуль:

-зосередженим і зрозумілим: стає легше зрозуміти, що робить модуль

-таким, яким легко керувати та який легше реорганізувати: зміна в одному модулі впливає на іншу невелику кількість модулів

-придатним до повторного використання: зосередивши увагу на одному завданні, модуль простіше використовувати повторно

-тестованим: вам легше перевірити модуль, який зосереджений на одному завданні

Висока пов'язаність, яка супроводжується слабким зв'язком є характеристикою добре розробленої системи.

Блок коду сам по собі може розглядатися як невеликий модуль. Для того, щоб отримати вигоду із високої пов'язаності, ви повинні оголошувати змінні якомога ближче до блоку коду, який використовує їх.

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

Класичний приклад непотрібного існування змінної поза циклом for:

function someFunc(array) {  
  var index, item, length = array.length;
  // some code...
  // some code...
  for (index = 0; index < length; index++) {
    item = array[index];
    // some code...
  }
  return 'some result';
}

index, item і length змінні оголошуються на початку тіла функції. Однак вони використовуються тільки ближче до кінця. Так яка ж проблема з таким підходом?

index, item і length мають тривалий термін існування в усій області видимості функції, але на це немає ніяких підстав. Кращий підхід – перемістити ці змінні як можна ближче до їх місця використання:

function someFunc(array) {  
  // some code...
  // some code...
  const length = array.length;
  for (let index = 0; index < length; index++) {
    const item = array[index];
    // some 
  }
  return 'some result';
}

Змінні index і item існують тільки в блоці for. Вони не мають ніякого сенсу поза for. Змінна length оголошена ближче до місця її використання.

Чому модифікована версія краще, ніж початкова? Подивимося:

  • Змінні не піддаються неініціалізованому стану, таким чином, ви не маєте ніякого ризику отримати змінну типу undefined
  • Переміщення змінних якомога ближче до їх місця використання підвищує читабельність коду
  • Код з високою пов'язаністю легше реорганізувати та виділяти його в окремі функції за необхідності

2.2. Доступ до відсутньої властивості

При доступі до відсутньої властивості об'єкта, JavaScript повертає undefined.

Продемонструємо приклад:

let favoriteMovie = {  
  title: 'Blade Runner'
};
favoriteMovie.actors; // => undefined

favoriteMovie є об'єктом з однією властивістю title. Доступ до відсутньої властивості actors, за допомогою favoriteMovie.actors оцінюється як undefined.

Сам по собі доступ до відсутньої властивості не видасть помилку. Реальна проблема виникає при спробі отримати дані з відсутнього значення. Це найбільш поширена пастка з undefined, відображена в добре відомій помилці TypeError: Cannot read property of undefined.

Трохи змінимо попередній фрагмент коду, щоб проілюструвати TypeError:

let favoriteMovie = {  
  title: 'Blade Runner'
};
favoriteMovie.actors[0];  
// TypeError: Cannot read property '0' of undefined

favoriteMovie не має властивості actors, тому favoriteMovie.actors має значення undefined. В результаті доступ до першого елемента зі значенням undefined, за допомогою favoriteMovie.actors[0] викидує TypeError.

Нестрогий характер JavaScript дозволяє отримати доступ до відсутніх властивостей, тому це є джерелом плутанини: властивість може бути встановлена або не встановлена. Ідеальний спосіб обійти цю проблему: обмежити можливість об'єкту завжди визначати ті властивості, які він має.

На жаль, ви часто не маєте контролю над об'єктами, з якими ви працюєте. Такі об'єкти можуть мати різний набір властивостей в різних сценаріях. Таким чином, ви повинні обробляти всі ці сценарії вручну.

Реалізуємо функцію append(array, toAppend), яка додає на початок і/або в кінець масиву нові елементи. Параметр toAppend приймає об'єкт з властивостями:

  • first: Елемент вставлений на початку array
  • last: Елемент вставлений в кінці array.

Функція повертає новий екземпляр масиву, не змінюючи вихідний масив. Перша версія append() може виглядати наступним чином:

function append(array, toAppend) {  
  const arrayCopy = array.slice();
  if (toAppend.first) {
    arrayCopy.unshift(toAppend.first);
  }
  if (toAppend.last) {
    arrayCopy.push(toAppend.last);
  }
  return arrayCopy;
}
append([2, 3, 4], { first: 1, last: 5 }); // => [1, 2, 3, 4, 5]  
append(['Hello'], { last: 'World' });     // => ['Hello', 'World']  
append([8, 16], { first: 4 });            // => [4, 8, 16] 

Потрібно обов'язково перевірити, чи існують ці властивості first та last в toAppend.

Властивість-аксесор має значення undefined, якщо властивості не існує. Хочеться перевірити чи дійсно first або last властивості присутні, щоб перевірити їх на undefined. Зробимо перевірку в умовах if(toAppend.first){} і if(toAppend.last){}...

Не так швидко! Існує серйозний недолік цього підходу. undefined, оскільки і false, null, 0, NaN і '' є falsy-значеннями.

У поточній реалізації append(), функція не дозволяє вставити falsy-елементи:

append([10], { first: 0, last: false }); // => [10]

0 і false є falsy. Тому що if(toAppend.first){} і if(toAppend.last){} насправді порівнюють проти falsy, ці елементи не вставляються в масив. Функція повертає вихідний масив [10] без змін.

Поради нижче пояснять, як правильно перевірити існування властивості.

Порада 3: Перевіряйте наявність властивості

JavaScript пропонує безліч способів, щоб визначити, чи об'єкт має певну властивість:

  • obj.prop !== undefined: порівнюється безпосередньо з undefined
  • typeof obj.prop !== 'undefined': перевірка типу значення властивості
  • obj.hasOwnProperty('prop'): перевірка, чи має об'єкт власну властивість
  • 'prop' in obj: перевірка, чи має об'єкт власну або успадковану властивість

Моя рекомендація - використовувати оператор in. Він має короткий синтаксис. Оператор присутності in передбачає чіткий намір перевірки: чи має об'єкт конкретну властивість, без доступу до фактичного значення властивості.

obj.hasOwnProperty('prop') також є хорошим рішенням. Воно трохи довше ніж оператор in та перевірка відбувається тільки у власних властивостях об'єкта.

Інші 2 способи порівняння з undefined також мають місце... Але мені здається, що obj.prop !== undefined і typeof obj.prop !== 'undefined' виглядають багатослівними та дивними.

Поліпшимо функцію append(array, toAppend) за допомогою оператора in:

function append(array, toAppend) {  
  const arrayCopy = array.slice();
  if ('first' in toAppend) {
    arrayCopy.unshift(toAppend.first);
  }
  if ('last' in toAppend) {
    arrayCopy.push(toAppend.last);
  }
  return arrayCopy;
}
append([2, 3, 4], { first: 1, last: 5 }); // => [1, 2, 3, 4, 5]  
append([10], { first: 0, last: false });  // => [0, 10, false]

'first' in toAppend'last' in toAppend) буде true, якщо відповідна властивість існує та false, якщо ні.

Використання оператора in усуває проблему з використанням falsy елементів 0 і false. Тепер, додавши ці елементи на початку і в кінці [10] маємо очікуваний результат [0, 10, false].

Порада 4: Деструктурування для отримання доступу до властивостей об'єкта

При зверненні до властивості об'єкта, іноді необхідно вказати значення за замовчуванням, якщо властивості не існує.

Ви можете використовувати in в супроводі з тернарним оператором для цього:

const object = { };  
const prop = 'prop' in object ? object.prop : 'default';  
prop; // => 'default'  

Використання тернарного оператора стає складнішим, коли число об'єктів для перевірки збільшується. Для кожної властивості ви повинні створити новий рядок коду для обробки за замовчуванням, збільшуючи кількість подібних тернарних операторів.

Для того, щоб використовувати більш витончений підхід, познайомимося з великою особливістю ES2015, яка називається об'єктне деструктурування.

Об'єктне деструктурування дозволяє видобувати значення властивостей об'єкта безпосередньо в змінні і встановлювати значення за замовчуванням, якщо властивість не існує. Маємо зручний синтаксис для уникнення проблем з undefined.

Дійсно, видобування властивості виглядає коротко і ясно:

const object = {  };  
const { prop = 'default' } = object;  
prop; // => 'default'  

Щоб побачити на прикладі, визначимо функцію, яка бере рядок в лапки.

quote(subject, config) приймає перший аргумент , як рядок, який буде братися в лапки. Другий аргумент config є об'єктом із властивостями:

  • char: символ, наприклад '(апостроф) або "(подвійні лапки). Значення за замовчуванням: ".
  • skipIfQuoted: логічне значення , щоб пропустити операцію взяття рядку в лапки, якщо рядок вже взято в лапки. Значення за замовчуванням: true.

Застосовуючи переваги об'єктної деструктуризації, реалізуємо quote():

function quote(str, config) {  
  const { char = '"', skipIfQuoted = true } = config;
  const length = str.length;
  if (skipIfQuoted
      && str[0] === char
      && str[length - 1] === char) {
    return str;
  }
  return char + str + char;
}
quote('Hello World', { char: '*' });        // => '*Hello World*'  
quote('"Welcome"', { skipIfQuoted: true }); // => '"Welcome"'  

const { char = '"', skipIfQuoted = true } = config деструктуруюче оголошення в один рядок витягує char і skipIfQuoted з config об'єкта. Якщо деякі властивості не доступні в config об'єкті, призначаються значення за замовчуванням: ' " ' для char і false для skipIfQuoted.

Функцію все ще можна покращити.

Перенесемо деструктуруюче оголошення прямо в розділ параметрів. І встановимо значення за замовчуванням (порожній об'єкт { }) для config параметра, щоб пропустити другий аргумент, коли настройок за замовчуванням буде достатньо.

function quote(str, { char = '"', skipIfQuoted = true } = {}) {  
  const length = str.length;
  if (skipIfQuoted
      && str[0] === char
      && str[length - 1] === char) {
    return str;
  }
  return char + str + char;
}
quote('Hello World', { char: '*' }); // => '*Hello World*'  
quote('Sunny day');                  // => '"Sunny day"'  

Зверніть увагу на заміну параметру config в сигнатурі функції на деструктуруюче оголошення. Мені це подобається: quote() стає на один рядок коротше.

= {} у правій стороні деструктуруючого оголошення гарантує те, що використовується порожній об'єкт, якщо другий аргумент не заданий взагалі quote('Sunny day').

Деструктуризація – потужний засіб, який ефективно обробляє властивості об'єктів. Мені подобається можливість вказати значення за замовчуванням, яке буде повернуто, якщо вказана властивість не існує. В результаті, можна уникнути проблем, пов'язаних з обробкою undefined.

Порада 5: Заповніть об'єкт властивостями за замовчуванням

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

ES2015 Object.assign(target, source1, source2, ...) копіює значення всіх перелічуваних власних властивостей з одного або декількох вихідних об'єктів в цільовий об'єкт. Функція повертає цільовий об'єкт.

Наприклад, вам необхідно отримати доступ до властивостей об'єкта unsafeOptions, який не завжди містить повний набір власних властивостей. Щоб уникнути undefined при зверненні до відсутньої властивості в unsafeOptions, зробимо деякі корективи:

  • Визначимо об'єкт defaults, який містить значення властивостей за замовчуванням
  • Викличемо Object.assign({ }, defaults, unsafeOptions)щоб побудувати новий об'єкт options. Новий об'єкт отримує всі властивості від unsafeOptions, а відсутні взяті з defaults.
const unsafeOptions = {  
  fontSize: 18
};
const defaults = {  
  fontSize: 16,
  color: 'black'
};
const options = Object.assign({}, defaults, unsafeOptions);  
options.fontSize; // => 18  
options.color;    // => 'black' 

unsafeOptions містить тільки fontSize властивість. Об'єкт defaults визначає значення за замовчуванням для властивостей fontSize і color.

Object.assign() приймає перший аргумент в якості цільового об'єкта {}. Цільовий об'єкт отримує значення властивості fontSize від вихідного об'єкта unsafeOptions. А значення властивості color з defaults, оскільки unsafeOptions не містить color.

Порядок, в якому перераховані вихідні об'єкти має значення: пізніші властивості вихідного об'єкта перезапишуть попередні.

Тепер ви безпечно можете отримати доступ до будь-якої властивості об'єкта options, в тому числі, options.color, який не був доступним в unsafeOptions спочатку.

Існує більш простий і легкий спосіб заповнити об'єкт з типовими властивостями. Я рекомендую використовувати нову функцію JavaScript (зараз на стадії 3 ) , що дозволяє поширювати властивості в ініціалізатор об'єктів.

Замість виклику Object.assign() використовується синтаксис об'єкта поширення для копіювання в цільовий об'єкт всіх власних і злічених властивостей з вихідних об'єктів:

const unsafeOptions = {  
  fontSize: 18
};
const defaults = {  
  fontSize: 16,
  color: 'black'
};
const options = {  
  ...defaults,
  ...unsafeOptions
};
options.fontSize; // => 18  
options.color;    // => 'black'

Об'єкт ініціалізатор поширює властивості від вихідних об'єктів defaults і unsafeOptions. Порядок, в якому вказуються вихідні об'єкти важливий: пізніші властивості об'єкта джерела перезапишуть попередні.

Заповнення неповного об'єкта зі значеннями властивостей за замовчуванням є ефективною стратегією, щоб зробити ваш код безпечним і довговічним. Незалежно від того, яка ситуація, об'єкт завжди містить повний набір властивостей: і undefined не може бути отримано.

2.3 Параметри функції

Параметри функції неявно мають стандартне значення undefined за замовчуванням.

Зазвичай функція, яка визначається з певним числом параметрів повинна бути викликана з тією ж кількістю аргументів. В цьому випадку параметри отримують значення, які ви очікуєте:

function multiply(a, b) {  
  a; // => 5
  b; // => 3
  return a * b;
}
multiply(5, 3); // => 15  

Виклик multiply(5, 3) призводить до отримання параметрами a і b відповідних 5 і 3 значень. Множення розраховується як очікувалося: 5 * 3 = 15.

Що відбувається, коли ви не включаєте аргумент у виклик? Параметр всередині функції стає undefined.

Трохи змінимо попередній приклад та викличемо функцію тільки з одним аргументом:

function multiply(a, b) {  
  a; // => 5
  b; // => undefined
  return a * b;
}
multiply(5); // => NaN  

function multiply(a, b) { } має два параметри a і b. Виклик multiply(5) з одним аргументом надає результат: параметр a = 5, а ось параметр bundefined.

Порада 6: Використовуйте значення параметра за замовчуванням

Іноді функція не вимагає повного набору аргументів при виклику. Ви можете просто встановити значення за замовчуванням для параметрів, які не мають значення.

Посилаючись на попередній приклад, покращимо наш код. Якщо параметр b - undefined, він отримує стандартне значення 2:

function multiply(a, b) {  
  if (b === undefined) {
    b = 2;
  }
  a; // => 5
  b; // => 2
  return a * b;
}
multiply(5); // => 10  

Функція викликається з одним аргументом multiply(5). Спочатку параметр a = 5 і b = undefined. Умовний оператор перевіряє , чи b це undefined. Якщо це так, встановлюється стандартне значення b = 2.

У той час як запропонований спосіб присвоєння стандартних значень працює, я не рекомендую порівнювати значення безпосередньо з undefined. Він багатослівний та схожий на хак.

Кращий підхід полягає у використанні ES2015 стандартних параметрів. Цей підхід достатньо короткий, а також немає прямих порівнянь з undefined.

Зміна попереднього прикладу зі стандартними параметрами для b дійсно виглядає чудово:

function multiply(a, b = 2) {  
  a; // => 5
  b; // => 2
  return a * b;
}
multiply(5);            // => 10  
multiply(5, undefined); // => 10  

b = 2 в оголошенні функції показує, що якщо b - undefined, параметр стає рівним 2.

ES2015 параметри за замовчуванням - інтуїтивно зрозумілі та виразні. Завжди використовуйте цю особливість, щоб встановити стандартні значення для необов'язкових параметрів.

2.4 Функція повертає значення

  • Неявно, без оголошення return, функція JavaScript повертає undefined.

У функціях JavaScript, які не мають жодних оголошень return, неявно повертається undefined:

function square(x) {  
  const res = x * x;
}
square(2); // => undefined  

функція square() не повертає ніяких результатів обчислень. Результатом виклику функції є undefined.

Така ж ситуація відбувається , коли оператор return присутній, але не містить виразів для повернення:

function square(x) {  
  const res = x * x;
  return;
}
square(2); // => undefined  

Оператор return; виконується, але він не повертає ніякого виразу. Результатом виклику буде також undefined.

Вкажемо після return значення, яке потрібно повернути. Тепер усе працює правильно:

function square(x) {  
  const res = x * x;
  return res;
}
square(2); // => 4  

Тепер виклик функції покаже 4, а це і є 2 в квадраті.

Порада 7: Не довіряйте автоматичній вставці крапки з комою

Нижче наведено список операторів в JavaScript, які повинні закінчуватися крапкою з комою (;):

  • порожній оператор
  • let, const, var, import, export оголошення
  • expression statement (присвоювання, виклик функції)
  • debugger оператор
  • continue, break
  • throw
  • return

Якщо ви використовуєте один із наведених вище операторів, не забудьте поставити крапку з комою в кінці:

function getNum() {  
  // Крапки з комою поставлені в кінці
  let num = 1; 
  return num;
}
getNum(); // => 1  

В кінці let і return оголошення ставиться крапка з комою.

Що відбувається, коли ви не хочете писати цю крапку з комою? Наприклад, щоб зменшити розмір вихідного файлу.

У такій ситуації ECMAScript забезпечує автоматичну вставку крапки з комою.

Щоб побачити, як працює цей механізм, ви можете видалити крапки з комою з попереднього прикладу:

function getNum() {  
  // Крапки з комою пропущені
  let num = 1
  return num
}
getNum() // => 1  

Наведений вище текст є робочим кодом JavaScript. Недостатні крапки з комою автоматично вставляться для вас.

На перший погляд, це виглядає перспективно. Цей механізм дозволяє пропускати вам непотрібні крапки з комою. Ви зможете зробити код JavaScript коротшим і легшим для читання.

Існує одна невелика, але дратівлива пастка, створена даним механізмом. Коли символ нового рядка стоїть між return і виразом, що повертає return \ expression, механізм автоматично вставляє крапку з комою перед новим рядком return; \ expression.

А що ми отримаємо, маючи всередині функції, оголошення типу return;? Функція поверне undefined. Тому, якщо ви погано знайомі з механізмом автоматичної вставки крапки з комою, маєте можливість несподівано повернути undefined з функції.

Наприклад, розглянемо значення, яке повертається викликом getPrimeNumbers():

function getPrimeNumbers() {  
  return 
    [ 2, 3, 5, 7, 11, 13, 17 ]
}
getPrimeNumbers() // => undefined  

Між return і літерним виразом масиву існує новий рядок. JavaScript автоматично вставляє крапку з комою після return, інтерпретуючи код наступним чином:

function getPrimeNumbers() {  
  return; 
  [ 2, 3, 5, 7, 11, 13, 17 ];
}
getPrimeNumbers(); // => undefined  

Оператор return; змушує функцію getPrimeNumbers() повернути undefined, замість очікуваного масиву.

Задача вирішується шляхом видалення символу нового рядка між return і масивом:

function getPrimeNumbers() {  
  return [ 
    2, 3, 5, 7, 11, 13, 17 
  ];
}
getPrimeNumbers(); // => [2, 3, 5, 7, 11, 13, 17]  

Моя рекомендація полягає в тому, щоб вивчити, як саме працює механізм автоматичної вставки крапки з комою, для того, щоб надалі уникнути таких ситуацій.

І, звісно, ніколи вставляйте новий рядок між return і виразом, який має повертатися.

2.5 void оператор

void expression обчислює вираз і повертає undefined незалежно від результату.

void 1;                    // => undefined  
void (false);              // => undefined  
void {name: 'John Smith'}; // => undefined  
void Math.min(1, 3);       // => undefined  

Одним з варіантів використання оператора void - це приведення обчисленого виразу до undefined, адже обчислення може мати деякий побічний ефект.

3. undefined в масивах

Ви отримаєте undefined при доступі до елементу масиву поза його границями.

const colors = ['blue', 'white', 'red'];  
colors[5];  // => undefined  
colors[-1]; // => undefined  

Масив colors складається з 3 елементів, таким чином, дійсні індекси 0, 1 і 2. Оскільки немає ніяких елементів масиву в індексах 5 і -1, аксессори colors[5] і colors[-1] будуть undefined.

В JavaScript ви можете зіткнутися з так званими розрідженими масивами. Це масиви, які мають діри, тобто в деяких індексах елементи не визначені. Коли діра (так званий, порожній слот) доступна всередині розрідженого масиву, ви також отримуєте undefined.

В наступному прикладі створюються розріджені масиви, та відбувається доступ до їх порожніх слотів:

const sparse1 = new Array(3);  
sparse1;       // => [<empty slot="">, <empty slot="">, <empty slot="">]  
sparse1[0];    // => undefined  
sparse1[1];    // => undefined  
const sparse2 = ['white',  ,'blue']  
sparse2;       // => ['white', <empty slot="">, 'blue']  
sparse2[1];    // => undefined  

sparse1 створюється за допомогою виклику Array конструктора з числовим першим аргументом. Він має 3 порожні слоти. sparse2 створюється масивом з відсутнім другим елементом. У будь-якому з цих розріджених масивів доступ до порожнього слоту має значення undefined.

При роботі з масивами, щоб уникнути undefined, не забувайте використовувати дійсні індекси масиву і взагалі уникайте створення розріджених масивів.

4. Різниця між undefined і null

З'являється основне питання: яка ж різниця між undefined і null? Ці обидва спеціальних значення означають пустий стан.

Основна відмінність полягає в тому, що undefined являє собою значення змінної, яка ще не була ініціалізована, а null являє собою навмисну відсутність об'єкта.

Розглянемо різницю на деяких прикладах.

Змінна number визначена, однак їй не призначається початкове значення:

let number;  
number; // => undefined  

Змінна number буде undefined, що явно вказує на неініціалізовану змінну. Те ж саме відбувається, коли ми намагаємось отримати доступ до відсутньої властивості об'єкта:

const obj = { firstName: 'Dmitri' };  
obj.lastName; // => undefined  

Оскільки властивості lastName не існує в obj, JavaScript правильно оцінює obj.lastName як undefined.

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

Наприклад, clone() – це функція , яка клонує простий об'єкт JavaScript. Функція повинна повернути об'єкт:

function clone(obj) {  
  if (typeof obj === 'object' && obj !== null) {
    return Object.assign({}, obj);
  }
  return null;
}
clone({name: 'John'}); // => {name: 'John'}  
clone(15);             // => null  
clone(null);           // => null 

Проте clone() не може бути викликана з об'єктом-аргументом: 15 чи null (або взагалі примітивним значенням null або undefined). У такому випадку неможливо створити клон, тому здається прийнятним повернення null – індикатора відсутнього об'єкта.

typeof оператор показує відмінність між цими двома значеннями:

typeof undefined; // => 'undefined'  
typeof null;      // => 'object'  

Оператор строгої рівності === правильно відрізняє undefined від null:

let nothing = undefined;  
let missingObject = null;  
nothing === missingObject; // => false  

5. Висновок

Існування undefined є наслідком нестрогого характеру JavaScript, який дозволяє використовувати:

  • неініціалізовані змінні
  • відсутні властивості об'єкта або методи
  • індекси поза межами для доступу до елементів масиву
  • виклики функцій, які нічого не повертають

В основному, порівняння безпосередньо з undefined є поганою звичкою.

Ефективна стратегія: зниження до мінімуму появи ключового слова undefined в вашому коді. Також, завжди пам'ятайте про його незвичну появу і користуйтеся такими корисними порадами:

  • зменшіть використання неініціалізованих змінних
  • зробіть життєвий цикл змінної коротким і близьким до джерела її використання
  • коли це можливо, привласнюйте початкове значення змінним
  • використовуйте const, в іншому випадку використовуйте let
  • використовуйте значення за замовчуванням для неважливих функціональних параметрів
  • перевіряйте існування властивостей або надавайте властивостям об'єктів значень за замовчуванням
  • уникайте використання розріджених масивів
Помітили помилку? Повідомте автору, для цього достатньо виділити текст з помилкою та натиснути Ctrl+Enter
Codeguida 5.6K
Приєднався: 8 місяців тому
Коментарі (0)

    Ще немає коментарів

Щоб залишити коментар необхідно авторизуватися.

Вхід / Реєстрація