Vacío
No recomiendo tratar de definir o usar una función que calcule si algún valor en todo el mundo está vacío. ¿Qué significa realmente estar "vacío"? Si tengo let human = { name: 'bob', stomach: 'empty' }
, debería isEmpty(human)
volver true
? Si tengo let reg = new RegExp('');
, debería isEmpty(reg)
volver true
? ¿Qué pasa isEmpty([ null, null, null, null ])
? Esta lista solo contiene el vacío, entonces, ¿está vacía la lista misma? Quiero presentar aquí algunas notas sobre "vacuidad" (una palabra intencionalmente oscura, para evitar asociaciones preexistentes) en javascript, y quiero argumentar que la "vacuidad" en los valores de javascript nunca debe tratarse genéricamente.
Verdad / falsedad
Para decidir cómo determinar la "vacuidad" de los valores, necesitamos acomodar el sentido inherente e inherente de JavaScript de si los valores son "verdaderos" o "falsos". Naturalmente, null
y undefined
ambos son "falsos". Menos naturalmente, el número 0
(y ningún otro número excepto NaN
) también es "falso". Menos natural: ''
es falso, pero []
y {}
(y new Set()
, y new Map()
) son verdaderos, ¡aunque todos parecen igualmente vacíos!
Nulo vs Indefinido
También hay una discusión acerca de null
vs undefined
: ¿realmente necesitamos ambos para expresar vacío en nuestros programas? Personalmente, evito que las letras u, n, d, e, f, i, n, e, d aparezcan en mi código en ese orden. Siempre uso null
para significar "vacuidad". Una vez más, sin embargo, necesitamos acomodar el sentido inherente de JavaScript de cómo null
y undefined
diferir:
- Intentar acceder a una propiedad inexistente da
undefined
- Omitir un parámetro al llamar a una función da como resultado que ese parámetro reciba
undefined
:
let f = a => a;
console.log(f('hi'));
console.log(f());
- Los parámetros con valores predeterminados reciben el valor predeterminado solo cuando se proporcionan
undefined
, no null
:
let f = (v='hello') => v;
console.log(f(null));
console.log(f(undefined));
Vacío no genérico
Creo que la vacuidad nunca debe tratarse de manera genérica. En su lugar, siempre deberíamos tener el rigor de obtener más información sobre nuestros datos antes de determinar si son vacíos; principalmente hago esto verificando qué tipo de datos estoy tratando:
let isType = (value, Cls) => {
try {
return Object.getPrototypeOf(value).constructor === Cls;
} catch(err) {
return false;
}
};
Tenga en cuenta que esta función ignora el polimorfismo: espera value
ser una instancia directa de Cls
, y no una instancia de una subclase de Cls
. Evito instanceof
por dos razones principales:
([] instanceof Object) === true
("Una matriz es un objeto")
('' instanceof String) === false
("Una cadena no es una cadena")
Tenga en cuenta que Object.getPrototypeOf
se utiliza para evitar un caso como let v = { constructor: String };
La isType
función todavía regresa correctamente para isType(v, String)
(falso) y isType(v, Object)
(verdadero).
En general, recomiendo usar esta isType
función junto con estos consejos:
- Minimice la cantidad de valores de procesamiento de código de tipo desconocido. Por ejemplo, para
let v = JSON.parse(someRawValue);
, nuestra v
variable ahora es de tipo desconocido. Tan pronto como sea posible, debemos limitar nuestras posibilidades. La mejor manera de hacerlo puede ser exigiendo un tipo particular: por ejemplo if (!isType(v, Array)) throw new Error('Expected Array');
, esta es una forma realmente rápida y expresiva de eliminar la naturaleza genérica de v
, y garantizar que siempre sea así Array
. A veces, sin embargo, debemos permitir v
ser de múltiples tipos. En esos casos, debemos crear bloques de código donde v
ya no sea genérico, lo antes posible:
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');
}
- Utilice siempre "listas blancas" para la validación. Si necesita que un valor sea, por ejemplo, una cadena, un número o una matriz, verifique esas 3 posibilidades "blancas" y arroje un error si ninguno de los 3 está satisfecho. Deberíamos poder ver que verificar las posibilidades "negras" no es muy útil: digamos que escribimos
if (v === null) throw new Error('Null value rejected');
, esto es excelente para garantizar que los null
valores no se logren, pero si un valor se logra , todavía no lo sabemos nada al respecto. Un valor v
que pasa esta verificación nula sigue siendo MUY genérico, ¡es todo menosnull
eso ! Las listas negras apenas disipan el genérico.
A menos que un valor sea null
, nunca considere "un valor vacío". En cambio, considere "una X que está vacía". Esencialmente, nunca considere hacer algo como if (isEmpty(val)) { /* ... */ }
: no importa cómo isEmpty
se implemente esa función (no quiero saber ...), ¡no tiene sentido! ¡Y es demasiado genérico! La vacuidad solo debe calcularse con conocimiento del val
tipo. Los controles de vacío deberían tener este aspecto:
- "Una cadena, sin caracteres":
if (isType(val, String) && val.length === 0) ...
- "Un objeto, con 0 accesorios":
if (isType(val, Object) && Object.entries(val).length === 0) ...
- "Un número, igual o menor que cero":
if (isType(val, Number) && val <= 0) ...
"Una matriz, sin elementos": if (isType(val, Array) && val.length === 0) ...
La única excepción es cuando null
se usa para indicar cierta funcionalidad. En este caso es significativo decir: "Un valor vacío":if (val === null) ...