首页 >web前端 >js教程 >不变的阵列方法:编写清洁器JavaScript代码

不变的阵列方法:编写清洁器JavaScript代码

Jennifer Aniston
Jennifer Aniston原创
2025-02-10 11:12:09328浏览

Immutable Array Methods: Write Cleaner JavaScript Code

本文承接JavaScript变量赋值和变异指南,探讨了变异数组方法带来的问题及解决方案。许多JavaScript数组方法会直接修改原数组,这在大型应用中容易导致难以追踪的bug。但并非所有方法都如此,Array.prototype.slice()Array.prototype.concat()Array.prototype.map()Array.prototype.filter()等方法会返回一个新数组,原始数组保持不变。为了避免意外变异,我们可以编写自己的不可变数组方法,这些方法返回新的数组对象,而不是修改原数组。本文将涵盖poppushshiftunshiftreversesplice等方法的不可变版本。使用不可变数组方法能编写更清晰、易维护的代码,避免副作用引发的bug。

JavaScript中的数组变异

JavaScript数组是对象,因此可以被修改。许多内置数组方法都会直接修改数组本身,这违反了编程中的最佳实践。以下示例展示了潜在问题:

<code class="language-javascript">const numbers = [1,2,3];
const countdown = numbers.reverse();</code>

这段代码看似正常,我们创建了一个名为numbers的数组,并希望创建一个名为countdown的数组,包含numbers数组中逆序排列的数字。表面上看,它确实起作用了:

<code class="language-javascript">countdown // [3, 2, 1]</code>

reverse()方法也修改了numbers数组,这并非我们想要的结果:

<code class="language-javascript">numbers // [3, 2, 1]</code>

更糟糕的是,两个变量都引用同一个数组,因此对其中一个数组的任何后续更改都会影响另一个数组。如果我们使用Array.prototype.push()方法向countdown数组末尾添加一个值为0的元素,numbers数组也会发生同样的变化(因为它们都引用同一个数组):

<code class="language-javascript">countdown.push(0)
countdown // [3, 2, 1, 0]
numbers // [3, 2, 1, 0]</code>

这种副作用在大型应用中很容易被忽视,并导致难以追踪的bug。

JavaScript中可变的数组方法

除了reverse(),还有许多其他数组方法会导致这种变异:

  • Array.prototype.pop()
  • Array.prototype.push()
  • Array.prototype.shift()
  • Array.prototype.unshift()
  • Array.prototype.reverse()
  • Array.prototype.sort()
  • Array.prototype.splice()

与之相对的是,一些方法不会修改原数组,而是返回一个新数组:

  • Array.prototype.slice()
  • Array.prototype.concat()
  • Array.prototype.map()
  • Array.prototype.filter()

这些方法会根据执行的操作返回一个新数组。例如,map()方法可以用来将数组中的所有数字加倍:

<code class="language-javascript">const numbers = [1,2,3];
const countdown = numbers.reverse();</code>

现在,如果我们检查numbers数组,可以看到它没有受到方法调用的影响:

<code class="language-javascript">countdown // [3, 2, 1]</code>

为什么有些方法会修改数组,而有些方法不会呢?这似乎没有明确的理由,但最近添加的方法倾向于使其不可变。记住哪些方法会修改数组,哪些方法不会修改数组,可能会比较困难。

不可变数组方法:修复变异问题

我们已经确定变异可能导致问题,并且许多数组方法都会导致变异。让我们看看如何避免使用它们。编写一些返回新数组对象而不是修改原数组的函数并不难。这些函数就是我们的不可变数组方法。

因为我们不会修改Array.prototype,所以这些函数总是将数组本身作为第一个参数。

pop

让我们从编写一个新的pop函数开始,该函数返回原始数组的副本,但不包含最后一个项。请注意,Array.prototype.pop()返回从数组末尾弹出的值:

<code class="language-javascript">numbers // [3, 2, 1]</code>

此函数使用Array.prototype.slice()返回数组的副本,但去除了最后一个项。第二个参数-1表示“停止切片,在末尾之前1个位置”。我们可以在下面的示例中看到它是如何工作的:

<code class="language-javascript">countdown.push(0)
countdown // [3, 2, 1, 0]
numbers // [3, 2, 1, 0]</code>

push

接下来,让我们创建一个push()函数,它将返回一个新数组,但在末尾附加了一个新元素:

<code class="language-javascript">const numbers = [1,2,3];
const evens = numbers.map(number => number * 2);</code>

这使用扩展运算符创建数组的副本。然后,它将作为第二个参数提供的value添加到新数组的末尾。这是一个例子:

<code class="language-javascript">numbers // [1, 2, 3]</code>

shiftunshift

我们可以类似地编写Array.prototype.shift()Array.prototype.unshift()的替代方法:

<code class="language-javascript">const pop = array => array.slice(0,-1);</code>

对于我们的shift()函数,我们只是从数组中切除第一个元素而不是最后一个元素。这可以在下面的示例中看到:

<code class="language-javascript">const food = ['?','?','?','?'];
pop(food) // ['?','?','?']</code>

我们的unshift()方法将返回一个新数组,并在数组的开头附加一个新值:

<code class="language-javascript">const push = (array, value) => [...array,value];</code>

扩展运算符允许我们将值按任何顺序放置在数组中。我们只需将新值放在原始数组副本的前面。我们可以在下面的示例中看到它是如何工作的:

<code class="language-javascript">const food = ['?','?','?','?'];
push(food,'?') // ['?','?','?','?','?']</code>

reverse

现在让我们尝试编写Array.prototype.reverse()方法的替代方法。它将返回逆序排列的数组副本,而不是修改原始数组:

<code class="language-javascript">const numbers = [1,2,3];
const countdown = numbers.reverse();</code>

此方法仍然使用Array.prototype.reverse()方法,但应用于我们使用扩展运算符创建的原始数组的副本。在创建对象后立即修改它并没有什么问题,这就是我们在这里所做的。我们可以在下面的示例中看到它是如何工作的:

<code class="language-javascript">countdown // [3, 2, 1]</code>

splice

最后,让我们处理Array.prototype.splice()。这是一个非常通用的函数,因此我们不会完全重写它的功能(尽管这是一个有趣的练习)。相反,我们将关注slice的两个主要用途:从数组中删除项和将项插入数组中。

删除数组项

让我们从一个函数开始,该函数将返回一个新数组,但在给定索引处删除了一个项:

<code class="language-javascript">numbers // [3, 2, 1]</code>

这使用Array.prototype.slice()将数组切成两半——我们要删除的项的两侧。第一个切片返回一个新数组,复制原始数组的元素,直到指定为参数的索引之前的索引。第二个切片返回一个数组,其中包含我们要删除的元素之后的元素,一直到原始数组的末尾。然后,我们使用扩展运算符将它们都放在一个新数组中。我们可以通过尝试删除下面food数组中索引为2的项来检查这是否有效:

<code class="language-javascript">countdown.push(0)
countdown // [3, 2, 1, 0]
numbers // [3, 2, 1, 0]</code>

添加数组项

最后,让我们编写一个函数,该函数将返回一个新数组,并在特定索引处插入一个新值:

<code class="language-javascript">const numbers = [1,2,3];
const evens = numbers.map(number => number * 2);</code>

这与remove()函数的工作方式类似。它创建数组的两个切片,但这次包括提供的索引处的元素。当我们将两个切片重新组合在一起时,我们将作为参数提供的value插入到它们之间。我们可以通过尝试将一个纸杯蛋糕表情符号插入到我们的food数组的中间来检查这是否有效:

<code class="language-javascript">numbers // [1, 2, 3]</code>

现在我们有一组不可变的数组方法,它们不会修改原始数组。您可以将它们保存在一个地方,并在您的项目中使用它们。您可以通过将它们作为单个对象的方法来命名空间它们,或者在需要时按原样使用它们。这些方法应该足以满足大多数数组操作。如果您需要执行不同的操作,请记住黄金法则:首先使用扩展运算符创建原始数组的副本。然后,立即将任何可变方法应用于此副本。

结论

在本文中,我们研究了JavaScript如何通过将修改原始数组的数组方法作为语言的一部分来使生活变得困难。然后,我们编写了自己的不可变数组方法来替换这些函数。

您能想到哪些其他数组方法可以受益于具有不可变版本?

JavaScript中创建和使用不可变数组方法的常见问题解答

什么是JavaScript中的不变性概念?

不变性是编程中的一个基本概念,它指的是在创建对象后无法修改的对象的状态。在JavaScript中,不变性不是默认强制执行的。但是,它是一个强大的概念,可以帮助您编写更可预测、更易于维护的代码。它在函数式编程中特别有用,在函数式编程中,不变性可以防止由更改状态引起的错误和复杂性。

为什么我应该在JavaScript中使用不可变数组方法?

在JavaScript中使用不可变数组方法可以编写更清晰、更易于维护的代码。由于这些方法不会修改原始数组,因此它们可以防止可能导致错误的副作用。这在大型代码库中或处理复杂数据结构时尤其重要。不可变方法返回一个新数组,而原始数组保持不变。这使您的代码更可预测,也更容易调试。

JavaScript中不可变数组方法的一些示例是什么?

JavaScript提供了一些不可变的数组方法,这些方法不会更改原始数组。一些示例包括map()filter()reduce()concat()方法。map()方法创建一个新数组,其中包含对数组中每个元素调用提供的函数的结果。filter()方法创建一个新数组,其中包含通过提供的函数实现的测试的所有元素。reduce()方法将一个函数应用于累加器和数组中的每个元素,以将其减少为单个输出值。concat()方法用于合并两个或多个数组,并返回一个新数组。

如何在JavaScript中使我的数组不可变?

JavaScript没有提供使数组不可变的内置方法。但是,您可以通过使用不会更改原始数组的方法(例如map()filter()reduce()concat())来实现不变性。另一种方法是使用Object.freeze()方法,该方法可以防止向对象添加新属性,防止删除现有属性,并防止更改现有属性的可枚举性、可配置性或可写性。

JavaScript中可变方法和不可变方法有什么区别?

JavaScript中可变方法和不可变方法的主要区别在于它们如何处理原始数组。可变方法会修改原始数组,而不可变方法不会。相反,不可变方法会返回一个新数组。这使您的代码更可预测,也更容易调试,因为它可以防止可能导致错误的副作用。

我可以在JavaScript中将不可变数组与其他数据类型一起使用吗?

是的,您可以在JavaScript中将不可变数组与其他数据类型一起使用。不变性的概念适用于所有数据类型,而不仅仅是数组。例如,您可以将不可变方法与字符串、数字、对象等一起使用。这可以帮助您编写更清晰、更易于维护的代码。

使用不可变数组方法是否存在任何性能影响?

使用不可变数组方法可能会产生一些性能影响,因为它们通常会创建一个新数组,而不是修改原始数组。这可能会导致内存使用量增加,尤其是在大型数组中。但是,在大多数情况下,使用不可变方法的好处(例如更清晰的代码和更少的错误)超过了潜在的性能成本。

如何在JavaScript中使用reduce()方法?

JavaScript中的reduce()方法是一个不可变方法,它将一个函数应用于累加器和数组中的每个元素,以将其减少为单个输出值。以下是如何使用它的示例:

<code class="language-javascript">const numbers = [1,2,3];
const countdown = numbers.reverse();</code>

在此示例中,reduce()方法计算数组中所有元素的总和。

JavaScript中的concat()方法是什么?

JavaScript中的concat()方法用于合并两个或多个数组。此方法不会更改现有数组,而是返回一个新数组。这是一个例子:

<code class="language-javascript">countdown // [3, 2, 1]</code>

在此示例中,concat()方法将array1array2合并到一个新数组array3中。

如何在JavaScript中使用filter()方法?

JavaScript中的filter()方法创建一个新数组,其中包含通过提供的函数实现的测试的所有元素。以下是如何使用它的示例:

<code class="language-javascript">numbers // [3, 2, 1]</code>

在此示例中,filter()方法创建一个新数组,其中包含大于3的数字。

以上是不变的阵列方法:编写清洁器JavaScript代码的详细内容。更多信息请关注PHP中文网其他相关文章!

声明:
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn