『译』5 个小技巧,让你在 JavaScript 中写出更好的条件语句

October 21,2018
0
JavaScript、译文

为多条件使用 Array.includes

我们一起看看下面的例子:

// condition
function test(fruit) {
  if (fruit === 'apple' || fruit === 'strawberry') {
    console.log('red')
  }
}

乍一看,上面的例子没有毛病。但是,如果我们拥有更多的红色水果,比如 cherrycranberries 呢?难道我们要使用更多的 || 来扩展这条语句吗?

我们可以使用 Array.includes(Array.includes) 来重写上面的条件:

function test(fruit) {
  // 把条件提取为一个数组
  const redFruits = ['apple', 'strawberry', 'cherry', 'cranberries']
  if (redFruits.includes(fruit)) {
    console.log('red')
  }
}

我们把红色水果(条件)提取成一个数组。通过这种方式,代码看起来更简洁。

减少嵌套,优先返回

让我们把上面的示例扩展一下,包含另外两个条件:

  • 如果没有提供水果,抛出错误
  • 如果水果的质量大于 10,接受并打印水果的质量
function test(fruit, quantity) {
  const redFruits = ['apple', 'strawberry', 'cherry', 'cranberries']

  // 条件1:fruit 必须有值
  if (fruit) {
    // 条件2:fruit 必须是红色
    if (redFruits.includes(fruit)) {
      console.log('red')

      // 条件3:质量必须大于 10
      if (quantity > 10) {
        console.log('big quantity')
      }
    }
  } else {
    throw new Error('no fruit')
  }
}

// 测试结果
test(null) // error: no fruit
test('apple') // 'red'
test('apple', 20) // 'red', 'big quantity'

观察上面的代码,我们可以得到:

  • 1 if/else 语句会筛选出无效条件
  • 3 层嵌套的 if 语句(条件 1、2、3)

我个人常用的一条规则是:当发现无效条件时尽早返回

// 当发现无效条件时尽早返回
function test(fruit, quantity) {
  const redFruits = ['apple', 'strawberry', 'cherry', 'cranberries']

  // 条件1:优先返回
  if (!fruit) throw new Error('no fruit')

  if (fruit) {
    // 条件2:fruit 必须是红色
    if (redFruits.includes(fruit)) {
      console.log('red')

      // 条件3:质量必须大于 10
      if (quantity > 10) {
        console.log('big quantity')
      }
    }
  }
}

这样改造之后,我们减少了一层嵌套。这种代码风格比较好,特别是当你有很长的 if 语句时(想象一下你需要滚动到最底部才知道有一个 else 语句,非常不爽)。

我们可以通过反转条件和优先返回来继续合并 if 嵌套。观察下面的条件 2,看看我们是如何做到的:

// 当发现无效条件时尽早返回
function test(fruit, quantity) {
  const redFruits = ['apple', 'strawberry', 'cherry', 'cranberries']

  // 条件1:优先返回
  if (!fruit) throw new Error('no fruit')
  // 条件2:当水果不是红色时,返回
  if (!redFruits.includes(fruit)) return

  console.log('red')

  // 条件3:质量必须大于 10
  if (quantity > 10) {
    console.log('big quantity')
  }
}

通过反转条件 2,我们的代码现在摆脱了嵌套语句。当我们的逻辑很长,并且当某一个条件不满足时停止进一步地处理时,这个技术非常有用。

然而,这并不是一件很难的事情。问问自己,在这个版本中(没有嵌套)代码是否比之前本的代码更加易读?

对于我来说,我将保留在之前的一个版本。因为:

  • 使用 if 嵌套,代码简单明了
  • 反转条件可能需要花费更多思考(增加认知负荷)

因此,总是保持:减少嵌套,优先返回,但不要过度。如果你感兴趣,这里有一篇文章和 StackOverflow 讨论,它们在这个话题上有深入的讨论。

使用函数默认参数和解构

我想下面的代码看起来很熟悉吧,在 JavaScript 中,我们总是需要检查 null/undefined 值,并为其赋予默认值:

function test(fruit, quantity) {
  if (!fruit) return
  const q = quantity || 1 // 如果没有提供 quantity,将其赋值为 1

  console.log(`We have ${q} ${fruit}`)
}

// 测试结果
test('banana') // We have 1 banana
test('apple', 2) // We have 2 apple

事实上,我们可以通过函数默认参数来消除变量 q

function test(fruit, quantity = 1) {
  // 如果没有提供 quantity,将其赋值为 1
  if (!fruit) return

  console.log(`We have ${q} ${fruit}`)
}

// 测试结果
test('banana') // We have 1 banana
test('apple', 2) // We have 2 apple

是不是更简单直观?请注意,每个参数都有它自己的默认函数参数。例如,我们也可以为 fruit 赋予默认值:function(test = 'unknown', quantity = 1)

如果 fruit 是一个对象呢?我们能为其指定默认参数么?

function test(fruit) {
  if (fruit && fruit.name) {
    console.log(fruit.name)
  } else {
    console.log('unknown')
  }
}

// 测试结果
test(null) // unknown
test({}) // unknown
test({ name: 'apple', color: 'red' }) // apple

瞧瞧上面的例子,如果水果名称可用,我们将输出水果名称,否则输出 unknown。我们可以通过使用默认函数参数和解构来避免 fruit && fruit.name 检查:

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

// 测试结果
test(undefined) // unknown
test({}) // unknown
test({ name: 'apple', color: 'red' }) // apple

我们只需要水果的 name 属性,所以可以通过 {name} 来解构参数,然后我们就可以在代码中将 name 作为变量,而不是 fruit.name

我们也指定了一个空对象 {} 作为默认值。如果不这样做,在执行 test(undefined) 时会出现错误——Cannot destructure property name of 'undefined' or 'null'.,因为 undefined 中没有 name 属性。

如果你不介意使用第三方库,这里有几种减少空检查的方法:

  • 使用 Lodash get 方法
  • 使用 Facebook 的开源库 idx(结合 Babel.js)

下面是使用 Lodash 的例子:

function test(fruit) {
  // 获取 name 属性,如果不可用,则指定默认值为 unknown
  console.log(_.get(fruit, 'name', 'unknown'))
}

// 测试结果
//test results
test(undefined) // unknown
test({}) // unknown
test({ name: 'apple', color: 'red' }) // apple

你可以在这儿运行示例代码。此外,如果你是函数式编程(FP)的爱好者,你还可以选择使用 Lodash fp

使用 Map/Object 字面量,而非 switch 语句

让看看下面的例子,我们要基于颜色输出水果:

function test(color) {
  switch (color) {
    case 'red':
      return ['apple', 'strawberry']
    case 'yellow':
      return ['banana', 'pineapple']
    case 'purple':
      return ['grape', 'plum']
    default:
      return []
  }
}

// 测试结果
test(null) // []
test('yellow') // ['banana', 'pineapple']

上面的代码似乎并没有错,但我觉得它很啰嗦。使用更加简洁的对象字面量语法可以实现相同的结果:

const fruitColor = {
  red: ['apple', 'strawberry'],
  yellow: ['banana', 'pineapple'],
  purple: ['grape', 'plum'],
}
function test(color) {
  return fruitColor[color]
}

另外,Map 也可以实现相同的结果:

const fruitColor = new Map()
  .set('red', ['apple', 'strawberry'])
  .set('yellow', ['banana', 'pineapple'])
  .set('purple', ['grape', 'plum'])

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

Map 是 ES2015 中的对象类型,它允许你存储键值对。

那么我们应该禁止使用 switch 语句吗?不要局限于此。就我个人而言,我会尽可能使用对象字面量,但我也不会设置规则来阻止,使用哪种取决于你应用的场景。

Todd Motto 有一篇深入比较 switch 语句和对象字面量的文章,你可以读一读

总结:重构语法

对于上面的例子,实际上我们可以使用 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 test(color) {
  return fruits.filter((fruit) => fruit.color === color)
}

有不止一种方法可以实现相同的结果,在这儿我们展示了四个示例。写代码非常有趣!

为所有/部分条件使用 Array.every/Array.some

最后一个提示是利用最新的(但也并不是最新) JavaScript 数组函数来合并多行代码。思考下面的代码,我们要检查是否所有的水果都是红色:

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

function test() {
  let isAllRed = true

  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() {
  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() {
  const isAnyRed = fruits.some((f) => f.color === 'red')
  console.log(isAnyRed) // true
}

总结

让我们一起“生产”更多易读的代码。我希望你能从这篇文章中学到新的东西。

以上。Happy coding!


商业转载请联系站长获得授权,非商业转载请注明本文出处及文章链接,您可以自由地在任何媒体以任何形式复制和分发作品,也可以修改和创作,但是分发衍生作品时必须采用相同的许可协议。
本文采用 CC BY-NC-SA 4.0 - 非商业性使用 - 相同方式共享 4.0 国际进行许可。