Коли я почав вивчати 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
Трохи змінимо попередній фрагмент коду, щоб проілюструвати 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
, а ось параметр b
– undefined
.
Порада 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
- використовуйте значення за замовчуванням для неважливих функціональних параметрів
- перевіряйте існування властивостей або надавайте властивостям об'єктів значень за замовчуванням
- уникайте використання розріджених масивів
Ще немає коментарів