數組提供了很多方法。為了讓事情變得更容易,在本章中,它們被分成幾組。
我們已經知道從開頭或結尾添加和刪除項目的方法:
arr.push(...items)
– 將項目加入到末尾,
arr.pop()
– 從最後提取一個項目,
arr.shift()
– 從開頭擷取一個項目,
arr.unshift(...items)
– 將項目加入開頭。
這裡還有其他一些。
如何從陣列中刪除一個元素?
數組是對象,所以我們可以嘗試使用delete
:
讓 arr = ["我", "走", "回家"]; 刪除arr[1]; // 刪除“去” 警報( arr[1] ); // 不明確的 // 現在 arr = ["I", , "home"]; 警報(arr.length); // 3
該元素被刪除,但數組仍然有 3 個元素,我們可以看到arr.length == 3
。
這是很自然的,因為delete obj.key
會刪除key
上的值。這就是它的全部作用。適合物體。但對於數組,我們通常希望其餘元素移動並佔據釋放的位置。我們希望現在有一個更短的陣列。
因此,應該使用特殊的方法。
arr.splice 方法是陣列的瑞士軍刀。它可以做一切:插入、刪除和替換元素。
語法是:
arr.splice(start[,deleteCount,elem1,...,elemN])
它從索引start
開始修改arr
:刪除deleteCount
元素,然後在其位置插入elem1, ..., elemN
。傳回已刪除元素的陣列。
此方法透過範例很容易掌握。
我們先從刪除開始:
let arr = ["我", "學習", "JavaScript"]; arr.splice(1, 1); // 從索引 1 刪除 1 個元素 警報( arr ); // [“我”,“JavaScript”]
容易,對吧?從索引1
開始,它刪除了1
元素。
在下一個範例中,我們刪除 3 個元素並將它們替換為另外兩個:
let arr = ["我", "學習", "JavaScript", "對", "現在"]; // 刪除 3 個第一個元素並用另一個元素取代它們 arr.splice(0, 3, "我們來吧", "跳舞吧"); alert( arr ) // 現在 ["讓我們", "跳舞", "對", "現在"]
這裡我們可以看到splice
回傳刪除元素的陣列:
let arr = ["我", "學習", "JavaScript", "對", "現在"]; // 刪除2個第一個元素 讓刪除 = arr.splice(0, 2); 警報(已刪除); // "I", "study" <-- 刪除元素的陣列
splice
方法也能夠插入元件而不需要任何移除。為此,我們需要將deleteCount
設為0
:
let arr = ["我", "學習", "JavaScript"]; // 從索引 2 //刪除0 // 然後插入“複雜”和“語言” arr.splice(2, 0, "複雜", "語言"); 警報( arr ); // “我”、“學習”、“複雜”、“語言”、“JavaScript”
允許負索引
在這裡和其他數組方法中,允許負索引。它們指定從陣列末端開始的位置,如下所示:
令 arr = [1, 2, 5]; // 從索引-1開始(距離最後一步) //刪除0個元素, //然後插入3和4 arr.splice(-1, 0, 3, 4); 警報( arr ); // 1,2,3,4,5
方法 arr.slice 比類似的arr.splice
簡單得多。
語法是:
arr.slice([開始], [結束])
它傳回一個新數組,複製從索引start
到end
所有項目(不包括end
)。 start
和end
都可以是負數,在這種情況下,假定從陣列結尾開始的位置。
它類似於字串方法str.slice
,但它不是創建子字串,而是創建子數組。
例如:
設arr = [“t”,“e”,“s”,“t”]; 警報( arr.slice(1, 3) ); // e,s(從1複製到3) 警報( arr.slice(-2) ); // s,t(從-2複製到最後)
我們也可以不帶參數來呼叫它: arr.slice()
建立arr
的副本。這通常用於獲取副本以進行進一步的轉換,而這些轉換不應影響原始數組。
arr.concat 方法建立一個新數組,其中包含其他數組的值和其他項目。
語法是:
arr.concat(arg1, arg2...)
它接受任意數量的參數—數組或值。
結果是一個新數組,其中包含arr
中的項目,然後是arg1
、 arg2
等。
如果參數argN
是數組,則複製其所有元素。否則,參數本身將被複製。
例如:
令 arr = [1, 2]; // 根據 arr 和 [3,4] 建立一個數組 警報( arr.concat([3, 4]) ); // 1,2,3,4 // 建立一個陣列:arr 和 [3,4] 和 [5,6] 警報( arr.concat([3, 4], [5, 6]) ); // 1,2,3,4,5,6 // 從 arr 和 [3,4] 建立一個數組,然後新增值 5 和 6 警報( arr.concat([3, 4], 5, 6) ); // 1,2,3,4,5,6
通常,它只複製數組中的元素。其他對象,即使它們看起來像數組,也會作為一個整體添加:
令 arr = [1, 2]; 設 arrayLike = { 0:“某事”, 長度:1 }; 警報(arr.concat(arrayLike)); // 1,2,[物件物件]
…但是如果一個類似數組的物件有一個特殊的Symbol.isConcatSpreadable
屬性,那麼它會被concat
視為一個陣列:它的元素會被添加:
令 arr = [1, 2]; 設 arrayLike = { 0:“某事”, 1:“其他”, [Symbol.isConcatSpreadable]:true, 長度:2 }; 警報(arr.concat(arrayLike)); // 1,2,某事,其他
arr.forEach 方法允許為陣列的每個元素執行一個函數。
文法:
arr.forEach(函數(項目,索引,陣列){ // ... 對某個項目執行某些操作 });
例如,這顯示了數組的每個元素:
// 對每個元素呼叫警報 [“比爾博”,“甘道夫”,“戒靈”].forEach(alert);
這段程式碼更詳細地說明了它們在目標數組中的位置:
["比爾博", "甘道夫", "戒靈"].forEach((項目, 索引, 數組) => { alert(`${item} 位於 ${array} 中的索引 ${index}`); });
函數的結果(如果它傳回任何結果)將被丟棄並忽略。
現在讓我們介紹在數組中搜尋的方法。
方法 arr.indexOf 和 arr.includes 具有類似的語法,並且與字串對應的方法本質上相同,但對項目而不是字元進行操作:
arr.indexOf(item, from)
– 尋找從索引from
開始的item
,並傳回找到它的索引,否則-1
。
arr.includes(item, from)
– 尋找從索引from
開始的item
,如果找到則傳回true
。
通常,這些方法僅與一個參數一起使用:要搜尋的item
。預設情況下,搜尋從頭開始。
例如:
令 arr = [1, 0, false]; 警報( arr.indexOf(0) ); // 1 警報( arr.indexOf(false) ); // 2 警報( arr.indexOf(null) ); // -1 警報( arr.includes(1) ); // 真的
請注意, indexOf
使用嚴格相等===
進行比較。因此,如果我們查找false
,它會準確地找到false
而不是零。
如果我們想檢查數組中是否存在item
並且不需要索引,那麼arr.includes
是首選。
方法 arr.lastIndexOf 與indexOf
相同,但從右到左。
讓水果 = ['蘋果', '橘子', '蘋果'] 警報(fruits.indexOf('蘋果')); // 0(第一個蘋果) 警報(fruits.lastIndexOf('蘋果')); // 2(最後一個蘋果)
includes
方法正確處理NaN
includes
的一個次要但值得注意的功能是它可以正確處理NaN
,這與indexOf
不同:
常量 arr = [NaN]; 警報( arr.indexOf(NaN) ); // -1(錯誤,應該是0) Alert( arr.includes(NaN) );// true(正確)
這是因為includes
是很晚才添加到 JavaScript 中的,並且在內部使用了更新的比較演算法。
想像一下我們有一個物件數組。我們如何找到具有特定條件的物件?
這裡 arr.find(fn) 方法就派上用場了。
語法是:
讓結果= arr.find(函數(項目,索引,數組){ // 如果傳回true,則傳回item並停止迭代 // 對於虛假場景回傳未定義 });
針對數組的元素依序呼叫函數:
item
是元素。
index
是它的索引。
array
是數組本身。
如果傳回true
,則停止搜尋並傳回該item
。如果沒有找到任何內容,則傳回undefined
。
例如,我們有一個使用者數組,每個使用者都有字段id
和name
。讓我們找出id == 1
的那個:
讓使用者= [ {id:1,姓名:「約翰」}, {id:2, 姓名:"皮特"}, {id:3,姓名:「瑪麗」} ]; 讓 user = users.find(item => item.id == 1); 警報(用戶名); // 約翰
在現實生活中,物件陣列是很常見的東西,所以find
方法非常有用。
請注意,在範例中,我們提供了使用一個參數來find
函數item => item.id == 1
功能。這是典型的,很少使用該函數的其他參數。
arr.findIndex 方法具有相同的語法,但傳回找到元素的索引而不是元素本身。如果未找到任何內容,則傳回值-1
。
arr.findLastIndex 方法類似於findIndex
,但從右向左搜索,類似於lastIndexOf
。
這是一個例子:
讓使用者= [ {id:1,姓名:「約翰」}, {id:2, 姓名:"皮特"}, {id:3,姓名:「瑪麗」}, {id:4,姓名:「約翰」} ]; // 尋找第一個 John 的索引 Alert(users.findIndex(user => user.name == 'John')); // 0 // 尋找最後一個約翰的索引 Alert(users.findLastIndex(user => user.name == 'John')); // 3
find
方法尋找使函數傳回true
的單一(第一個)元素。
如果可能很多,我們可以使用arr.filter(fn)。
語法與find
類似,但filter
傳回所有符合元素的陣列:
讓結果= arr.filter(函數(項目,索引,數組){ // 如果 true 項被推送到結果並且迭代繼續 // 如果沒有找到則傳回空數組 });
例如:
讓使用者= [ {id:1,姓名:「約翰」}, {id:2, 姓名:"皮特"}, {id:3,姓名:「瑪麗」} ]; // 傳回前兩個使用者的數組 讓 someUsers = users.filter(item => item.id < 3); 警報(someUsers.length); // 2
讓我們繼續討論轉換和重新排序數組的方法。
arr.map 方法是最有用且最常用的方法之一。
它為數組的每個元素調用該函數並傳回結果數組。
語法是:
讓結果= arr.map(函數(項目,索引,陣列){ // 傳回新值而不是項目 });
例如,這裡我們將每個元素轉換為其長度:
let lengths = ["Bilbo", "Gandalf", "Nazgul"].map(item => item.length); 警報(長度); // 5,7,6
對 arr.sort() 的呼叫對數組進行就地排序,更改其元素順序。
它也傳回排序後的數組,但傳回的值通常被忽略,因為arr
本身被修改了。
例如:
令 arr = [ 1, 2, 15 ]; // 此方法對arr的內容重新排序 arr.sort(); 警報( arr ); // 1, 15, 2
您注意到結果有什麼奇怪的嗎?
順序變為1, 15, 2
。不正確。但為什麼?
預設情況下,項目會按字串排序。
從字面上看,所有元素都轉換為字串以進行比較。對於字串,應用字典順序,實際上是"2" > "15"
。
要使用我們自己的排序順序,我們需要提供一個函數作為arr.sort()
的參數。
該函數應該比較兩個任意值並傳回:
函數比較(a,b){ 如果 (a > b) 返回 1; // 如果第一個值大於第二個值 如果(a==b)返回0; // 如果值相等 如果 (a < b) 返回 -1; // 如果第一個值小於第二個值 }
例如,要按數字排序:
函數比較數值(a,b){ 如果 (a > b) 返回 1; 如果(a==b)返回0; 如果 (a < b) 返回 -1; } 令 arr = [ 1, 2, 15 ]; arr.sort(比較數值); 警報(arr); // 1, 2, 15
現在它按預期工作了。
讓我們退到一邊,想想發生了什麼事。 arr
可以是任何內容的數組,對嗎?它可能包含數字、字串、物件或其他內容。我們有一套一些物品。為了對其進行排序,我們需要一個知道如何比較其元素的排序函數。預設是字串順序。
arr.sort(fn)
方法實作通用排序演算法。我們不需要關心它內部是如何運作的(大多數時候是優化的快速排序或 Timsort)。它將遍歷數組,使用提供的函數比較其元素並對它們重新排序,我們需要的只是提供進行比較的fn
。
順便說一句,如果我們想知道比較了哪些元素——沒有什麼可以阻止我們提醒它們:
[1, -2, 15, 2, 0, 8].sort(函數(a, b) { 警報(a +“<>”+b); 返回 a - b; });
該演算法可能會在此過程中將一個元素與多個其他元素進行比較,但它會嘗試盡可能少地進行比較。
比較函數可以傳回任意數字
實際上,比較函數只需要傳回一個正數表示“更大”,而傳回一個負數表示“較小”。
這允許編寫更短的函數:
令 arr = [ 1, 2, 15 ]; arr.sort(函數(a, b) { 回傳 a - b; }); 警報(arr); // 1, 2, 15
箭頭函數為最佳
還記得箭頭函數嗎?我們可以在這裡使用它們來進行更整齊的排序:
arr.sort( (a, b) => a - b );
這與上面較長的版本完全相同。
對字串使用localeCompare
還記得字串比較演算法嗎?預設情況下,它按代碼比較字母。
對於許多字母表,最好使用str.localeCompare
方法來正確排序字母,例如Ö
。
例如,讓我們用德語對幾個國家進行排序:
讓國 = ['Österreich', '安道爾', '越南']; 警報(國家/地區.sort((a,b)=> a> b?1:-1)); // 安道爾、越南、Österreich(錯誤) 警報(國家/地區.sort((a,b)=> a.localeCompare(b))); // 安道爾、奧地利、越南(正確!)
方法 arr.reverse 反轉arr
中元素的順序。
例如:
令 arr = [1, 2, 3, 4, 5]; arr.reverse(); 警報( arr ); // 5,4,3,2,1
它也傳回反轉後的陣列arr
。
這是現實生活中的情況。我們正在編寫一個訊息傳遞應用程序,該人輸入以逗號分隔的接收者清單: John, Pete, Mary
。但對我們來說,名稱數組比單一字串舒服得多。如何獲得?
str.split(delim) 方法正是這樣做的。它透過給定的分隔符號delim
將字串拆分為數組。
在下面的範例中,我們用逗號後面跟著空格分隔:
let names = '比爾博、甘道夫、戒靈'; 讓 arr = 名稱.split(', '); for (令 arr 的名稱) { alert(`給${name}的訊息。`); // 給比爾博(和其他名字)的訊息 }
split
方法有一個可選的第二個數字參數 - 陣列長度的限制。如果提供了,則忽略額外的元素。但在實踐中很少使用它:
let arr = '比爾博、甘道夫、戒靈、薩魯曼'.split(', ', 2); 警報(arr); // 比爾博、甘道夫
分成字母
呼叫帶有空s
的split(s)
會將字串拆分為字母數組:
讓str =“測試”; 警報( str.split('') ); // 測試
呼叫 arr.join(glue) 與split
執行相反的操作。它創建一串arr
項目,它們之間通過glue
連接起來。
例如:
let arr = ['比爾博', '甘道夫', '戒靈']; 令 str = arr.join(';'); // 使用 ; 將陣列黏合到字串中 警報(str); // 比爾博;甘道夫;戒靈
當我們需要迭代數組時 - 我們可以使用forEach
、 for
或for..of
。
當我們需要迭代並傳回每個元素的資料時 - 我們可以使用map
。
方法 arr.reduce 和 arr.reduceRight 也屬於該型,但稍微複雜一些。它們用於根據數組計算單一值。
語法是:
讓值 = arr.reduce(函數(累加器, 項目, 索引, 陣列) { // ... }, [最初的]);
該函數依序應用於所有數組元素,並將其結果「保留」到下一次呼叫。
論點:
accumulator
– 是前一個函數呼叫的結果,第一次等於initial
(如果提供了initial
)。
item
– 是目前陣列項目。
index
– 是它的位置。
array
——是數組。
應用函數時,前一個函數呼叫的結果將作為第一個參數傳遞給下一個函數。
因此,第一個參數本質上是儲存所有先前執行的組合結果的累加器。最後,它變成了reduce
的結果。
聽起來很複雜?
掌握這一點的最簡單方法是透過範例。
這裡我們在一行中得到一個陣列的和:
令 arr = [1, 2, 3, 4, 5]; 讓結果 = arr.reduce((sum, current) => sum + current, 0); 警報(結果); // 15
傳遞給reduce
函數只使用2 個參數,這通常就足夠了。
讓我們看看到底發生了什麼事情的細節。
第一次執行時, sum
是initial
值( reduce
的最後一個參數),等於0
, current
是第一個陣列元素,等於1
。所以函數結果是1
。
在第二次運行時, sum = 1
,我們將第二個陣列元素 ( 2
) 加到其中並返回。
在第三次運行時, sum = 3
,我們向其中添加一個元素,依此類推…
計算流程:
或以表格的形式,其中每一行代表對下一個陣列元素的函數呼叫:
sum | current | 結果 | |
---|---|---|---|
第一個電話 | 0 | 1 | 1 |
第二次通話 | 1 | 2 | 3 |
第三次通話 | 3 | 3 | 6 |
第四個電話 | 6 | 4 | 10 |
第五次通話 | 10 | 5 | 15 |
在這裡我們可以清楚地看到上一個呼叫的結果如何成為下一個呼叫的第一個參數。
我們也可以省略初始值:
令 arr = [1, 2, 3, 4, 5]; // 從reduce中刪除初始值(沒有0) 讓結果 = arr.reduce((sum, current) => sum + current); 警報(結果); // 15
結果是一樣的。這是因為如果沒有初始值,那麼reduce
就會將數組的第一個元素作為初始值,並從第二個元素開始迭代。
計算表與上面相同,減去第一行。
但這樣的使用需要格外小心。如果數組為空,則沒有初始值的reduce
呼叫會出錯。
這是一個例子:
令 arr = []; // 錯誤:減少沒有初始值的空數組 // 如果初始值存在,reduce 會為空 arr 傳回它。 arr.reduce((sum, current) => sum + current);
因此建議始終指定初始值。
方法 arr.reduceRight 執行相同的操作,但從右到左。
數組不形成單獨的語言類型。它們是基於物件的。
所以typeof
無助於區分普通物件和陣列:
警報(類型{}); // 目的 警報(類型[]); // 物件(相同)
…但是陣列的使用非常頻繁,因此有一個特殊的方法:Array.isArray(value)。如果value
是數組,則傳回true
,否則傳回false
。
警報(Array.isArray({})); // 錯誤的 警報(Array.isArray([])); // 真的
幾乎所有呼叫函數的陣列方法(例如find
、 filter
、 map
)(除了sort
)都接受可選的附加參數thisArg
。
這個參數在上面的部分中沒有解釋,因為它很少被使用。但為了完整起見,我們必須覆蓋它。
以下是這些方法的完整語法:
arr.find(func, thisArg); arr.filter(func, thisArg); arr.map(func, thisArg); // ... // thisArg 是可選的最後一個參數
對於func
thisArg
參數的值變成this
。
例如,這裡我們使用army
物件的一種方法作為過濾器, thisArg
傳遞上下文:
讓軍隊= { 最低年齡:18歲, 最大年齡:27, 可以加入(用戶){ return user.age >= this.minAge && user.age < this.maxAge; } }; 讓使用者= [ {年齡:16}, {年齡:20}, {年齡:23}, {年齡:30} ]; // 尋找用戶,其中 Army.canJoin 傳回 true 讓士兵 = users.filter(army.canJoin, Army); 警報(士兵.長度); // 2 警報(士兵[0].年齡); // 20 警報(士兵[1].年齡); // 23
如果在上面的範例中我們使用了users.filter(army.canJoin)
,那麼army.canJoin
將作為獨立函數調用,並且this=undefined
,從而導致即時錯誤。
users.filter(army.canJoin, army)
的呼叫可以替換為users.filter(user => army.canJoin(user))
,其作用相同。後者使用得更頻繁,因為它對大多數人來說更容易理解。
數組方法的備忘單:
新增/刪除元素:
push(...items)
– 將項目加入到末尾,
pop()
– 從最後提取一個項目,
shift()
– 從開頭擷取一個項目,
unshift(...items)
– 將項目加入開頭。
splice(pos, deleteCount, ...items)
– 在索引pos
處刪除deleteCount
元素並插入items
。
slice(start, end)
– 建立一個新數組,將索引start
到end
(不包含)的元素複製到其中。
concat(...items)
– 傳回一個新陣列:複製目前陣列的所有成員並在其中新增items
。如果任何items
是數組,則取得其元素。
若要在元素之間搜尋:
indexOf/lastIndexOf(item, pos)
– 尋找從位置pos
開始的item
,並返回索引,如果找不到則傳回-1
。
includes(value)
– 如果陣列有value
則回傳true
,否則傳回false
。
find/filter(func)
– 透過函數過濾元素,傳回第一個/所有使其傳回true
值。
findIndex
與find
類似,但傳回索引而不是值。
迭代元素:
forEach(func)
– 為每個元素呼叫func
,不傳回任何內容。
要轉換數組:
map(func)
– 根據為每個元素呼叫func
的結果建立一個新陣列。
sort(func)
– 對陣列進行就地排序,然後傳回它。
reverse()
– 就地反轉數組,然後傳回它。
split/join
– 將字串轉換為陣列並傳回。
reduce/reduceRight(func, initial)
– 透過為每個元素呼叫func
並在呼叫之間傳遞中間結果來計算數組上的單一值。
另外:
Array.isArray(value)
檢查value
是否為數組,如果是則傳回true
,否則傳回false
。
請注意, sort
、 reverse
和splice
方法會修改陣列本身。
這些方法是最常用的方法,它們涵蓋了 99% 的用例。但還有其他一些:
arr.some(fn)/arr.every(fn) 檢查陣列。
與map
類似,對陣列的每個元素呼叫函數fn
。如果任何/所有結果為true
,則傳回true
,否則false
。
這些方法的行為有點像||
和&&
運算子:如果fn
傳回 true 值, arr.some()
立即傳回true
並停止迭代其餘項;如果fn
傳回一個假值, arr.every()
立即傳回false
並停止迭代其餘項。
我們可以使用every
來比較陣列:
函數 arraysEqual(arr1, arr2) { return arr1.length === arr2.length && arr1.every((value, index) => value === arr2[index]); } 警報( arraysEqual([1, 2], [1, 2])); // 真的
arr.fill(value, start, end) – 使用從索引start
到end
重複value
填入陣列。
arr.copyWithin(target, start, end) – 將其元素從位置start
到位置end
複製到其自身中,位於target
位置(覆蓋現有)。
arr.flat(深度)/arr.flatMap(fn) 從多維數組建立一個新的平面數組。
有關完整列表,請參閱手冊。
乍一看,方法很多,很難記住。但實際上,這要容易得多。
查看備忘單只是為了了解它們。然後解決本章的任務來練習,這樣你就有了陣列方法的經驗。
之後,每當您需要對數組執行某些操作,並且您不知道如何操作時,請來這裡,查看備忘單並找到正確的方法。範例將幫助您正確編寫它。很快您就會自動記住這些方法,而無需您進行特定的努力。
重要性:5
編寫函數camelize(str)
,將破折號分隔的單字(如“my-short-string”)改為駝峰式“myShortString”。
即:刪除所有破折號,破折號後的每個字都變成大寫。
範例:
駱駝化(“背景顏色”)=='背景顏色'; Camelize("列表樣式圖片") == 'listStyleImage'; Camelize("-webkit-transition") == 'WebkitTransition';
PS 提示:使用split
將字串拆分為數組,轉換它並join
回來。
打開一個包含測試的沙箱。
函數駱駝化(str){ 返回字串 .split('-') // 將 'my-long-word' 拆分為陣列 ['my', 'long', 'word'] 。 // 將除第一個之外的所有數組項的首字母大寫 // 將 ['my', 'long', 'word'] 轉換為 ['my', 'Long', 'Word'] (單字,索引)=> 索引 == 0 ?字 : 字[0].toUpperCase() + 字.slice(1) ) 。 // 將 ['my', 'Long', 'Word'] 連接到 'myLongWord' }
在沙箱中開啟包含測試的解決方案。
重要性:4
寫一個函數filterRange(arr, a, b)
取得數組arr
,找出值大於或等於a
且小於或等於b
的元素,並將結果傳回為陣列。
此函數不應修改數組。它應該會傳回新數組。
例如:
令 arr = [5, 3, 8, 1]; 讓過濾 = filterRange(arr, 1, 4); 警報(過濾); // 3,1(匹配值) 警報( arr ); // 5,3,8,1(未修改)
打開一個包含測試的沙箱。
函數filterRange(arr, a, b) { // 在表達式兩邊加上括號以提高可讀性 return arr.filter(item => (a <= item && item <= b)); } 令 arr = [5, 3, 8, 1]; 讓過濾 = filterRange(arr, 1, 4); 警報(過濾); // 3,1(匹配值) 警報( arr ); // 5,3,8,1(未修改)
在沙箱中開啟包含測試的解決方案。
重要性:4
寫一個函數filterRangeInPlace(arr, a, b)
取得數組arr
並從中刪除除a
和b
之間的值之外的所有值。測試為: a ≤ arr[i] ≤ b
。
該函數應該只修改數組。它不應該返回任何東西。
例如:
令 arr = [5, 3, 8, 1]; filterRangeInPlace(arr, 1, 4); // 刪除 1 到 4 以外的數字 警報( arr ); // [3, 1]
打開一個包含測試的沙箱。
函數filterRangeInPlace(arr, a, b) { for (令 i = 0; i < arr.length; i++) { 令 val = arr[i]; // 若超出區間則刪除 if (val < a || val > b) { arr.splice(i, 1); 我 - ; } } } 令 arr = [5, 3, 8, 1]; filterRangeInPlace(arr, 1, 4); // 刪除 1 到 4 以外的數字 警報( arr ); // [3, 1]
在沙箱中開啟包含測試的解決方案。
重要性:4
令 arr = [5, 2, 1, -10, 8]; // ...您的程式碼以降序對其進行排序 警報( arr ); // 8, 5, 2, 1, -10
令 arr = [5, 2, 1, -10, 8]; arr.sort((a, b) => b - a); 警報( arr );
重要性:5
我們有一個字串陣列arr
。我們想要一份它的排序副本,但保持arr
不變。
建立一個傳回此類副本的函數copySorted(arr)
。
讓 arr = ["HTML", "JavaScript", "CSS"]; 讓排序 = copySorted(arr); 警報(已排序); // CSS、HTML、JavaScript 警報( arr ); // HTML、JavaScript、CSS(無變化)
我們可以使用slice()
製作副本並對其運行排序:
函數複製排序(arr){ 返回 arr.slice().sort(); } 讓 arr = ["HTML", "JavaScript", "CSS"]; 讓排序 = copySorted(arr); 警報(已排序); 警報( arr );
重要性:5
建立一個建構函式Calculator
來建立「可擴充」計算器物件。
該任務由兩部分組成。
首先,實作方法calculate(str)
,它採用「NUMBER運算子NUMBER」(空格分隔)格式的字串(如"1 + 2"
並傳回結果。應該理解加+
和減-
。
使用範例:
讓 calc = 新計算器; 警報( calc.calculate("3 + 7") ); // 10
然後加入方法addMethod(name, func)
來教導計算器新的操作。它採用運算子name
和實現它的雙參數函數func(a,b)
。
例如,我們加入乘法*
、除法/
和冪**
:
讓 powerCalc = 新計算器; powerCalc.addMethod("*", (a, b) => a * b); powerCalc.addMethod("/", (a, b) => a / b); powerCalc.addMethod("**", (a, b) => a ** b); 讓結果 = powerCalc.calculate("2 ** 3"); 警報(結果); // 8
此任務中沒有括號或複雜的表達式。
數字和運算子只用一個空格分隔。
如果您想添加它,可能會有錯誤處理。
打開一個包含測試的沙箱。
請注意方法是如何儲存的。它們只是添加到this.methods
屬性中。
所有測試和數值轉換都在calculate
方法中完成。將來它可能會擴展以支援更複雜的表達式。
函數計算器() { 這個.方法 = { 「-」:(a,b)=> a - b, 「+」:(a,b)=> a + b }; this.calculate = 函數(str) { 令 split = str.split(' '), a = +分割[0], op = 分割[1], b = +分割[2]; if (!this.methods[op] || isNaN(a) || isNaN(b)) { 返回 NaN; } 返回 this.methods[op](a, b); }; this.addMethod = 函數(name, func) { this.methods[名稱] = func; }; }
在沙箱中開啟包含測試的解決方案。
重要性:5
您有一組user
對象,每個對像都有user.name
。編寫將其轉換為名稱數組的程式碼。
例如:
設約翰 = { 姓名:“約翰”,年齡:25 }; 設皮特 = { 姓名:“皮特”,年齡:30 }; 設瑪麗 = { 姓名:“瑪麗”,年齡:28 }; 讓使用者= [約翰,皮特,瑪麗]; 讓名稱= /* ...你的程式碼*/ 警報(名稱); // 約翰、皮特、瑪麗
設約翰 = { 姓名:“約翰”,年齡:25 }; 設皮特 = { 姓名:“皮特”,年齡:30 }; 設瑪麗 = { 姓名:“瑪麗”,年齡:28 }; 讓使用者= [約翰,皮特,瑪麗]; 讓名稱 = users.map(item => item.name); 警報(名稱); // 約翰、皮特、瑪麗
重要性:5
您有一組user
對象,每個對像都有name
、 surname
和id
。
編寫程式碼以從中建立另一個數組,其中包含具有id
和fullName
對象,其中fullName
是根據name
和surname
產生的。
例如:
讓 john = { 名字:“約翰”,姓氏:“史密斯”,id:1 }; 讓皮特 = { 名字:“皮特”,姓氏:“亨特”,id:2 }; 讓瑪麗 = { 名字:“瑪麗”,姓氏:“Key”,id:3 }; 讓使用者= [約翰,皮特,瑪麗]; 讓 usersMapped = /* ... 你的程式碼 ... */ /* 使用者映射 = [ { fullName: "約翰史密斯", id: 1 }, { fullName: "皮特亨特", id: 2 }, { 全名:“瑪麗鍵”,id:3 } ] */ 警報( usersMapped[0].id ) // 1 Alert( usersMapped[0].fullName ) // 約翰‧史密斯
因此,實際上您需要將一個物件數組映射到另一個物件數組。嘗試在這裡使用=>
。有一個小問題。
讓 john = { 名字:“約翰”,姓氏:“史密斯”,id:1 }; 讓皮特 = { 名字:“皮特”,姓氏:“亨特”,id:2 }; 讓瑪麗 = { 名字:“瑪麗”,姓氏:“Key”,id:3 }; 讓使用者= [約翰,皮特,瑪麗]; 令 usersMapped = users.map(用戶 => ({ 全名: `${user.name} ${user.surname}`, id: 用戶.id })); /* 使用者映射 = [ { fullName: "約翰史密斯", id: 1 }, { fullName: "皮特亨特", id: 2 }, { 全名:“瑪麗鍵”,id:3 } ] */ 警報(usersMapped[0].id); // 1 警報( usersMapped[0].fullName ); // 約翰史密斯
請注意,在箭頭函數中我們需要使用額外的括號。
我們不能這樣寫:
讓 usersMapped = users.map(user => { 全名: `${user.name} ${user.surname}`, id: 用戶.id });
我們記得,有兩個箭頭函數:沒有主體value => expr
和有主體value => {...}
。
這裡 JavaScript 會將{
視為函數體的開始,而不是物件的開始。解決方法是將它們放在“普通”括號中:
令 usersMapped = users.map(用戶 => ({ 全名: `${user.name} ${user.surname}`, id: 用戶.id }));
現在好了。
重要性:5
編寫函數sortByAge(users)
,取得具有age
屬性的物件數組,並按age
對它們進行排序。
例如:
設約翰 = { 姓名:“約翰”,年齡:25 }; 設皮特 = { 姓名:“皮特”,年齡:30 }; 設瑪麗 = { 姓名:“瑪麗”,年齡:28 }; 令 arr = [ 皮特、約翰、瑪麗 ]; 按年齡排序(arr); // 現在:[約翰、瑪麗、皮特] 警報(arr[0].name); // 約翰 警報(arr[1].name); // 瑪麗 警報(arr[2].name); // 皮特
函數 sortByAge(arr) { arr.sort((a, b) => a.age - b.age); } 設約翰 = { 姓名:“約翰”,年齡:25 }; 設皮特 = { 姓名:“皮特”,年齡:30 }; 設瑪麗 = { 姓名:“瑪麗”,年齡:28 }; 令 arr = [ 皮特、約翰、瑪麗 ]; 按年齡排序(arr); // 現在排序的是:[john, mary, pete] 警報(arr[0].name); // 約翰 警報(arr[1].name); // 瑪麗 警報(arr[2].name); // 皮特
重要性:3
編寫函數shuffle(array)
來對陣列的元素進行洗牌(隨機重新排序)。
多次執行shuffle
可能會導致元素順序不同。例如:
令 arr = [1, 2, 3]; 隨機播放(arr); // arr = [3, 2, 1] 隨機播放(arr); // arr = [2, 1, 3] 隨機播放(arr); // arr = [3, 1, 2] // ...
所有元素順序應該具有相同的機率。例如, [1,2,3]
可以重新排序為[1,2,3]
或[1,3,2]
或[3,1,2]
等,每種情況的機率相等。
簡單的解決方案可能是:
函數洗牌(陣列){ array.sort(() => Math.random() - 0.5); } 令 arr = [1, 2, 3]; 隨機播放(arr); 警報(arr);
這在某種程度上是有效的,因為Math.random() - 0.5
是一個可能是正數或負數的隨機數,因此排序函數會隨機地重新排序元素。
但由於排序函數不應該以這種方式使用,因此並非所有排列都具有相同的機率。
例如,考慮下面的程式碼。它運行shuffle
1000000 次併計算所有可能結果的出現次數:
函數洗牌(陣列){ array.sort(() => Math.random() - 0.5); } // 所有可能排列的出現次數 讓計數 = { ‘123’: 0, ‘132’: 0, ‘213’: 0, ‘231’: 0, ‘321’: 0, ‘312’:0 }; for (設 i = 0; i < 1000000; i++) { 讓數組 = [1, 2, 3]; 洗牌(陣列); 計數[array.join('')]++; } // 顯示所有可能排列的計數 for (讓鍵入計數) { 警報(`${key}: ${count[key]}`); }
範例結果(取決於 JS 引擎):
123:250706 132:124425 213:249618 231:124880 312:125148 321:125223
我們可以清楚地看到這種偏差: 123
和213
比其他數字出現的頻率要高得多。
程式碼的結果可能因 JavaScript 引擎而異,但我們已經可以看到該方法是不可靠的。
為什麼它不起作用?一般來說, sort
是一個「黑盒子」:我們將一個陣列和一個比較函數放入其中,並期望數組被排序。但由於比較的完全隨機性,黑盒子會發瘋,而它到底如何發瘋取決於引擎之間不同的具體實現。
還有其他好方法來完成這項任務。例如,有一個很棒的演算法,稱為 Fisher-Yates shuffle。這個想法是以相反的順序遍歷數組,並將每個元素與其前面的隨機元素交換:
函數洗牌(陣列){ for (令 i = array.length - 1; i > 0; i--) { 令 j = Math.floor(Math.random() * (i + 1)); // 從 0 到 i 的隨機索引 // 交換數組[i]和數組[j]的元素 // 我們使用「解構賦值」語法來實現這一點 // 您將在後面的章節中找到有關該語法的更多詳細信息 // 同樣可以寫成: // 設 t = array[i];數組[i] = 數組[j];數組[j] = t [數組[i], 數組[j]] = [數組[j], 數組[i]]; } }
我們用同樣的方法來測試:
函數洗牌(陣列){ for (令 i = array.length - 1; i > 0; i--) { 令 j = Math.floor(Math.random() * (i + 1)); [數組[i], 數組[j]] = [數組[j], 數組[i]]; } } // 所有可能排列的出現次數 讓計數 = { ‘123’: 0, ‘132’: 0, ‘213’: 0, ‘231’: 0, ‘321’: 0, ‘312’:0 }; for (設 i = 0; i < 1000000; i++) { 讓數組 = [1, 2, 3]; 洗牌(陣列); 計數[array.join('')]++; } // 顯示所有可能排列的計數 for (讓鍵入計數) { 警報(`${key}: ${count[key]}`); }
範例輸出:
123:166693 132:166647 213:166628 231:167517 312:166199 321:166316
現在看起來不錯:所有排列都以相同的機率出現。
此外,從效能角度來看,Fisher-Yates 演算法要好得多,沒有「排序」開銷。
重要性:4
編寫函數getAverageAge(users)
,取得具有age
屬性的物件陣列並傳回平均年齡。
平均值的公式為(age1 + age2 + ... + ageN) / N
。
例如:
設約翰 = { 姓名:“約翰”,年齡:25 }; 設皮特 = { 姓名:“皮特”,年齡:30 }; 設瑪麗 = { 姓名:“瑪麗”,年齡:29 }; 令 arr = [ 約翰、皮特、瑪麗 ]; 警報( getAverageAge(arr) ); // (25 + 30 + 29) / 3 = 28
函數 getAverageAge(用戶) { return users.reduce((prev, user) => prev + user.age, 0) / users.length; } 設約翰 = { 姓名:“約翰”,年齡:25 }; 設皮特 = { 姓名:“皮特”,年齡:30 }; 設瑪麗 = { 姓名:“瑪麗”,年齡:29 }; 令 arr = [ 約翰、皮特、瑪麗 ]; 警報( getAverageAge(arr) ); // 28
重要性:4
設arr
為數組。
建立一個函數unique(arr)
,該函數應傳回一個包含arr
唯一項的陣列。
例如:
函數唯一(arr){ /* 你的程式碼 */ } 讓字串= [“野兔”,“克里希納”,“野兔”,“克里希納”, “克里希納”,“克里希納”,“野兔”,“野兔”,“:-O” ]; 警報(唯一(字串)); // 野兔,克里希納,:-O
打開一個包含測試的沙箱。
讓我們來看看數組項:
對於每個項目,我們將檢查結果陣列是否已經包含該項目。
如果是這樣,則忽略,否則將添加到結果中。
函數唯一(arr){ 讓結果=[]; for (令 arr 的 str) { if (!result.includes(str)) { 結果.push(str); } } 返回結果; } 讓字串= [“野兔”,“克里希納”,“野兔”,“克里希納”, “克里希納”,“克里希納”,“野兔”,“野兔”,“:-O” ]; 警報(唯一(字串)); // 野兔,克里希納,:-O
該程式碼可以工作,但其中存在潛在的效能問題。
result.includes(str)
方法在內部遍歷數組result
並將每個元素與str
進行比較以找到匹配項。
因此,如果result
中有100
元素並且沒有一個元素與str
匹配,那麼它將遍歷整個result
並進行100
比較。如果result
很大,例如10000
,那麼就會有10000
比較。
這本身並不是問題,因為 JavaScript 引擎非常快,因此遍歷10000
陣列只需幾微秒的時間。
但是我們在for
迴圈中對arr
的每個元素進行這樣的測試。
因此,如果arr.length
為10000
我們將進行10000*10000
= 1 億次比較。就這麼多了。
所以該解決方案僅適用於小型陣列。
在「映射與集合」一章中,我們將進一步了解如何最佳化它。
在沙箱中開啟包含測試的解決方案。
重要性:4
假設我們收到了{id:..., name:..., age:... }
形式的使用者陣列。
建立一個函數groupById(arr)
該函數從中建立一個對象,以id
為鍵,以數組項為值。
例如:
讓使用者= [ {id:'約翰',姓名:“約翰·史密斯”,年齡:20}, {id:'ann',姓名:“Ann Smith”,年齡:24}, {id: 'pete', name: "Pete Peterson", 年齡: 31}, ]; 讓 usersById = groupById(users); /* // 呼叫之後我們應該有: 使用者按 ID = { 約翰:{id:'約翰',姓名:“約翰史密斯”,年齡:20}, 安:{id:'安',姓名:“安·史密斯”,年齡:24}, 皮特:{id:'皮特',姓名:“皮特·彼得森”,年齡:31}, } */
在處理伺服器資料時,這樣的功能非常方便。
在這個任務中,我們假設id
是唯一的。不能有兩個數組項具有相同的id
。
請在解決方案中使用 array .reduce
方法。
打開一個包含測試的沙箱。
函數 groupById(數組) { return array.reduce((obj, value) => { obj[值.id] = 值; 返回對象; }, {}) }
在沙箱中開啟包含測試的解決方案。