Вакуумність
Я не рекомендую намагатися визначити чи використовувати функцію, яка обчислює, чи будь-яке значення у цілому світі порожнє. Що насправді означає бути "порожнім"? Якщо я маю let human = { name: 'bob', stomach: 'empty' }
, повинен isEmpty(human)
повернутися true
? Якщо я маю let reg = new RegExp('');
, повинен isEmpty(reg)
повернутися true
? А як бути isEmpty([ null, null, null, null ])
- цей список містить лише порожнечу, тому сам список порожній? Я хочу висунути тут кілька приміток про "вакуумність" (навмисно незрозуміле слово, щоб уникнути раніше існуючих асоціацій) у javascript - і я хочу стверджувати, що "вакуумність" у значеннях JavaScript ніколи не повинна стосуватися загально.
Правдивість / хитрість
Щоб вирішити, як визначити "вакуумність" значень, нам потрібно врахувати вбудований javascript, властивий йому сенс того, чи є значення "truthy" чи "false". Природно, null
і undefined
вони обидва "хибні". Менш природно, число 0
(і жодне інше число, крім NaN
) теж "хибне". Найменше, природно: ''
хибний, але []
і {}
(і new Set()
, і new Map()
) є неправдивими, хоча всі вони здаються однаково вакуумними!
Нуль проти невизначеного
Існує також деяка дискусія щодо null
vs undefined
- чи нам справді потрібні обидва, щоб виразити нерішучість в наших програмах? Я особисто уникаю того, щоб літери u, n, d, e, f, i, n, e, d відображалися в моєму коді в такому порядку. Я завжди використовую null
для позначення "вакуумності". Знову ж таки, нам потрібно відповідати властивому JavaSkript почуттю того, як null
і чим undefined
відрізняються:
- Спроба отримати доступ до неіснуючої власності дає
undefined
- Пропуск параметра при виклику функції призводить до отримання цього параметра
undefined
:
let f = a => a;
console.log(f('hi'));
console.log(f());
- Параметри зі значеннями за замовчуванням отримують за замовчуванням лише тоді, коли вони задані
undefined
, не null
:
let f = (v='hello') => v;
console.log(f(null));
console.log(f(undefined));
Вакуозність, яка не є загальною
Я вважаю, що з вакуумністю ніколи не слід поводитися із загальним способом. Натомість нам слід завжди мати суворість, щоб отримати більше інформації про наші дані, перш ніж визначати, чи є вони вакуумними - я це роблю в основному, перевіряючи, з яким типом даних я маю справу:
let isType = (value, Cls) => {
try {
return Object.getPrototypeOf(value).constructor === Cls;
} catch(err) {
return false;
}
};
Зауважимо, що ця функція ігнорує поліморфізм - вона очікує, value
що це прямий екземпляр Cls
, а не екземпляр підкласу Cls
. Я уникаю instanceof
з двох основних причин:
([] instanceof Object) === true
("Масив - об'єкт")
('' instanceof String) === false
("Рядок не є рядком")
Зверніть увагу , що Object.getPrototypeOf
використовується , щоб уникнути випадку , як функція по- , як і раніше повертає правильно для (брехня), і (істина).let v = { constructor: String };
isType
isType(v, String)
isType(v, Object)
Загалом, рекомендую використовувати цю isType
функцію разом із цими порадами:
- Мінімізуйте кількість значень обробки коду невідомого типу. Наприклад,
let v = JSON.parse(someRawValue);
наша v
змінна зараз невідомого типу. Як можна раніше нам слід обмежити наші можливості. Найкращий спосіб зробити це можна, вимагаючи певного типу: наприклад if (!isType(v, Array)) throw new Error('Expected Array');
- це дійсно швидкий та виразний спосіб усунути родову природу v
та забезпечити її завжди Array
. Однак іноді нам потрібно дозволити v
бути декількома типами. У цих випадках ми повинні створити блоки коду, де v
це вже не є загальним, як можна раніше:
if (isType(v, String)) {
/* v isn't generic in this block - It's a String! */
} else if (isType(v, Number)) {
/* v isn't generic in this block - It's a Number! */
} else if (isType(v, Array)) {
/* v isn't generic in this block - it's an Array! */
} else {
throw new Error('Expected String, Number, or Array');
}
- Завжди використовуйте "білі списки" для перевірки. Якщо вам потрібне значення, наприклад, рядок, номер або масив, перевірте ці 3 "білі" можливості і введіть помилку, якщо жоден з 3 не задоволений. Ми повинні бачити, що перевірка "чорних" можливостей не дуже корисна: скажімо, ми пишемо
if (v === null) throw new Error('Null value rejected');
- це чудово для того, щоб null
значення не проробляли, але якщо значення робить це, ми все ще навряд чи знаємо нічого про це. Значення, v
яке проходить цю нульову перевірку, все ще ДУЖЕ загальне - це все, алеnull
! Чорні списки навряд чи розвіюють загальну сутність.
Якщо значення не є null
, ніколи не вважайте "вакуумним значенням". Замість цього розглянемо "X, який є вакуумним". По суті, ніколи не думайте робити щось подібне if (isEmpty(val)) { /* ... */ }
- незалежно від того, як ця isEmpty
функція реалізована (я не хочу знати ...), це не має сенсу! І це занадто загально! Вакуумність повинна обчислюватися лише на основі знань val
типу. Чекові перевірки повинні виглядати так:
- "Рядок без знаків":
if (isType(val, String) && val.length === 0) ...
- "Об'єкт з 0 реквізитами":
if (isType(val, Object) && Object.entries(val).length === 0) ...
- "Число, рівне або менше нуля":
if (isType(val, Number) && val <= 0) ...
"Масив без елементів": if (isType(val, Array) && val.length === 0) ...
Єдиний виняток - коли null
використовується для позначення певної функціональності. У цьому випадку має сенс сказати: "Вакуумне значення":if (val === null) ...