如果學程式也能像這些甜甜圈一樣美好就好了......
這邊是我初學 JavaScript 陣列時為了怕金魚腦的筆記區,主要的內容會以 Alpha Camp 的教材為主,輔以一些我自己在網路上看到的實用資訊。由於隨時可能學到新知識,這篇筆記將不定期更新。
由於先前我有看過 CS50 的 Data Structure 影片,因此知道陣列是在記憶體當中以線性且連續的方式儲存資料。不過 JavaScript 在初版發布時沒有包含陣列這種資料結構,只有物件 (Object) 可供使用。然而後續大家還是需要有次序的資料結構,所以 JavaScript 硬是弄了個類陣列的物件。透過 typeof
我們便能發現 JavaScript 陣列雖然披著陣列的皮,實質上就是個不折不扣的物件。
有種被欺騙感情的感覺......
陣列的最大特點就是能用索引存取資料,我們可以把索引想像成編號,每個陣列都是從 0 開始索引的。只要在陣列名稱後面加上中括號,並於中括號內帶入資料的索引,程式碼就會回傳對應的資料值囉~
JavaScript 陣列提供了眾多操作方法,其中 array.length
能快速回傳陣列的長度給我們,回想起當初 CS50 用 C 語言人工算出陣列長度,才發現 JavaScript 果然有方便的地方。
既然能快速知道陣列的長度,我們可以透過 array.length
找出陣列的最後一筆資料。
❗由於陣列索引從 0 開始,所以中括號要帶入的是 array.length - 1
。
找出陣列最後一筆資料
看起來不錯,但第三行程式碼重複呼叫了 numbers 這個變數。我們可以參考 array.at
更簡潔的寫法,小括號中的 - 1 代表從陣列尾端往回推的第一筆資料。
用更簡潔的寫法取得陣列最後一筆資料~
畢竟我們也長大了,有時候難免全都要陣列的所有資料值,這時候可以搭配迴圈去遍歷陣列每個索引,然後回傳該索引對應的資料值。由於陣列索引是從 0 開始的,加上我們能取得陣列的長度,因此 for 迴圈能帶來不錯的效果。
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
for (let i = 0; i < numbers.length; i++) {
console.log(numbers[i]);
}
如果不需要取得資料的索引,for ... of 迴圈提供了更加簡潔的寫法。若要採用這種寫法陣列的命名最好用英文複數比較清楚。
const numbers = [1,2,3,4,5,6,7,8,9,10];
for (let number of numbers) {
console.log(number);
}
除了 array.length
之外,JavaScript 還提供了其他好用的操作方法,讓我們自由新增、刪除陣列資料。不過在進入到操作方式的介紹之前,我們需要先認識兩種資料結構:Queue 以及 Stack。JavaScript 陣列支援這兩種方式。
ADT 是資料結構的一種類別,這種類別的資料架構僅定義了資料類型的行為 (behaviour),而沒有定義實現的方式 (implementation),像是 Array、List、Map、Queue、Stack、Table、Tree、Vector 都是 ADT。我們可以把 ADT 想像成書這個概念,書在人類定義中是要被用來記錄、閱讀的 (行為),然而根據不同的書寫目的,世界上有成千上萬種書,像是小說、電話簿、工具書、教科書等等……(實現的方式)。
Queue
是 ADT (abstract data type) 的一種,遵循 FIFO (fitst in first out) 的先進先出原則。什麼是先進先出呢?我們可以把 Queue
想像成餐廳的排隊隊伍。餐廳一定是讓先來排隊的客人先進去用餐,越晚來的客人則越晚能進去,這就是 FIFO 的道理。
如果我們想把東西加入進去,這個動作稱為 enqueue
;相對來說,若要把東西從 Queue
取出來,該動作被稱為 dequeue
。
圖片來自 https://javascript.info/
相較於 Queue
,Stack
遵循的是 LIFO (last in first out) 的後進先出原則。我們可以想像成是大賣場的推車,通常大家都是先拿最外面的推車。如果我們想把東西加入到 Stack 最上面,這個動作被稱為 push;而把東西從 Stack 最上面抽走的動作則稱為 pop。
圖片來自 https://javascript.info/
JavaScript 陣列提供了 push、pop、shift 這三種操作方法,除此之外,若想要將資料加入到陣列最前面,還有 unshift 可以使用。unshift 的命名也許就是比較 shift 而來的 (?)
圖片來自 Alpha Camp
其中 pop 和 shift 的回傳值為被移除的資料值,而 push 和 unshift 則是回傳陣列的新長度,兩方的差別需要特別留意。
前面所提到的操作方法都是在陣列頭尾新增或移除資料,如果是想從中間著手的話,可以透過 array.splice
實現。以下是 splice 在劍橋辭典的解釋:
to join two pieces of rope, film, etc. together at their ends in order to form one long piece
splice
的語法比較複雜,有三個參數:
若是要插入資料,那三個參數都要輸入;倘若只是要移除資料,僅需要輸入前兩項參數即可。無論是插入或移除資料,splice
的回傳值都是變更完成後的陣列。
splice
插入資料假設今天我們希望在把 99 插入到陣列索引 2,由於沒有要移除任何資料,所以 splice
第二個參數輸入 0 即可。
const nums = [0, 1, 2, 3, 4];
nums.splice(2, 0, 99);
console.log(nums); // [0, 1, 99, 2, 3, 4]
splice
移除資料假設我們希望從陣列索引 2 開始移除兩筆資料,程式碼會長這樣:
const nums = [0, 1, 2, 3, 4];
const deletedNumbers = nums.splice(2, 2); // 移除索引二和索引三的資料
console.log(deletedNumbers); // [2, 3]
若我們希望從陣列擷取部分資料,然後存放到另一個新陣列的話,不妨參考 array.slice
。splice
和 slice
的概念和語法都不太一樣,要特別注意!
slice
有兩個參數:
所以今天如果我們希望擷取 fruits 陣列的第二與第三索引的資料,寫法如下:
const fruits = ["Banana", "Orange", "Lemon", "Apple", "Mango"];
const citrus = fruits.slice(1, 3);
// fruits contains ['Banana', 'Orange', 'Lemon', 'Apple', 'Mango']
// citrus contains ['Orange','Lemon']
由此可見,slice 並不會對原先的陣列造成影響,只是把擷取下來的資料複製到新陣列裡面而已。
JavaScript 是一個相對寬鬆的語言,所以容易出現資料型別強制轉換 (coercion) 的狀況。比方說,若我們單純用 +
妄想合併陣列,回傳的值將被強制轉換成字串。所以正確的做法應該是使用 array.concat
來合併兩個或是多個陣列 (以半形逗號隔開)。
const num1 = [1, 2, 3];
const num2 = [4, 5, 6];
const num3 = [7, 8, 9];
const numbers = num1.concat(num2, num3);
console.log(numbers);
// results in [1, 2, 3, 4, 5, 6, 7, 8, 9]
這邊請注意 concat
回傳的是一個合併後的新陣列,所以不會對原先的陣列造成影響。