Vácuo
Não recomendo tentar definir ou usar uma função que calcule se algum valor no mundo inteiro está vazio. O que realmente significa ser "vazio"? Se eu tiver let human = { name: 'bob', stomach: 'empty' }
, devo isEmpty(human)
voltar true
? Se eu tiver let reg = new RegExp('');
, devo isEmpty(reg)
voltar true
? Que isEmpty([ null, null, null, null ])
tal - esta lista contém apenas o vazio, então a lista em si está vazia? Quero apresentar aqui algumas notas sobre "vacuidade" (uma palavra intencionalmente obscura, para evitar associações pré-existentes) em javascript - e quero argumentar que "vacuidade" em valores de javascript nunca deve ser tratada genericamente.
Veracidade / falsidade
Para decidir como determinar a "vacuidade" dos valores, precisamos acomodar o senso inerente e inerente ao javascript de saber se os valores são "verdadeiros" ou "falsos". Naturalmente, null
e undefined
ambos são "falsos". Menos naturalmente, o número 0
(e nenhum outro número, exceto NaN
) também é "falso". Menos naturalmente: ''
é falso, mas []
e {}
(e new Set()
, e new Map()
) são verdadeiros - embora todos pareçam igualmente vazios!
Nulo vs Indefinido
Há também alguma discussão sobre null
vs undefined
- nós realmente precisamos de ambos para expressar a vacuidade em nossos programas? Pessoalmente, evito que as letras u, n, d, e, f, i, n, e, d apareçam no meu código nessa ordem. Eu sempre uso null
para significar "vazio". Novamente, porém, precisamos acomodar o senso inerente de javascript de como null
e undefined
diferir:
- Tentar acessar uma propriedade inexistente fornece
undefined
- Omitir um parâmetro ao chamar uma função resulta nesse parâmetro recebendo
undefined
:
let f = a => a;
console.log(f('hi'));
console.log(f());
- Parâmetros com valores padrão recebem o padrão somente quando fornecidos
undefined
, não null
:
let f = (v='hello') => v;
console.log(f(null));
console.log(f(undefined));
Vacuidade não genérica
Acredito que a vacuidade nunca deve ser tratada de maneira genérica. Em vez disso, devemos sempre ter o rigor para obter mais informações sobre nossos dados antes de determinar se estão vazios - eu faço isso principalmente verificando com que tipo de dados estou lidando:
let isType = (value, Cls) => {
try {
return Object.getPrototypeOf(value).constructor === Cls;
} catch(err) {
return false;
}
};
Observe que essa função ignora o polimorfismo - espera value
ser uma instância direta de Cls
, e não uma instância de uma subclasse de Cls
. eu evitoinstanceof
por duas razões principais:
([] instanceof Object) === true
("Uma matriz é um objeto")
('' instanceof String) === false
("Uma String não é uma String")
Observe que Object.getPrototypeOf
é usado para evitar um caso como let v = { constructor: String };
A isType
função ainda retorna corretamente para isType(v, String)
(false) e isType(v, Object)
(true).
No geral, eu recomendo usar esta isType
função junto com estas dicas:
- Minimize a quantidade de valores de processamento de código de tipo desconhecido. Por exemplo, pois
let v = JSON.parse(someRawValue);
, nossa v
variável agora é do tipo desconhecido. O mais cedo possível, devemos limitar nossas possibilidades. A melhor maneira de fazer isso pode ser exigindo um tipo específico: por exemplo if (!isType(v, Array)) throw new Error('Expected Array');
- essa é uma maneira muito rápida e expressiva de remover a natureza genérica v
e garantir que seja sempre uma Array
. Às vezes, porém, precisamos permitir v
que sejam de vários tipos. Nesses casos, devemos criar blocos de código onde v
não são mais genéricos, o mais cedo possível:
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');
}
- Sempre use "listas brancas" para validação. Se você precisar que um valor seja, por exemplo, String, Number ou Array, verifique essas 3 possibilidades "brancas" e gere um Erro se nenhuma das 3 estiver satisfeita. Devemos ser capazes de ver que a verificação de possibilidades "negros" não é muito útil: dizer que escrever
if (v === null) throw new Error('Null value rejected');
- isso é ótimo para garantir que null
valores não passar por isso, mas se um valor faz passar por isso, ainda sabemos pouco nada sobre isso. Um valor v
que passa nessa verificação nula ainda é MUITO genérico - é tudo menosnull
! As listas negras dificilmente dissipam a genérica.
A menos que seja um valor null
, nunca considere "um valor vazio". Em vez disso, considere "um X que é vazio". Essencialmente, nunca considere fazer algo assim if (isEmpty(val)) { /* ... */ }
- não importa como essa isEmpty
função seja implementada (eu não quero saber ...), não é significativo! E é genérico demais! A vacuidade deve ser calculada apenas com o conhecimento do val
tipo. As verificações de vacuidade devem ter a seguinte aparência:
- "Uma string, sem caracteres":
if (isType(val, String) && val.length === 0) ...
- "Um objeto, com 0 adereços":
if (isType(val, Object) && Object.entries(val).length === 0) ...
- "Um número igual ou menor que zero":
if (isType(val, Number) && val <= 0) ...
"Uma matriz, sem itens": if (isType(val, Array) && val.length === 0) ...
A única exceção é quando null
é usado para significar determinada funcionalidade. Nesse caso, é significativo dizer: "Um valor vazio":if (val === null) ...