首頁 > web前端 > js教程 > 不變的陣列方法:編寫清潔器JavaScript代碼

不變的陣列方法:編寫清潔器JavaScript代碼

Jennifer Aniston
發布: 2025-02-10 11:12:09
原創
318 人瀏覽過

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數組是對象,因此可以被修改。許多內置數組方法都會直接修改數組本身,這違反了編程中的最佳實踐。以下示例展示了潛在問題:

const numbers = [1,2,3];
const countdown = numbers.reverse();
登入後複製
登入後複製
登入後複製
登入後複製

這段代碼看似正常,我們創建了一個名為numbers的數組,並希望創建一個名為countdown的數組,包含numbers數組中逆序排列的數字。表面上看,它確實起作用了:

countdown // [3, 2, 1]
登入後複製
登入後複製
登入後複製
登入後複製

reverse()方法也修改了numbers數組,這並非我們想要的結果:

numbers // [3, 2, 1]
登入後複製
登入後複製
登入後複製
登入後複製

更糟糕的是,兩個變量都引用同一個數組,因此對其中一個數組的任何後續更改都會影響另一個數組。如果我們使用Array.prototype.push()方法向countdown數組末尾添加一個值為0的元素,numbers數組也會發生同樣的變化(因為它們都引用同一個數組):

countdown.push(0)
countdown // [3, 2, 1, 0]
numbers // [3, 2, 1, 0]
登入後複製
登入後複製
登入後複製

這種副作用在大型應用中很容易被忽視,並導致難以追踪的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()方法可以用來將數組中的所有數字加倍:

const numbers = [1,2,3];
const countdown = numbers.reverse();
登入後複製
登入後複製
登入後複製
登入後複製

現在,如果我們檢查numbers數組,可以看到它沒有受到方法調用的影響:

countdown // [3, 2, 1]
登入後複製
登入後複製
登入後複製
登入後複製

為什麼有些方法會修改數組,而有些方法不會呢?這似乎沒有明確的理由,但最近添加的方法傾向於使其不可變。記住哪些方法會修改數組,哪些方法不會修改數組,可能會比較困難。

不可變數組方法:修復變異問題

我們已經確定變異可能導致問題,並且許多數組方法都會導致變異。讓我們看看如何避免使用它們。編寫一些返回新數組對象而不是修改原數組的函數並不難。這些函數就是我們的不可變數組方法。

因為我們不會修改Array.prototype,所以這些函數總是將數組本身作為第一個參數。

pop

讓我們從編寫一個新的pop函數開始,該函數返回原始數組的副本,但不包含最後一個項。請注意,Array.prototype.pop()返回從數組末尾彈出的值:

numbers // [3, 2, 1]
登入後複製
登入後複製
登入後複製
登入後複製

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

countdown.push(0)
countdown // [3, 2, 1, 0]
numbers // [3, 2, 1, 0]
登入後複製
登入後複製
登入後複製

push

接下來,讓我們創建一個push()函數,它將返回一個新數組,但在末尾附加了一個新元素:

const numbers = [1,2,3];
const evens = numbers.map(number => number * 2);
登入後複製
登入後複製

這使用擴展運算符創建數組的副本。然後,它將作為第二個參數提供的value添加到新數組的末尾。這是一個例子:

numbers // [1, 2, 3]
登入後複製
登入後複製

shiftunshift

我們可以類似地編寫Array.prototype.shift()Array.prototype.unshift()的替代方法:

const pop = array => array.slice(0,-1);
登入後複製

對於我們的shift()函數,我們只是從數組中切除第一個元素而不是最後一個元素。這可以在下面的示例中看到:

const food = ['?','?','?','?'];
pop(food) // ['?','?','?']
登入後複製

我們的unshift()方法將返回一個新數組,並在數組的開頭附加一個新值:

const push = (array, value) => [...array,value];
登入後複製

擴展運算符允許我們將值按任何順序放置在數組中。我們只需將新值放在原始數組副本的前面。我們可以在下面的示例中看到它是如何工作的:

const food = ['?','?','?','?'];
push(food,'?') // ['?','?','?','?','?']
登入後複製

reverse

現在讓我們嘗試編寫Array.prototype.reverse()方法的替代方法。它將返回逆序排列的數組副本,而不是修改原始數組:

const numbers = [1,2,3];
const countdown = numbers.reverse();
登入後複製
登入後複製
登入後複製
登入後複製

此方法仍然使用Array.prototype.reverse()方法,但應用於我們使用擴展運算符創建的原始數組的副本。在創建對像後立即修改它並沒有什麼問題,這就是我們在這裡所做的。我們可以在下面的示例中看到它是如何工作的:

countdown // [3, 2, 1]
登入後複製
登入後複製
登入後複製
登入後複製

splice

最後,讓我們處理Array.prototype.splice()。這是一個非常通用的函數,因此我們不會完全重寫它的功能(儘管這是一個有趣的練習)。相反,我們將關注slice的兩個主要用途:從數組中刪除項和將項插入數組中。

刪除數組項

讓我們從一個函數開始,該函數將返回一個新數組,但在給定索引處刪除了一個項:

numbers // [3, 2, 1]
登入後複製
登入後複製
登入後複製
登入後複製

這使用Array.prototype.slice()將數組切成兩半——我們要刪除的項的兩側。第一個切片返回一個新數組,複製原始數組的元素,直到指定為參數的索引之前的索引。第二個切片返回一個數組,其中包含我們要刪除的元素之後的元素,一直到原始數組的末尾。然後,我們使用擴展運算符將它們都放在一個新數組中。我們可以通過嘗試刪除下面food數組中索引為2的項來檢查這是否有效:

countdown.push(0)
countdown // [3, 2, 1, 0]
numbers // [3, 2, 1, 0]
登入後複製
登入後複製
登入後複製

添加數組項

最後,讓我們編寫一個函數,該函數將返回一個新數組,並在特定索引處插入一個新值:

const numbers = [1,2,3];
const evens = numbers.map(number => number * 2);
登入後複製
登入後複製

這與remove()函數的工作方式類似。它創建數組的兩個切片,但這次包括提供的索引處的元素。當我們將兩個切片重新組合在一起時,我們將作為參數提供的value插入到它們之間。我們可以通過嘗試將一個紙杯蛋糕表情符號插入到我們的food數組的中間來檢查這是否有效:

numbers // [1, 2, 3]
登入後複製
登入後複製

現在我們有一組不可變的數組方法,它們不會修改原始數組。您可以將它們保存在一個地方,並在您的項目中使用它們。您可以通過將它們作為單個對象的方法來命名空間它們,或者在需要時按原樣使用它們。這些方法應該足以滿足大多數數組操作。如果您需要執行不同的操作,請記住黃金法則:首先使用擴展運算符創建原始數組的副本。然後,立即將任何可變方法應用於此副本。

結論

在本文中,我們研究了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()方法是一個不可變方法,它將一個函數應用於累加器和數組中的每個元素,以將其減少為單個輸出值。以下是如何使用它的示例:

const numbers = [1,2,3];
const countdown = numbers.reverse();
登入後複製
登入後複製
登入後複製
登入後複製

在此示例中,reduce()方法計算數組中所有元素的總和。

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

JavaScript中的concat()方法用於合併兩個或多個數組。此方法不會更改現有數組,而是返回一個新數組。這是一個例子:

countdown // [3, 2, 1]
登入後複製
登入後複製
登入後複製
登入後複製

在此示例中,concat()方法將array1array2合併到一個新數組array3中。

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

JavaScript中的filter()方法創建一個新數組,其中包含通過提供的函數實現的測試的所有元素。以下是如何使用它的示例:

numbers // [3, 2, 1]
登入後複製
登入後複製
登入後複製
登入後複製

在此示例中,filter()方法創建一個新數組,其中包含大於3的數字。

以上是不變的陣列方法:編寫清潔器JavaScript代碼的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
作者最新文章
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板