7 порад з обробки “undefined” в JavaScript


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

Коли новачки починають вивчати JavaScript, вони іноді стикаються з однією цікавою проблемою. Вони частенько не можуть зрозуміти різницю undefined і null. Адже null і undefined є порожніми значеннями. Навіть операція порівняння null == undefined виводить true. Давайте спробуємо розібратися в тому, що з себе все-таки представляє undefined.

Зміст статті :

  1. Вступ.
  2. Що таке undefined.
  3. Що впливає на появу undefined.
  4. Undefined в масивах.
  5. Різниця між null і undefined.
  6. Висновок.

Вступ

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

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

let company; company; // => undefined let person ={ name: 'John Snow'}; 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 (undefined не є функцією);
  • typeError: Cannot read property ''of undefined (неможливо прочитати '' властивість undefined);
  • інші подібні помилки.

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

function undefined (){ // проблема розв'язана}

Щоб уникнути помилок, пов’язаних з undefined, треба розуміти, коли ця помилка може виникати, і понизити риски її появи у вашому додатку.

Що таке undefined

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

  • Boolean. У цього типу є два значення: true (істина) і false (брехня). Як правило, логічний тип boolean використовується для зберігання значення типу так/ні.
  • Number. Тип число використовується як для цілих, так і для дробових чисел (1, 6.7, 0xff);
  • String. У JavaScript будь-які текстові дані є рядками. У JavaScript немає різниці між подвійними і одинарними лапками: var text = " рядок" / var texts = 'рядки';
  • Symbol. Новий примітивний тип даних Symbol служить для створення унікальних ідентифікаторів: Symbol ("name") (починаючи з ES2015);
  • Null. Значення null не відноситься ні до одного з типів вище, а утворює свій окремий тип, що складається з єдиного значення null;
  • Undefined. Значення undefined, як і null, утворює свій власний тип, що складається з одного цього значення. Якщо змінна оголошена, але в неї нічого не записано, то її значення якраз і є undefined (значення не присвоєне).

Окремо від усіх стоїть тип Object (об’єкт)який використовується для організації даних і оголошення складніших сутностей. Приклад:

var human ={ id: 12884002, age: 28, name: 'Bob', favorite _song: 'Photograph, by Nickelback'}

Об’єкт може мати будь-які властивості до тих пір, поки вони знаходяться усередині фігурних дужок {...}.

Серед цих 6 примітивних типів undefined є спеціальним значенням. Згідно специфікації ECMAScript, undefined використовується тоді, коли змінній не присвоєно значення.

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

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

Приклад вище показує, що undefined виводиться при спробі доступу до:

  • неініціалізованій змінній number;
  • неіснуючій властивості об’єкту movie.year;
  • неіснуючому елементу масиву movies[3].

Оператор typeof повертає рядок undefined для невизначеного значення:

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

Оператор typeof чудово підходить для перевірки значення undefined у змінної:

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

Що впливає на появу undefined

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

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

Приклад:

let myVariable; myVariable; // => undefined

Змінна myVariable вже оголошена, але ще не має присвоєного значення. Спроба звернутися до змінної закінчиться виведенням undefined.

Ефективний підхід до рішення проблем неініціалізованих змінних – це по можливості присвойте їм початкове значення. Чим менше змінна існує в неініціалізованому стані, тим краще. В ідеалі ви відразу ж привласнюєте значення myVariable = 'initial' (початкове значення)хоча це не завжди можливо.

Віддавайте перевагу 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.

Проблема використання var полягає в так званому піднятті змінних (англ. variable hoisting). Де б не знаходилося оголошення, це рівнозначно тому, що змінну оголосили на самому початку коду.

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

В цьому випадку змінна myVariable містить undefined до набуття значення :

myVariable = 'Initial value'

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

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

function bigFunction (){ // код... myVariable; // => виводиться 'ReferenceError: myVariable is not defined' // код... let myVariable = 'Initial value'; // код...
myVariable; // => 'Initial value'}bigFunction ();

Використання const чи let для незмінних прив’язок дозволить понизити риски набуття значення undefined при написанні коду.

Посильте зв’язність

Зв’язність характеризує ступінь взаємозв’язності елементів модуля (простори імен, класу, методу, блоку коду). Є два види зв’язності – сильна і слабка.

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

  • сфокусованим і зрозумілим – легшє зрозуміти, яке завдання виконує модуль;
  • легко підтримуємим і піддающимся рефакторингу – зміна модуля впливає на меншу кількість елементів;
  • багаторазовим – якщо модуль орієнтований на виконання одного завдання, то його легше використати повторно;
  • простим для тестування – вам буде простіші протестувати модуль, орієнтований на одне завдання.

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

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

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

Одним з класичних прикладів зайвого терміну життя змінних є використання циклу for усередині функції:

function someFunc (array) { var index, item, length = array.length; // деякий код... // деякий код... for (index = 0; index < length; index++) { item = array[index]; // деякий код... } return 'some result';}

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

Увесь час між оголошенням змінної на початку і до використання її в циклі for змінні index і item не ініціалізувалися і виводять undefined. По цьому прикладу видно, що термін життя змінної є необгрунтовано довгим.

Розумніше буде перемістити змінні ближче до місця їх застосування :

function someFunc (array) { // деякий код... // деякий код... const length = array.length; for (let index = 0; index < length; index++) { const item = array[index]; // деякий код } return 'some result';}

Змінні index і item існують тільки в зоні дії блоку for. У них немає ніякого значення за його межами.

Змінна (const) length оголошується в місці її використання.

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

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

Доступ до неіснуючої властивості

При доступі до неіснуючої властивості об'єкту 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: елемент, вставлений на початку масиву;
  • last: елемент, вставлений в кінець масиву.

Функція повертає новий екземпляр масиву, не змінюючи початковий (тобто це чиста функція).

Функція 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]

Оскільки об'єкт toAppend може видаляти перші або останні властивості, необхідно перевірити, чи існують ці властивості в toAppend.

Доступ до властивості приведе до отримання undefined, якщо його не існує. Можна перевірити чи існує перша або остання властивість, щоб уникнути undefined. Як варіант, це можна зробити за допомогою умов if (toAppend.first){} і if (toAppend.last){}.

Але не квапитимемося. У цьому підході є серйозний недолік. undefined, також як false, null, 0, NaN і ' ' є неправдивими значеннями (falsy values).

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

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

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

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

Перевірте, чи існує властивість

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

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

Рекомендацією в цьому випадку буде використання оператора in . У нього простий і зрозумілий синтаксис. Присутність оператора in вказує на чіткий намір перевірити, чи має об'єкт певна властивість, не звертаючись до фактичного значення цієї властивості.

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

Ці два способи порівняння з undefined можуть спрацювати. Але здається, що використання obj.prop !== undefined і typeof obj.prop !== 'undefined' є не дуже хорошим рішенням і може привести до незрозумілих наслідків при прямому порівнянні з 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 усуває проблему зі вставкою неправдивих елементів 0 і false. Тепер додавання елементів на початку і у кінці масиву [10] призводить до очікуваного результату [0, 10, false].

Виконайте деструктурировання доступу до властивостей об'єкту

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

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

Встановіть об'єкту властивості за умовчанням.

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

Функція 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, яка дозволяє розширювати властивості при ініціалізації об'єктів.

Замість виклику 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 неможлива.

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

Параметри функції за умовчанням встановлені на 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

Функція multiply (a, b) { } визначається двома параметрами: a і b. Виклик multiply (5) виконується за допомогою одного аргументу, в результаті параметр рівний 5, а параметр b набуває значення undefined.

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

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

Враховуючи попередній приклад, давайте зробимо деякі поліпшення. Якщо параметр 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 рівний 2, а b відповідає undefined. Вираження перевіряє, чи не рівне b параметру undefined. Якщо це так, b набуває значення за умовчанням 2.

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

Кращим підходом є використання параметрів за умовчанням з ES2015. Він короткий і не містить в собі прямого порівняння значень з undefined.

Установка параметрів за умовчанням для b не виводить значення undefined:

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 інтуїтивно зрозуміла і виразна. Завжди використовуйте її для установки значень за умовчанням для необов'язкових параметрів.

Повертане значення функції

У 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

Тепер виклик функції виведе потрібне значення.

Не довіряйте автоматичному розставлянню крапки з комою

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

  • порожній оператор;
  • оператори: let, const, var, import, export;
  • затвердження значення;
  • затвердження відладчика;
  • твердження: continue, break, throw, return.

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

function getNum (){ // Notice the semicolons at the end let num = 1; return num;}getNum (); // => 1

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

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

У такій ситуації ECMAScript надає механізм автоматичної установки крапки з комою (ASI), який зробить усю роботу за вас.

Використовуючи ASI, ви можете видалити крапки з комою з попереднього прикладу:

function getNum (){ // Notice that semicolons are missing let num = 1 return num}getNum () // => 1

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

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

Але тут існує один невеликий, але дратівливий момент, який може створити ASI. Якщо між return і повертаним вираженням стоїть новий рядок return n expression, ASI автоматично вставляє крапку з комою return; n expression.

До чого приведе порожній оператор return ? Функція поверне значення undefined. Якщо ви не знаєте, як працює механізм ASI, то несподівана поява значення 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

У такому разі ми отримаємо значення undefined.

Проблема розв'язана шляхом видалення нового рядку між return і масивом:

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

Оператор void

Оператор void виконує вирази і повертає undefined незалежно від результату:

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

Одним з варіантів використання оператора void являється перевизначення результату виконання виразу і повернення undefined, у разі виникнення побічних ефектів виконання функції.

Undefined в масивах

Ви отримуєте undefined при спробі доступу до елементу масиву з індексом поза межами масиву.

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

Масив colors має 3 елементи, тому коректні індекси рівні 0, 1 і 2.

Оскільки в індексах масиву 5 і - 1 немає елементів, значення colors[5] and colors[- 1] набувають значення undefined.

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

Коли робимо спробу отримати доступ до порожнього значення в розрідженому масиві, на виході отримуємо undefined.

Наступний приклад генерує розріджені масиви і намагається отримати доступ до їх порожніх елементів:

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

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

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

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

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

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

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

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

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

let number; 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

Висновок

Існування undefined є наслідком дозвільної природи JavaScript, яка дозволяє використати :

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

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

Стратегія боротьби з undefined полягає в зменшенні появи цього значення у вашому коді. І виробіть в собі звичку робити наступне :

  • зменшувати використання неініціалізованих змінних;
  • робити життєвий цикл змінних коротким і близьким до джерела їх використання;
  • по можливості призначати початкове значення змінним;
  • намагатися використати const чи let;
  • використати значення за умовчанням для некритичних параметрів функції;
  • перевіряти наявність властивостей або заповнити небезпечні об'єкти за умовчанням;
  • уникати використання розріджених масивів.

Переклад статті "7 tips to handle undefined in JavaScript"

Київ, Харків, Одеса, Дніпро, Запоріжжя, Кривий Ріг, Вінниця, Херсон, Черкаси, Житомир, Хмельницький, Чернівці, Рівне, Івано-Франківськ, Кременчук, Тернопіль, Луцьк, Ужгород, Кам'янець-Подільський, Стрий - за статистикою саме з цих міст програмісти найбільше переїжджають працювати до Львова. А Ви розглядаєте relocate?


Залишити відповідь

Ваша e-mail адреса не оприлюднюватиметься. Обов’язкові поля позначені *