WEB程序员笔记

一个前端开发工程师的个人博客

在JavaScript中使用集合时应避免的5种反模式

在JavaScript中使用集合可能会成为一项艰巨的任务,尤其是当功能块中发生了很多事情时。

你有没有想过如何在代码中的一些项目看起来更美观比别人?还是当一个看似困难的项目最终变得如此之小时,您的脑子里突然浮出水面,想知道他们如何能够同时保持其简单和强大?

当在保持良好性能的情况下易于阅读项目时,可以确保将相当好的惯例应用于代码。

当代码写得一团糟时,很容易变成相反的样子。此时,很容易出现这样的情况,即修改少量代码最终会给您的应用程序带来灾难性的问题-换句话说,抛出的错误使网页无法继续运行。遍历集合时,看到糟糕的代码运行可能会变得很恐怖。

实施更好的做法是禁止自己采取短途行动,这反过来又有助于确保担保。这意味着从长远来看,使代码尽可能地可维护取决于

本文将介绍5种反模式,以避免在JavaScript中使用集合时

本文中的许多代码示例都将体现一种称为函数式编程的编程范例。正如Eric Elliot解释的那样,函数式编程是“通过组合纯函数,避免共享状态,可变数据和副作用来构建软件的过程。” 。在这篇文章中,我们经常会提到副作用和突变。

以下是在使用集合时应避免的___ JavaScript反模式:

1.过早传递函数作为直接参数

我们将要讨论的第一个反模式是过早地将函数作为直接传递给遍历集合的数组方法的参数。

这是一个简单的例子:

function add(nums, callback) {
  const result = nums[0] + nums[1]
  console.log(result)
  if (callback) {
    callback(result)
  }
}

const numbers = [[1, 2], [2, 2], [18, 1], [4, 5], [8, 9], [0, 0]]

numbers.forEach(add)

那么为什么这是反模式呢?

大多数开发人员,尤其是那些更喜欢函数式编程的开发人员,可能会发现它最好,简洁,性能最佳。我的意思是,看看它。不必这样做:

const numbers = [[1, 2], [2, 2], [18, 1], [4, 5], [8, 9], [0, 0]]

numbers.forEach(function(nums, callback) {
  const result = nums[0] + nums[1]
  console.log(result)
  if (callback) {
    callback(result)
  }
})

仅仅输入函数名称并每天调用它似乎要好得多:

const numbers = [[1, 2], [2, 2], [18, 1], [4, 5], [8, 9], [0, 0]]

numbers.forEach(add)

在一个完美的世界中,这将是不费吹灰之力就可以在JavaScript中使用我们所有函数的完美解决方案。

但是事实证明,以这种方式过早地传递处理程序可能会导致意外错误。例如,让我们继续看一下前面的示例:

function add(nums, callback) {
  const result = nums[0] + nums[1]
  console.log(result)
  if (callback) {
    callback(result)
  }
}

const numbers = [[1, 2], [2, 2], [18, 1], [4, 5], [8, 9], [0, 0]]

numbers.forEach(add)

我们的add函数需要一个数组,其中第一个索引和第二个索引是数字,然后将它们相加并检查是否存在回调,如果存在则调用它。这里的问题是,callback最终可能会被作为a调用number并导致错误:

《在JavaScript中使用集合时应避免的5种反模式》

2.依靠迭代器函数(例如.map和)的顺序.filter

JavaScript的基本功能按照数组中当前元素的顺序处理集合中的元素。然而,你的代码应该取决于此。

首先,在每种语言和每种库中,迭代顺序都永远不会100%稳定。将每个iteratee函数视为在多个进程中同时运行是一个好习惯。

我看过执行以下操作的代码:

let count = 0

frogs.forEach((frog) => {
  if (count === frogs.length - 1) {
    window.alert(
      `You have reached the last frog. There a total of ${count} frogs`,
    )
  }
  count++
})

大多数情况下,这是完全可以的,但是如果仔细观察,这并不是最安全的方法,因为全局范围内的任何内容都可以更新count。如果发生这种情况,并count最终在代码中的某个地方意外地递减,那么window.alert它将永远无法运行!

在异步操作中工作时甚至会变得更糟:

function someAsyncFunc(timeout) {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve()
    }, timeout)
  })
}

const promises = [someAsyncFunc, someAsyncFunc, someAsyncFunc, someAsyncFunc]

let count = 0
promises.forEach((promise) => {
  count++
  promise(count).then(() => {
    console.log(count)
  })
})

结果:

《在JavaScript中使用集合时应避免的5种反模式》

那些对JavaScript更有经验的人可能会知道为什么我们在控制台上记录了四个数字4而不是这样1, 2, 3, 4。关键是最好index在迭代集合时使用大多数函数接收的第二个参数(通常称为current ),以避免并发:

promises.forEach((promise, index) => {
  promise(index).then(() => {
    console.log(index)
  })
})

结果:

《在JavaScript中使用集合时应避免的5种反模式》

3.过早优化

当您寻求优化时,通常会在选择是偏爱可读性还是速度时做出决定。有时候,把更多的注意力放在优化应用程序的速度上而不是提高代码的可读性可能变得诱人。毕竟,网站的速度至关重要是一个广为接受的真理。但这实际上是一个坏习惯

首先,JavaScript中的集合通常比您想象的要小,并且处理每个操作所花费的时间也比您想象的要快。这里要遵循的一个好规则是,除非您知道某些事情会变慢,否则不要尝试使其变快。这称为过早优化,或者换句话说,尝试优化可能已经在速度上达到最佳的代码。

正如Donald Knuth所说:“真正的问题是程序员花了太多时间来担心在错误的地方和错误的时间的效率;过早的优化是编程中所有(或至少大部分)邪恶的根源。 ”。

在很多情况下,应用更好的速度会更容易,因为代码最终会比需要强调保持混乱的混乱状态中保持快速工作的代码慢一些。

我建议您优先选择可读性,然后再进行测量。如果您使用探查器,并且它报告了应用程序中的瓶颈,请仅对该位进行优化,因为现在您知道它实际上是一个缓慢的代码,而不是尝试在您认为可能很慢的地方尝试对其进行优化。

4.依靠状态

状态是编程中一个非常重要的概念,因为状态使我们能够构建健壮的应用程序,但是如果我们对自己的关注不够,它也会破坏我们的应用程序。

这是在集合中使用状态时的反模式示例:

let toadsCount = 0

frogs.forEach((frog) => {
  if (frog.skin === 'dry') {
    toadsCount++
  }
})

这是一个副作用的示例,一定要提防这些问题,因为它可能会引起以下问题:

  • 产生意外的副作用(非常危险!)
  • 增加内存使用量
  • 降低应用程序的性能
  • 使您的代码难以阅读/理解
  • 使得测试代码更加困难

那么,编写此方法而不引起副作用的更好方法是什么?还是我们可以使用更好的做法来重写它?

在使用集合时,我们需要在操作过程中使用状态,请记住,我们可以利用某些方法为您提供某些事物(例如对象)的新引用

一个示例使用该.reduce方法:

const toadsCount = frogs.reduce((accumulator, frog) => {
  if (newFrog.skin === 'dry') {
    accumulator++
  }
  return accumulator
}, 0)

因此,这里发生的是我们正在与其块内的某个状态进行交互,但是我们还利用了第二个参数来.reduce确定在初始化时可以在何处重新创建值。与我们之前的片段相比,这是一种更好的方法,因为我们不会改变范围之外的任何东西。这使我们toadsCount成为处理不可变集合并避免副作用的示例。

5.变元

发生变异的东西手段在形式上或本质改变。这是在JavaScript中特别要注意的重要概念,特别是在函数式编程的情况下。可变的东西可以更改,而不变的东西不能(或不应该)更改。

这是一个例子:

const frogs = [
  { name: 'tony', isToad: false },
  { name: 'bobby', isToad: true },
  { name: 'lisa', isToad: false },
  { name: 'sally', isToad: true },
]

const toToads = frogs.map((frog) => {
  if (!frog.isToad) {
    frog.isToad = true
  }
  return frog
})

我们期望将toToads返回的新数组的值frogs通过将其isToad属性翻转为转换为蟾蜍的值true

但这是一个令人不寒而栗的地方:当我们frog通过执行以下操作来对某些对象进行突变时frog.isToad = true,我们也无意间frogs数组内部的对象进行了突变

我们可以看到frogs它们现在都是蟾蜍,因为它已经变异了:

《在JavaScript中使用集合时应避免的5种反模式》

发生这种情况是因为JavaScript中的对象都是通过引用传递的!如果我们在代码的10个不同位置周围分配相同的对象怎么办?

例如,如果我们在整个代码中将此引用分配给10个不同的变量,然后在代码的稍后某个位置对变量7进行了突变,则在内存中持有对该同一指针的引用的所有其他变量也将被突变

const bobby = {
  name: 'bobby',
  age: 15,
  gender: 'male',
}

function stepOneYearIntoFuture(person) {
  person.age++
  return person
}

const doppleGanger = bobby
const doppleGanger2 = bobby
const doppleGanger3 = bobby
const doppleGanger4 = bobby
const doppleGanger5 = bobby
const doppleGanger6 = bobby
const doppleGanger7 = bobby
const doppleGanger8 = bobby
const doppleGanger9 = bobby
const doppleGanger10 = bobby

stepOneYearIntoFuture(doppleGanger7)

console.log(doppleGanger)
console.log(doppleGanger2)
console.log(doppleGanger4)
console.log(doppleGanger7)
console.log(doppleGanger10)

doppleGanger5.age = 3

console.log(doppleGanger)
console.log(doppleGanger2)
console.log(doppleGanger4)
console.log(doppleGanger7)
console.log(doppleGanger10)

结果:

《在JavaScript中使用集合时应避免的5种反模式》

相反,我们可以做的是每次我们要对其进行突变时都创建新的引用:

const doppleGanger = { ...bobby }
const doppleGanger2 = { ...bobby }
const doppleGanger3 = { ...bobby }
const doppleGanger4 = { ...bobby }
const doppleGanger5 = { ...bobby }
const doppleGanger6 = { ...bobby }
const doppleGanger7 = { ...bobby }
const doppleGanger8 = { ...bobby }
const doppleGanger9 = { ...bobby }
const doppleGanger10 = { ...bobby }

结果:

《在JavaScript中使用集合时应避免的5种反模式》

结论

到此结束本文的结尾!我发现您发现这很有价值,并在将来寻找更多!

点赞

发表评论

电子邮件地址不会被公开。 必填项已用*标注