1. Utilisez Array.includes pour plusieurs critères

Jetons un coup d’oeil à l’exemple ci-dessous:

1// condition
2function test(fruit) {
3  if (fruit == 'apple' || fruit == 'strawberry') {
4    console.log('red');
5  }
6}

À première vue, l’exemple ci-dessus semble bon. Cependant, que se passe-t-il si nous obtenons plus de fruits rouges, par exemple des cherry et des cranberries? Allons-nous étendre la déclaration avec plus ||?

Nous pouvons réécrire la condition ci-dessus en utilisant `Array.includes (Array.includes):

1function test(fruit) {
2  // extract conditions to array
3  const redFruits = ['apple', 'strawberry', 'cherry', 'cranberries'];
4  if (redFruits.includes(fruit)) {
5    console.log('red');
6  }
7}

2. Moins d’emboîtements, retour hâtif

Développons l’exemple précédent pour inclure deux conditions supplémentaires:

  • si aucun fruit n’est fourni, lancer une erreur
  • acceptée et imprimer la quantité de fruits si elle dépasse 10.
 1function test(fruit, quantity) {
 2  const redFruits = ['apple', 'strawberry', 'cherry', 'cranberries'];
 3  // condition 1: fruit must has value
 4  if (fruit) {
 5    // condition 2: must be red
 6    if (redFruits.includes(fruit)) {
 7      console.log('red');
 8      // condition 3: must be big quantity
 9      if (quantity > 10) {
10        console.log('big quantity');
11      }
12    }
13  } else {
14    throw new Error('No fruit!');
15  }
16}
17// test results
18test(null); // error: No fruits
19test('apple'); // print: red
20test('apple', 20); // print: red, big quantity

Regardez le code ci-dessus, nous avons:

  • 1 instruction if / else qui filtre les conditions non valides
  • 3 niveaux de déclarations if imbriqué (conditions 1, 2 et 3)

Une règle générale que je suis personnellement est le retour anticipé lorsque des conditions invalides sont trouvées.

 1/_ return early when invalid conditions found _/
 2function test(fruit, quantity) {
 3  const redFruits = ['apple', 'strawberry', 'cherry', 'cranberries'];
 4  // condition 1: throw error early
 5  if (!fruit) throw new Error('No fruit!');
 6  // condition 2: must be red
 7  if (redFruits.includes(fruit)) {
 8    console.log('red');
 9    // condition 3: must be big quantity
10    if (quantity > 10) {
11      console.log('big quantity');
12    }
13  }
14}

En faisant cela, nous avons un niveau moins de déclarations imbriquées. Ce style de codage convient particulièrement lorsque vous avez une déclaration if long (imaginez que vous devez faire défiler l’écran jusqu’au bas de la page pour savoir qu’il existe une autre instruction, pas cool).

Nous pouvons réduire davantage la nidification en inversant les conditions et en revenant plus tôt. Regardez la condition 2 ci-dessous pour voir comment nous procédons :

 1/_ return early when invalid conditions found _/
 2function test(fruit, quantity) {
 3  const redFruits = ['apple', 'strawberry', 'cherry', 'cranberries'];
 4  if (!fruit) throw new Error('No fruit!'); // condition 1: throw error early
 5  if (!redFruits.includes(fruit)) return; // condition 2: stop when fruit is not red
 6  console.log('red');
 7  // condition 3: must be big quantity
 8  if (quantity > 10) {
 9    console.log('big quantity');
10  }
11}

En inversant les conditions de la condition 2, notre code est maintenant exempt d’instruction imbriquée. Cette technique est utile lorsque la logique est longue et que nous voulons arrêter d’autres processus lorsqu’une condition n’est pas remplie.

Cependant, ce n’est pas une règle absolue. Demandez-vous si cette version (sans imbrication) est meilleure / plus lisible que la précédente (condition 2 avec imbriquée)?

Pour moi, je le laisserais comme dans la version précédente (condition 2 avec imbriquée) parce que:

  • le code est court et simple, il est plus clair avec imbriqué si
  • condition d’inversion peut impliquer plus de processus de pensée (augmentation de la charge cognitive)

Par conséquent, vise toujours moins de nidification et de retour anticipé, mais n’en faites pas trop. Il existe un article et une discussion sur StackOverflow qui approfondissent ce sujet si vous êtes intéressé:

3. Utiliser les paramètres de fonction par défaut et la destruction

Je suppose que le code ci-dessous pourrait vous sembler familier. Nous devons toujours vérifier les valeurs null / undefined et assigner des valeurs par défaut lorsque vous utilisez JavaScript:

1function test(fruit, quantity) {
2  if (!fruit) return;
3  const q = quantity || 1; // if quantity not provided, default to one
4  console.log(`We have ${q} ${fruit}!`);
5}
6//test results
7test('banana'); // We have 1 banana!
8test('apple', 2); // We have 2 apple!

En fait, on peut éliminer la variable q en assignant des paramètres de fonction par défaut.

1function test(fruit, quantity = 1) { // if quantity not provided, default to one
2  if (!fruit) return;
3  console.log(`We have ${quantity} ${fruit}!`);
4}
5//test results
6test('banana'); // We have 1 banana!
7test('apple', 2); // We have 2 apple!

Beaucoup plus facile et intuitif n’est-ce pas? Veuillez noter que chaque paramètre peut avoir son propre paramètre de fonction par défaut. Par exemple, nous pouvons aussi assigner une valeur par défaut aux fruits: function test (fruit = 'unknown', quantity = 1).

Et si notre fruit est un objet? Peut-on assigner un paramètre par défaut?

 1function test(fruit) { 
 2  // printing fruit name if value provided
 3  if (fruit && fruit.name)  {
 4    console.log (fruit.name);
 5  } else {
 6    console.log('unknown');
 7  }
 8}
 9//test results
10test(undefined); // unknown
11test({ }); // unknown
12test({ name: 'apple', color: 'red' }); // apple

Regardez l’exemple ci-dessus : nous voulons afficher le nom du fruit s’il est disponible ou nous allons afficher inconnus. Nous pouvons éviter le contrôle conditionnel de fruit && fruit.name avec le paramètre de fonction par défaut et la destruction.

1// destructing - get name property only
2// assign default empty object {}
3function test({name} = {}) {
4  console.log (name || 'unknown');
5}
6//test results
7test(undefined); // unknown
8test({ }); // unknown
9test({ name: 'apple', color: 'red' }); // apple

Puisque nous n’avons besoin que de la propriété name de fruit, nous pouvons déstructurer le paramètre en utilisant {name}, alors nous pouvons utiliser name comme variable dans notre code au lieu de fruit.name.

Nous assignons aussi l’objet vide {} comme valeur par défaut. Si nous ne le faisons pas, vous obtiendrez une erreur lors de l’exécution de la ligne test(undefined) - Cannot destructure property name of 'undefined' or 'null'. car il n’y a pas de propriété name dans undefined.

Si cela ne vous dérange pas d’utiliser des bibliothèques tierces, il y a plusieurs façons de réduire les contrôles nuls:

Voici un exemple d’utilisation de Lodash:

1// Include lodash library, you will get _
2function test(fruit) {
3  console.log(__.get(fruit, 'name', 'unknown'); // get property name, if not available, assign default value 'unknown'
4}
5//test results
6test(undefined); // unknown
7test({ }); // unknown
8test({ name: 'apple', color: 'red' }); // apple

4. Favorisez Map / Objet Littéral par rapport à l’instruction Switch

Regardons l’exemple ci-dessous - nous voulons afficher des fruits en fonction de la couleur:

 1function test(color) {
 2  // use switch case to find fruits in color
 3  switch (color) {
 4    case 'red':
 5      return ['apple', 'strawberry'];
 6    case 'yellow':
 7      return ['banana', 'pineapple'];
 8    case 'purple':
 9      return ['grape', 'plum'];
10    default:
11      return [];
12  }
13}
14//test results
15test(null); // []
16test('yellow'); // ['banana', 'pineapple']

Le code ci-dessus ne semble rien de mal, mais je le trouve assez verbeux. Le même résultat peut être obtenu avec un objet littéral avec une syntaxe plus prore:

1// use object literal to find fruits in color
2  const fruitColor = {
3    red: ['apple', 'strawberry'],
4    yellow: ['banana', 'pineapple'],
5    purple: ['grape', 'plum']
6  };
7function test(color) {
8  return fruitColor[color] || [];
9}

Vous pouvez également utiliser Map pour obtenir le même résultat:

1// use Map to find fruits in color
2const fruitColor = new Map()
3  .set('red', ['apple', 'strawberry'])
4  .set('yellow', ['banana', 'pineapple'])
5  .set('purple', ['grape', 'plum']);
6function test(color) {
7  return fruitColor.get(color) || [];
8}

Map est le type d’objet disponible depuis ES2015, qui vous permet d’enregistrer des paires de clés/valeur.
Devrions-nous interdire l’utilisation des déclarations switch? Ne vous limitez pas à cela. Personnellement, j’utilise des objets littéraux chaque fois que c’est possible, mais je n’établirais pas une règle stricte pour bloquer cela - utilisez celui qui a un sens pour votre scénario.
Todd Motto a un article qui creuse plus en profondeur sur les déclarations switch par rapport aux objets littéraux. Vous pouvez le lire ici.

Refonte de la syntaxe

Pour l’exemple ci-dessus, nous pouvons réellement refactoriser notre code pour obtenir le même résultat avec Array.filter.

 1const fruits = [
 2    { name: 'apple', color: 'red' }, 
 3    { name: 'strawberry', color: 'red' }, 
 4    { name: 'banana', color: 'yellow' }, 
 5    { name: 'pineapple', color: 'yellow' }, 
 6    { name: 'grape', color: 'purple' }, 
 7    { name: 'plum', color: 'purple' }
 8];
 9function test(color) {
10  // use Array filter to find fruits in color
11  return fruits.filter(f => f.color == color);
12}

Il y a toujours plus d’une façon d’obtenir le même résultat. Nous en avons montré 4 avec le même exemple. Coder, c’est amusant !

5. Utilisez Array.every & Array.some pour tous les critères / critères partiels

Ce dernier conseil concerne l’utilisation de la nouvelle fonction JavaScript Array (mais pas si nouvelle) pour réduire les lignes de code. Regardez le code ci-dessous - nous voulons vérifier si tous les fruits sont de couleur rouge:

 1const fruits = [
 2    { name: 'apple', color: 'red' },
 3    { name: 'banana', color: 'yellow' },
 4    { name: 'grape', color: 'purple' }
 5  ];
 6function test() {
 7  let isAllRed = true;
 8  // condition: all fruits must be red
 9  for (let f of fruits) {
10    if (!isAllRed) break;
11    isAllRed = (f.color == 'red');
12  }
13  console.log(isAllRed); // false
14}

Le code est si long ! Nous pouvons réduire le nombre de lignes avec Array.every:

 1const fruits = [
 2    { name: 'apple', color: 'red' },
 3    { name: 'banana', color: 'yellow' },
 4    { name: 'grape', color: 'purple' }
 5  ];
 6function test() {
 7  // condition: short way, all fruits must be red
 8  const isAllRed = fruits.every(f => f.color == 'red');
 9  console.log(isAllRed); // false
10}

C’est plus propre, non ? De la même manière, si nous voulons tester si un fruit est rouge, nous pouvons utiliser Array.some pour l’obtenir en une ligne.

 1const fruits = [
 2    { name: 'apple', color: 'red' },
 3    { name: 'banana', color: 'yellow' },
 4    { name: 'grape', color: 'purple' }
 5];
 6function test() {
 7  // condition: if any fruit is red
 8  const isAnyRed = fruits.some(f => f.color == 'red');
 9  console.log(isAnyRed); // true
10}

Résumé

Produisons ensemble un code plus lisible. J’espère que vous avez appris quelque chose de nouveau dans cet article.

Happy coding!