Vacuité
Je ne recommande pas d'essayer de définir ou d'utiliser une fonction qui calcule si une valeur dans le monde entier est vide. Que signifie vraiment être "vide"? Si je l'ai let human = { name: 'bob', stomach: 'empty' }
, devrait isEmpty(human)
revenir true
? Si je l'ai let reg = new RegExp('');
, devrait isEmpty(reg)
revenirtrue
? Qu'en est-il isEmpty([ null, null, null, null ])
- cette liste ne contient que du vide, donc la liste elle-même est-elle vide? Je veux présenter ici quelques notes sur la "vacuité" (un mot intentionnellement obscur, pour éviter les associations préexistantes) en javascript - et je veux faire valoir que la "vacuité" dans les valeurs javascript ne doit jamais être traitée de manière générique.
Vérité / fausseté
Pour décider comment déterminer la «vacuité» des valeurs, nous devons tenir compte du sens intrinsèque intégré de javascript de savoir si les valeurs sont «véridiques» ou «fausses». Naturellement, null
et undefined
sont tous les deux "falsifiés". Moins naturellement, le nombre 0
(et aucun autre nombre sauf NaN
) est également "falsifié". Le moins naturellement: ''
est faux, mais []
et {}
(et new Set()
, et new Map()
) sont vrais - bien qu'ils semblent tous également vides!
Null vs Undefined
Il y a aussi une discussion concernant null
vs undefined
- avons-nous vraiment besoin des deux pour exprimer la vacuité dans nos programmes? Personnellement, j'évite que les lettres u, n, d, e, f, i, n, e, d apparaissent dans mon code dans cet ordre. J'utilise toujours null
pour signifier la «vacuité». Encore une fois, cependant, nous devons tenir compte du sens inhérent de javascript de la manière null
et de la undefined
différence:
- Essayer d'accéder à une propriété inexistante donne
undefined
- L'omission d'un paramètre lors de l'appel d'une fonction entraîne la réception de ce paramètre
undefined
:
let f = a => a;
console.log(f('hi'));
console.log(f());
- Les paramètres avec des valeurs par défaut reçoivent la valeur par défaut uniquement lorsqu'ils sont donnés
undefined
, pas null
:
let f = (v='hello') => v;
console.log(f(null));
console.log(f(undefined));
Vacuité non générique
Je pense que la vacuité ne doit jamais être traitée de manière générique. Au lieu de cela, nous devrions toujours avoir la rigueur pour obtenir plus d'informations sur nos données avant de déterminer si elles sont vides - je le fais principalement en vérifiant le type de données que je traite:
let isType = (value, Cls) => {
try {
return Object.getPrototypeOf(value).constructor === Cls;
} catch(err) {
return false;
}
};
Notez que cette fonction ignore le polymorphisme - elle s'attend value
à être une instance directe de Cls
, et non une instance d'une sous-classe de Cls
. J'évite instanceof
pour deux raisons principales:
([] instanceof Object) === true
("Un tableau est un objet")
('' instanceof String) === false
("Une chaîne n'est pas une chaîne")
Notez que cela Object.getPrototypeOf
est utilisé pour éviter un cas comme let v = { constructor: String };
La isType
fonction renvoie toujours correctement pour isType(v, String)
(false), etisType(v, Object)
(true).
Dans l'ensemble, je recommande d'utiliser cette isType
fonction avec ces conseils:
- Réduisez la quantité de valeurs de traitement de code de type inconnu. Par exemple, pour
let v = JSON.parse(someRawValue);
, notre v
variable est maintenant de type inconnu. Le plus tôt possible, nous devons limiter nos possibilités. La meilleure façon de le faire peut être d'exiger un type particulier: par exemple if (!isType(v, Array)) throw new Error('Expected Array');
- c'est un moyen très rapide et expressif de supprimer la nature générique de v
, et de s'assurer que c'est toujours un Array
. Parfois, cependant, nous devons permettre v
d'être de plusieurs types. Dans ces cas, nous devons créer des blocs de code qui v
ne sont plus génériques, le plus tôt possible:
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');
}
- Utilisez toujours des "listes blanches" pour la validation. Si vous voulez qu'une valeur soit, par exemple, une chaîne, un nombre ou un tableau, vérifiez ces 3 possibilités "blanches" et lancez une erreur si aucun des 3 n'est satisfait. Nous devrions être en mesure de voir que la vérification des possibilités "noires" n'est pas très utile: disons que nous écrivons
if (v === null) throw new Error('Null value rejected');
- c'est très bien pour s'assurer que les null
valeurs ne passent pas, mais si une valeur le fait , nous savons encore à peine quoi que ce soit à ce sujet. Une valeur v
qui passe ce null-check est encore TRÈS générique - c'est tout saufnull
! Les listes noires dissipent à peine le caractère générique.
À moins qu'une valeur ne soit null
, ne considérez jamais «une valeur vide de sens». Considérez plutôt "un X qui est vide". Essentiellement, n'envisagez jamais de faire quelque chose comme if (isEmpty(val)) { /* ... */ }
- peu importe comment cette isEmpty
fonction est implémentée (je ne veux pas savoir ...), ce n'est pas significatif! Et c'est bien trop générique! La vacuité ne doit être calculée qu'en connaissant val
le type de. Les contrôles de vacuité devraient ressembler à ceci:
- "Une chaîne, sans caractères":
if (isType(val, String) && val.length === 0) ...
- "Un objet, avec 0 accessoires":
if (isType(val, Object) && Object.entries(val).length === 0) ...
- "Un nombre égal ou inférieur à zéro":
if (isType(val, Number) && val <= 0) ...
"An Array, with no items": if (isType(val, Array) && val.length === 0) ...
La seule exception est quand null
est utilisé pour signifier certaines fonctionnalités. Dans ce cas, il est significatif de dire: "Une valeur vide":if (val === null) ...