вырожденность
Я не рекомендую пытаться определить или использовать функцию, которая вычисляет, является ли какое-либо значение во всем мире пустым. Что на самом деле означает быть «пустым»? Если у меня есть let human = { name: 'bob', stomach: 'empty' }, должен isEmpty(human)вернуться true? Если у меня есть let reg = new RegExp('');, должен isEmpty(reg)вернуться true? Как насчет isEmpty([ null, null, null, null ])- этот список содержит только пустоту, поэтому сам список пуст? Я хочу выдвинуть здесь некоторые примечания о «пустотности» (намеренно неясное слово, чтобы избежать ранее существовавших ассоциаций) в javascript - и я хочу доказать, что «пустотность» в значениях javascript никогда не должна рассматриваться вообще.
Truthiness / Falsiness
Чтобы решить, как определить «пустоту» значений, нам нужно учесть встроенный в javascript внутренний смысл того, являются ли значения «правдивыми» или «ложными». Естественно nullи то undefinedи другое "фальшиво". Менее естественно, число 0(и никакое другое число кроме NaN) также не соответствует действительности. Наименее естественно: ''это ложь, но []и {}(и new Set(), и new Map()) правдивы - хотя все они кажутся одинаково пустыми!
Нулевой и неопределенный
Существует также некоторая дискуссия относительно nullvs undefined- действительно ли нам нужно и то, и другое, чтобы выразить пустоту в наших программах? Лично я избегаю, чтобы буквы u, n, d, e, f, i, n, e, d появлялись в моем коде в таком порядке. Я всегда использую nullдля обозначения "пустотности". Опять же, тем не менее, мы должны учесть присущие JavaScript особенности того, как 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 };isTypeisType(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');
}
- Всегда используйте «белые списки» для проверки. Если вам требуется, чтобы значением было, например, String, Number или Array, проверьте эти 3 «белые» возможности и выведите Error, если ни один из 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) ...