0%

陣列屬性 length 的真面貌

Day 05

一列隊伍有長度,也有各式各樣不同的物種,這才有趣嘛!

length 這個屬性應該算是我們在學 Javascript 的初期,最早開始用的一個屬性,我們常常來用它來當作是跑字串或陣列的迴圈條件,但是,作為 Array 的唯一屬性 length 真的有這麼單純嗎?

我們可以從 Chrome 的開發工具的 console 裡,輸入window.Array.prototype 可以找到它。並試著了解他多一點。

先看一下 NDM 對它的定義:
作為 Array 類型的實例物件的 length 屬性,設置或回傳該陣列中的元素數。 該值是一個無符號的 32 位整數,其數值總是大於陣列中的最高索引。

嗯,解釋得很清楚,但好像單薄了點。果然,查找了「JavaScript 大全」的解釋,把 length 解釋得較為詳細,且還講到了密集陣列(dense)和稀疏陣列(sparse),其中更強調了無論是以上哪種型態的陣列,length 的長度不會等於或小於陣列的最大索引值,也就是說 length 會比陣列最大的索引值多 1。但有個例外,就是空陣列。 廢話

這個其實不難理解,在許多程式語言裡,陣列的索引值都是從 0 開始算起,當然 JavaScript 也不例外。JavaScript 的 length 是從 1 開始,從下面這張圖片可以看出為什麼長度總比索引值大的原因。

怎麼使用 length ?

別看 length 這個屬性,好像只能知道陣列的長度,但是如果知道怎麼活用它,會發現他的功用還不少。接下來介紹是幾種 length 的用法:

求陣列的長度

  • 只要在我們想知道的陣列變數,以方法的方式加上.length。回傳回來的就是這個陣列的長度。
  • 不管是密集或稀疏陣列,長度(length)永遠大於索引值(index)。
1
2
let arr = ['hi', 'ho', 'woops', 'ciao'];
console.log(arr.length); // 4
  • 如果重新指定長度,.length,原本有的會被刪除,沒有的會以空的補上而成為稀疏陣列。
1
2
3
4
5
6
arr = [1, 2, 3, 4, 5];
arr; // [1, 2, 3, 4, 5]
arr.length = 10;
arr; // [1, 2, 3, empty × 7]
arr.length = 3;
arr; // [1, 2, 3]

把陣列清空 第一種

這的確是個快速清空的方法,但是陣列在 JavaScript 裡,骨子裡是物件,也就是在記憶體裡,當我們宣告它時,也同時記錄了它在記憶體的位置(call by reference),而如果我們複製了這個陣列,然後用 length 這個屬性去操控它,嗯,恩湯喔,很危險,同時會指向來源陣列,所以也會修改來源陣列。
這個 codepen 有更詳細的範例說明,記得只觀察比對 JS 和 console(在左下角)的部分。

1
2
3
let say = ['hi', 'ho', 'woops', 'ciao'];
say.length = 0;
console.log(say); // []

把陣列清空 第二種

和第一種一樣,都會更動到來源陣列,請慎用。

1
2
3
let say = ['hi', 'ho', 'woops', 'ciao'];
say.length = [];
console.log(say); // []

把陣列最後一個元素抓出來

除了可以用 JavaScript 的內建函式 pop();去提取陣列的最後一個元素外,還可以用下面這種方法。找到陣列中最後一個值, 把 length 的長度減掉 1 再把它對應到索引值,就是陣列的最後一個了。

1
2
3
let say = ['hi', 'ho', 'woops', 'ciao'];
let lastSay = say[say.length - 1];
console.log(lastSay); // ciao

刪除陣列的最後一個元素

直接把原本的陣列長度,用指派的方式減掉最後一個陣列元素。

1
2
3
let say = ['hi', 'ho', 'woops', 'ciao'];
say.length = say.length - 1;
console.log(say); // ["hi", "ho", "woops"]

從陣列尾部增加一個元素

如果要在陣列尾部增加一個元素,除了可以用 JavaScript 的內建函式 push();去增加外,還可以用這種方法增加,真的很奇妙!

1
2
3
let say = ['hi', 'ho', 'woops', 'ciao'];
say[say.length] = 'haha';
console.log(say); // ["hi", "ho", "woops", "ciao", "haha"]

當作是迴圈的停止條件

這是我們常用的方法,把迴圈限制在陣列的長度範圍內,跑完陣列的長度自動停止,真的很方便。以下的範例就是計算陣列裡面元素的總和。

1
2
3
4
5
6
let nbr = [2, 4, 6, 8];
let sum = 0;
for (let i = 0; i < nbr.length; i++) {
sum = sum + nbr[i];
}
console.log(sum); // 20

服用 length 注意事項

雖然 length 可以這樣用,但並不表示就是好的方法,因為 length 是陣列的屬性,陣列在 JavaScript 是屬於非原始型別(Non-Primitive)也就是物件型別(Object type)與傳址(Pass by referance), 別緊張,這幾個名詞都是指同一件事情。 所以不可不慎用,一改動是會同時更動複製的原陣列的。

小總結

不知道這篇 length 的介紹,有沒有讓大家對它有另一種看法,程式的變化實在是太精采,但知道活用或許比知道原本的功能更重要吧?

透過鐵人賽寫文來查找資料,實在是個不錯的方法,人家說「當爸爸之後才學著怎麼當爸爸」,本人雖然無法當爸爸,但對這句話的涵義也頗有感觸,期望在這樣查找與發現的狀態下,多練些腦肌肉也是好的。

如有需要改進的地方,拜託懇求請告知,我會盡量快速度修改,感謝您~


==要補充==

陣列裡的 length 屬性

對稀疏陣列來說,如果.length 屬性比元素的數目還大,length 會比陣列索引還大,也就是說,陣列永遠不會有元素的索引大於或等於陣列的.length長度。為了要為持這個不變式(invariant),陣列會有兩個特殊行為: 1.如果我們指定一個值給索引 i,而 i 大於或等於陣列目前的 length 長度,那麼 length 的屬性就會被設為 i+1。 2.如果我們將 length 屬性設為小於目前陣列值的非負整數 n,那麼,任何索引值大於或等於 n 的陣列元素都會因此從陣列中被刪除。
==記住:.length長度永遠會比index[]大 1,因為index從 0 開始計算。==

1
2
3
4
a = [1, 2, 3, 4, 5]; // 創建一個有五個元素的陣列
a.length = 3; // a 現在變成 [1,2,3]
a.length = 0; // 將長度指定成 0 等於刪除所有元素,所以現在陣列變成 []
a.length = 5; // 將長度指定成 5,但是沒有元素,所以會如 new Array(5) a 變成 [empty × 5]

我們也可將長度射設成比現在更大的長度,但並不會因此新增任何的陣列元素,而是會在陣列尾部創建一塊稀疏的區域。

1
a.length = 15; // a 變成 [empty × 15]

可以把 length 設為唯讀,鎖死陣列長度嗎?可以。在 ECMAScript,我們可以使用 Object.defineProperty()把陣列 length 設為唯讀,這樣不只無法更改長度,也無法增加長度與刪除。

1
2
3
a = [1, 2, 3, 4, 5]; //
Object.defineProperty(a, 'length', { writable: false }); // 設為唯讀
a.length = 0; // a 不變。 a 仍然是 [1, 2, 3, 4, 5]

節錄「JavaScript 大全」的說明:

  • 陣列(Array)是動態的(dynamic):陣列的長度可以根據需求變長或變短,沒有必要在創建陣列時,宣告陣列的長度,或是在長短改變時重新配置(reallocate)。
  • 每個陣列都有一個控制或偵測的屬性:length 特性,對非稀疏陣列(nonsparse)來說,length 代表陣列中所包含的元素數量
    對稀疏陣列(sparse)而言,length 比陣列中所有元素的索引值還要大,原因不難理解,因為索引值是從 0 開始計算,而 length 是從 1 開始。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 陣列常値
const arr1 = [1, 2, 3]; // 數字陣列
const arr2 = ['one', 2, 'three']; // 非同型陣列
const arr3 = [
[1, 2, 3],
['one', 2, 'three']
]; // 陣列裡有陣列
const arr4 = [
{ name: 'Tsuifei', type: 'object', luckyNumber: [5, 7, 13, 42] }, // 非同型陣列
[
{ name: 'Philippe', type: 'object' },
{ name: 'Ayda', type: 'object' }
],
1,
'three',
function() {
return 'array can contain function too';
}
];

// 陣列長度
arr1.length; // 1
arr4.length; // 5
arr4[1].length; // 2

// 增加陣列長度
arr1[4] = 5;
arr1; // [1,2,3,undefined,5] 直接指定,中間無值所以undefined
arr1.length; // 5

length 的妙用
數組的 length 不是只讀的。所以我們可以通過 length 完成
【1】從數組末尾移除項
【2】在數組末尾添加新項
【3】清空數組

/*\
_ 從數組末尾移除項
_ **/
var colors = [“red”, “blue”, “green”]; // 創建一個包含 3 個字符串的數組
colors.length = 2;
alert(colors[2]); // undefined

/***
 * 在數組末尾添加新項
 * **/
var colors = ["red", "blue", "green"]; // 創建一個包含3個字符串的數組
colors[colors.length] = "black"; // (在位置3)添加一種顏色
colors[colors.length] = "brown"; // (在位置4)再添加一種顏色

/***
 * 清空數組
 * **/
var colors = ["red", "blue", "green"]; // 創建一個包含3個字符串的數組
colors.length = 0;
console.log(colors); // []

圖篇來源:
https://unsplash.com/s/photos/seat

真清除 尾巴斷了

length 除了可以拿來查看陣列的長短,還可以拿來刪除陣列尾部的元素。只要直接指定想要的長度,截至想保留的長度,就可以輕鬆刪掉尾部的元素了。

1
2
3
4
5
6
let a = [1, 2, 3, 4, 5, 6, 7, 8, 9];

a.length; // 9
a.length = 5;
a; // [1, 2, 3, 4, 5]
a.length; // 5