使用 JavaScript 编写更好的条件语句

巴拉米 等级 588 0 0
标签: 数组

在任何编程语言中,代码需要根据不同的条件在给定的输入中做不同的决定和执行相应的动作。

例如,在一个游戏中,如果玩家生命点为0,游戏结束。在天气应用中,如果在早上被查看,显示一个日出图片,如果是晚上,则显示星星和月亮。在这篇文章中,我们将探索JavaScript中所谓的条件语句如何工作。

如果你使用JavaScript工作,你将写很多包含条件调用的代码。条件调用可能初学很简单,但是还有比写一对对if/else更多的东西。这里有些编写更好更清晰的条件代码的有用提示。

  1. 数组方法 Array.includes

  2. 提前退出 / 提前返回

  3. 用对象字面量或Map替代Switch语句

  4. 默认参数和解构

  5. 用 Array.every & Array.some 匹配全部/部分内容

  6. 使用可选链和空值合并

1. 数组方法 Array.includes

使用 Array.includes 进行多条件选择

例如:

function printAnimals(animal) {  
    if (animal === 'dog' || animal === 'cat') {  
        console.log(I have a ${animal});  
    }  
}  

console.log(printAnimals('dog')); // I have a dog  

上面的代码看起来很好因为我们只检查了两个动物。然而,我们不确定用户输入。如果我们要检查任何其他动物呢?如果我们通过添加更多“或”语句来扩展,代码将变得难以维护和不清晰。

解决方案:

我们可以通过使用 Array.includes 来重写上面的条件

function printAnimals(animal) {  
   const animals = ['dog', 'cat', 'hamster', 'turtle'];   

   if (animals.includes(animal)) {  
     console.log(I have a ${animal});  
   }  
}  

console.log(printAnimals('hamster')); // I have a hamster  

这里,我们创建来一个动物数组,所以条件语句可以和代码的其余部分抽象分离出来。现在,如果我们想要检查任何其他动物,我们只需要添加一个新的数组项。

我们也能在这个函数作用域外部使用这个动物数组变量来在代码中的其他任意地方重用它。这是一个编写更清晰、易理解和维护的代码的方法,不是吗?

2. 提前退出 / 提前返回

这是一个精简你的代码的非常酷的技巧。我记得当我开始专业工作时,我在第一天学习使用提前退出来编写条件。

让我们在之前的例子上添加更多的条件。用包含确定属性的对象替代简单字符串的动物。

现在的需求是:

  • 如果没有动物,抛出一个异常

  • 打印动物类型

  • 打印动物名字

  • 打印动物性别

const printAnimalDetails = animal => {  
  let result; // declare a variable to store the final value  

  // condition 1: check if animal has a value  
  if (animal) {  

    // condition 2: check if animal has a type property  
    if (animal.type) {  

      // condition 3: check if animal has a name property  
      if (animal.name) {  

        // condition 4: check if animal has a gender property  
        if (animal.gender) {  
          result = ${animal.name} is a ${animal.gender} ${animal.type};;  
        } else {  
          result = "No animal gender";  
        }  
      } else {  
        result = "No animal name";  
      }  
    } else {  
      result = "No animal type";  
    }  
  } else {  
    result = "No animal";  
  }  

  return result;  
};  

console.log(printAnimalDetails()); // 'No animal'  

console.log(printAnimalDetails({ type: "dog", gender: "female" })); // 'No animal name'  

console.log(printAnimalDetails({ type: "dog", name: "Lucy" })); // 'No animal gender'  

console.log(  
  printAnimalDetails({ type: "dog", name: "Lucy", gender: "female" })  
); // 'Lucy is a female dog'  

你觉得上面的代码怎么样?

它工作得很好,但是代码很长并且维护困难。如果不使用lint工具,找出闭合花括号在哪都会浪费很多时间。😄 想象如果代码有更复杂的逻辑会怎么样?大量的if..else语句。

我们能用三元运算符、&&条件等语法重构上面的功能,但让我们用多个返回语句编写更清晰的代码。

const printAnimalDetails = ({type, name, gender } = {}) => {  
  if(!type) return 'No animal type';  
  if(!name) return 'No animal name';  
  if(!gender) return 'No animal gender';  

// Now in this line of code, we're sure that we have an animal with all //the three properties here.  

  return ${name} is a ${gender} ${type};  
}  

console.log(printAnimalDetails()); // 'No animal type'  

console.log(printAnimalDetails({ type: dog })); // 'No animal name'  

console.log(printAnimalDetails({ type: dog, gender: female })); // 'No animal name'  

console.log(printAnimalDetails({ type: dog, name: 'Lucy', gender: 'female' })); // 'Lucy is a female dog'  

在这个重构过的版本中,也包含了解构和默认参数。默认参数确保如果我们传递undefined作为一个方法的参数,我们仍然有值可以解构,在这里它是一个空对象{}。

通常,在专业领域,代码被写在这两种方法之间。

另一个例子:

function printVegetablesWithQuantity(vegetable, quantity) {  
  const vegetables = ['potato', 'cabbage', 'cauliflower', 'asparagus'];  

  // condition 1: vegetable should be present  
   if (vegetable) {  
     // condition 2: must be one of the item from the list  
     if (vegetables.includes(vegetable)) {  
       console.log(I like ${vegetable});  

       // condition 3: must be large quantity  
       if (quantity >= 10) {  
         console.log('I have bought a large quantity');  
       }  
     }  
   } else {  
     throw new Error('No vegetable from the list!');  
   }  
}  

printVegetablesWithQuantity(null); //  No vegetable from the list!  
printVegetablesWithQuantity('cabbage'); // I like cabbage  
printVegetablesWithQuantity('cabbage', 20);   
// 'I like cabbage  
// 'I have bought a large quantity'  

现在,我们有:

  • 1 if/else 语句过滤非法条件

  • 3 级嵌套if语句 (条件 1, 2, & 3)

一个普遍遵循的规则是:在非法条件匹配时提前退出。

function printVegetablesWithQuantity(vegetable, quantity) {  

  const vegetables = ['potato', 'cabbage', 'cauliflower', 'asparagus'];  

   // condition 1: throw error early  
   if (!vegetable) throw new Error('No vegetable from the list!');  

   // condition 2: must be in the list  
   if (vegetables.includes(vegetable)) {  
      console.log(I like ${vegetable});  

     // condition 3: must be a large quantity  
      if (quantity >= 10) {  
        console.log('I have bought a large quantity');  
      }  
   }  
}  

通过这么做,我们少了一个嵌套层级。当你有一个长的if语句时,这种代码风格特别好。

我们能通过条件倒置和提前返回,进一步减少嵌套的if语句。查看下面的条件2,观察我们是怎么做的

function printVegetablesWithQuantity(vegetable, quantity) {  

  const vegetables = ['potato', 'cabbage', 'cauliflower', 'asparagus'];  

   if (!vegetable) throw new Error('No vegetable from the list!');   
   // condition 1: throw error early  

   if (!vegetables.includes(vegetable)) return;   
   // condition 2: return from the function is the vegetable is not in   
  //  the list   


  console.log(I like ${vegetable});  

  // condition 3: must be a large quantity  
  if (quantity >= 10) {  
      console.log('I have bought a large quantity');  
  }  
}  

通过倒置条件2,代码没有嵌套语句了。这种技术在我们有很多条件并且当任何特定条件不匹配时,我们想停止进一步处理的时候特别有用。

所以,总是关注更少的嵌套和提前返回,但也不要过度地使用。

3. 用对象字面量或Map替代Switch语句

让我们来看看下面的例子,我们想要基于颜色打印水果:

function printFruits(color) {  
  // use switch case to find fruits by color  
  switch (color) {  
    case 'red':  
      return ['apple', 'strawberry'];  
    case 'yellow':  
      return ['banana', 'pineapple'];  
    case 'purple':  
      return ['grape', 'plum'];  
    default:  
      return [];  
  }  
}  

printFruits(null); // []  
printFruits('yellow'); // ['banana', 'pineapple']  

上面的代码没有错误,但是它仍然有些冗长。相同的功能能用对象字面量以更清晰的语法实现:

// use object literal to find fruits by color  
  const fruitColor = {  
    red: ['apple', 'strawberry'],  
    yellow: ['banana', 'pineapple'],  
    purple: ['grape', 'plum']  
  };  

function printFruits(color) {  
  return fruitColor[color] || [];  
}  

另外,你也能用 Map 来实现相同的功能:

// use Map to find fruits by color  
  const fruitColor = new Map()  
    .set('red', ['apple', 'strawberry'])  
    .set('yellow', ['banana', 'pineapple'])  
    .set('purple', ['grape', 'plum']);  

function printFruits(color) {  
  return fruitColor.get(color) || [];  
}  

Map 允许保存键值对,是自从ES2015以来可以使用的对象类型。

对于上面的例子,相同的功能也能用数组方法Array.filter 来实现。

const fruits = [  
    { name: 'apple', color: 'red' },   
    { name: 'strawberry', color: 'red' },   
    { name: 'banana', color: 'yellow' },   
    { name: 'pineapple', color: 'yellow' },   
    { name: 'grape', color: 'purple' },   
    { name: 'plum', color: 'purple' }  
];  

function printFruits(color) {  
  return fruits.filter(fruit => fruit.color === color);  
}  

4. 默认参数和解构

当使用 JavaScript 工作时,我们总是需要检查 null/undefined 值并赋默认值,否则可能编译失败。

function printVegetablesWithQuantity(vegetable, quantity = 1) {   
// if quantity has no value, assign 1  

  if (!vegetable) return;  
    console.log(We have ${quantity} ${vegetable}!);  
}  

//results  
printVegetablesWithQuantity('cabbage'); // We have 1 cabbage!  
printVegetablesWithQuantity('potato', 2); // We have 2 potato!  

如果 vegetable 是一个对象呢?我们能赋一个默认参数吗?

function printVegetableName(vegetable) {   
    if (vegetable && vegetable.name) {  
     console.log (vegetable.name);  
   } else {  
    console.log('unknown');  
   }  
}  

printVegetableName(undefined); // unknown  
printVegetableName({}); // unknown  
printVegetableName({ name: 'cabbage', quantity: 2 }); // cabbage  

在上面的例子中,如果vegetable 存在,我们想要打印 vegetable name, 否则打印"unknown"。

我们能通过使用默认参数和解构来避免条件语句 if (vegetable && vegetable.name) {} 。

// destructing - get name property only  
// assign default empty object {}  

function printVegetableName({name} = {}) {  
   console.log (name || 'unknown');  
}  


printVegetableName(undefined); // unknown  
printVegetableName({ }); // unknown  
printVegetableName({ name: 'cabbage', quantity: 2 }); // cabbage  

因为我们只需要 name 属性,所以我们可以使用 { name } 解构参数,然后我们就能在代码中使用 name 作为变量,而不是 vegetable.name

我们还赋了一个空对象 {} 作为默认值,因为当执行 printVegetableName(undefined) 时会得到一个错误:不能从 undefinednull 解构属性 name ,因为在 undefined 中没有 name 属性。

5. 用 Array.every & Array.some 匹配全部/部分内容

我们能使用数组方法减少代码行。查看下面的代码,我们想要检查是否所有的水果都是红色的:

const fruits = [  
    { name: 'apple', color: 'red' },  
    { name: 'banana', color: 'yellow' },  
    { name: 'grape', color: 'purple' }  
  ];  

function test() {  
  let isAllRed = true;  

  // condition: all fruits must be red  
  for (let f of fruits) {  
    if (!isAllRed) break;  
    isAllRed = (f.color == 'red');  
  }  

  console.log(isAllRed); // false  
}  

这代码太长了!我们能用 Array.every 来减少代码行数:

const fruits = [  
    { name: 'apple', color: 'red' },  
    { name: 'banana', color: 'yellow' },  
    { name: 'grape', color: 'purple' }  
  ];  

function test() {  
  // condition: short way, all fruits must be red  
  const isAllRed = fruits.every(f => f.color == 'red');  

  console.log(isAllRed); // false  
}  

相似地,如果我们想测试是否有任何红色的水果,我们能用一行 Array.some 来实现它。

const fruits = [  
    { name: 'apple', color: 'red' },  
    { name: 'banana', color: 'yellow' },  
    { name: 'grape', color: 'purple' }  
];  

function test() {  
  // condition: if any fruit is red  
  const isAnyRed = fruits.some(f => f.color == 'red');  

  console.log(isAnyRed); // true  
}  

6. 使用可选链和空值合并

这有两个为编写更清晰的条件语句而即将成为 JavaScript 增强的功能。当写这篇文章时,它们还没有被完全支持,你需要使用 Babel 来编译。

可选链允许我们没有明确检查中间节点是否存在地处理 tree-like 结构,空值合并和可选链组合起来工作得很好,以确保为不存在的值赋一个默认值。

这有一个例子:

const car = {  
    model: 'Fiesta',  
    manufacturer: {  
    name: 'Ford',  
    address: {  
        street: 'Some Street Name',  
        number: '5555',  
        state: 'USA'  
      }  
    }  
}   

// to get the car model  
const model = car && car.model || 'default model';  

// to get the manufacturer street  
const street = car && car.manufacturer && car.manufacturer.address &&   
car.manufacturer.address.street || 'default street';  

// request an un-existing property  
const phoneNumber = car && car.manufacturer && car.manufacturer.address   
&& car.manufacturer.phoneNumber;  

console.log(model) // 'Fiesta'  
console.log(street) // 'Some Street Name'  
console.log(phoneNumber) // undefined  

所以,如果我们想要打印是否车辆生产商来自美国,代码将看起来像这样:

const isManufacturerFromUSA = () => {  
   if(car && car.manufacturer && car.manufacturer.address &&   
 car.manufacturer.address.state === 'USA') {  
     console.log('true');  
   }  
}  


checkCarManufacturerState() // 'true'  

你能清晰地看到当有一个更复杂的对象结构时,这能变得多乱。有一些第三方的库有它们自己的函数,像 lodash 或 idx。例如 lodash 有 _.get 方法。然而,JavaScript 语言本身被引入这个特性是非常酷的。

这展示了这些新特性如何工作:

// to get the car model  
const model = car?.model ?? 'default model';  

// to get the manufacturer street  
const street = car?.manufacturer?.address?.street ?? 'default street';  

// to check if the car manufacturer is from the USA  
const isManufacturerFromUSA = () => {  
   if(car?.manufacturer?.address?.state === 'USA') {  
     console.log('true');  
   }  
}  

这看起来很美观并容易维护。它已经到 TC39 stage 3 阶段,让我们等待它获得批准,然后我们就能无处不在地看到这难以置信的语法的使用。

总结

让我们为了编写更清晰、易维护的代码,学习并尝试新的技巧和技术,因为在几个月后,长长的条件看起来像搬石头砸自己的脚。😄

收藏
评论区

相关推荐

What the f*ck JavaScript?
What the fck JavaScript? 一个有趣和棘手的 JavaScript 示例列表。 JavaScript 是一种很好的语言。
JavaScript中的类型
JavaScript中的类型 一、关于类型 什么叫做类型?简单地说,类型就是把内存中的一个二进制序列赋予某种意义。比如,二进制序列0100 0000 0111 0000 0001 0101 0100 1011 1100 0110 1010 0111 1110 1111 1001 1110如果看作是64位无符号整数类型就是4
JavaScript进阶之new的实现
我的前端学习笔记📒 最近花了点时间把笔记整理到语雀上了,方便童鞋们阅读 我的前端学习笔记📒(https://www.yuque.com/wanggangfeng
关于JavaScript 对象的理解
关于JavaScript 对象的理解 对象 理解对象 ECMA262把对象定义为:“无序属性的集合,其属性可以包含基本值、对象或者函数。”严格来讲,这就相当于说对象是一组没有特定顺序的值。对象的每个属性或方法都有一个名字,而每个名字都映射到一个值。 我们可以把ECMAScript的对象想象成散列表:无非就是一组名值对,其中的值可以是
js-Answers一
JavaScript的组成 JavaScript 由以下三部分组成: 1. ECMAScript(核心):JavaScript 语言基础 2. DOM(文档对象模型):规定了访问HTML和XML的接口 3. BOM(浏览器对象模型):提供了浏览器窗口之间进行交互的对象和方法 JS的基本数据类型和引用数据类型
14个优秀 JS 前端框架、库、工具及其使用时机
  这篇文章主要描述现今流行的一些 Javascript web 前端框架,库以及它们的适用场景。   新的 Javascript 库层出不穷,从而Web 社区愈发活跃、多样、在多方面快速发展。详细去描述每一种主流的 Javascript 框架和库近乎
JavaScript 是什么?
前言 引用《JavaScript 高级程序设计第四版》中说的话 ——“从简单的输入验证脚本到强大的编程语言,JavaScript 的崛起没有任何人预测到。它很简单,学会用只要几分钟;它又很复杂,掌握它要很多年。要真正学好用好 JavaScript,理解其本质、历史及局限性是非常重要的”。 面试官:JavaScript 是什么? 我:
理解 Javascript 中的 Async / Await
在本文中,我们将探讨async/await,对于每个Javascript开发人员来说,是异步编程的首选工具。如果您不熟悉javascript,请不要担心,本文将帮助您async/await从头开始理解。 介绍async/await 是javascript中的一种模式,可使您的代码以同步方式执行,但又不影响javascript的异步行为。 定义异步功能要定义一
JavaScript 和 Node.js 中事件循环
1.JavaScript中事件循环可以参考《JavaScript忍者秘籍第二版》第十三章,讲解的很好。JavaScript中事件循环,主要就在理解宏任务和微任务这两种异步任务。宏任务(macrotask): setTimeOut 、 setInterval 、 setImmediate 、 I/O 、 各种callback、 UI渲染 、messageCh
了解什么是 TypeScript
内容纲要 了解什么是 TypeScript TypeScript 基本语法 TypeScript 介绍 TypeScript 是什么TypeScript 是 JavaScript 的强类型版本。然后在编译期去掉类型和特有语法,生成纯粹的 JavaScript代码。由于最终在浏览器中运行的仍然是 JavaScript,所以 TypeScript 并
JAVA回调机制(CallBack)之小红是怎样买到房子的??
JAVA回调机制CallBack 序言最近学习java,接触到了回调机制(CallBack)。初识时感觉比较混乱,而且在网上搜索到的相关的讲解,要么一言带过,要么说的比较单纯的像是给CallBack做了一个定义。当然了,我在理解了回调之后,再去看网上的各种讲解,确实没什么问题。但是,对于初学的我来说,缺了一个循序渐进的过程。此处,将我对回调机制的个人理解,按
一篇文章带你了解JavaScript作用域
在JavaScript中,对象和函数也是变量。在JavaScript中,作用域是你可以访问的变量、对象和函数的集合。JavaScript 有函数作用域: 这个作用域在函数内变化。一、本地JavaScript变量一个变量声明在JavaScript函数内部,成为函数的局部变量。局部变量有局部作用域: 它们只能在函数中访问。JS://code here can n
JavaScript中call()、apply()、bind()的用法
call() apply() bind() 都是用来更改 this 的指向的其中bind() 返回的是一个函数,必须执行才行传参差异:call 、bind 、 apply 这三个函数的第一个参数都是 this 的指向对象,第二个参数差别就来了:call的参数是直接放进去的,第二第三第n个参数全都用逗号分隔,直接放到后面 obj.myFun.call(db,'
javascript实践教程-01-javascript介绍
本节目标1. 了解javascript是什么。2. 了解javascript能干什么。 内容摘要本篇介绍了javascript是什么,为什么要用javascript,ECMAScript标准是什么等。阅读时间大约510分钟。 javascript是什么?javascript是世界上最流行的脚本语言,因为你在电脑、手机、平板上浏览的所有的网页,以及无数基于HT
javascript实践教程-02-javascript入门
本节目标1. 掌握如何编写javascript代码。2. 掌握javascript的3个弹框。3. 掌握javascript的注释。4. 掌握浏览器的调试工具控制台。 内容摘要本篇介绍了如何在网页上编写js代码,如何引入外部js代码文件,js的3个弹框、注释语法,还有浏览器调试工具的控制台使用。阅读时间1520分钟。 script标签如果我们需要在网页中编写