вырожденность
Я не рекомендую пытаться определить или использовать функцию, которая вычисляет, является ли какое-либо значение во всем мире пустым. Что на самом деле означает быть «пустым»? Если у меня есть 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()
) правдивы - хотя все они кажутся одинаково пустыми!
Нулевой и неопределенный
Существует также некоторая дискуссия относительно null
vs 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 };
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');
}
- Всегда используйте «белые списки» для проверки. Если вам требуется, чтобы значением было, например, 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) ...