數字為特性的陣列


陣列是記憶體中線性的連續資料,在 JavaScript 中,並沒有實際的陣列,而是以物件來模擬出相似的操作外觀。如果你要在 JavaScript 中建立所謂的陣列(以下還是先簡稱陣列),可以使用 Array 建構出實例。例如:

> var array1 = new Array();
undefined
> array1.length;
0
> var array2 = new Array(10);
undefined
> array2.length;
10
> var array3 = new Array(10, 20, 30);
undefined
> array3.length;
3
> 

上面示範了三種 Array 實例建構的方式,第一種方式建構出沒有任何元素的陣列,第二種方式建構出長度為 10 的陣列,每個索引位置(0 到 9)都是 undefined,第三種方式則建構出內含三個元素的陣列,索引 0 開始分別是 10、20、30。

實際上,很少人會直接使用 Array 建構陣列,而會使用陣列實字(Array literal)。例如:

> var array1 = [];
undefined
> array1.length;
0
> var array2 = [];
undefined
> array2.length = 10;
10
> array2.length;
10
> var array3 = [10, 20, 30];
undefined
> array3.length;
3
> 

你並沒有看錯,在 JavaScript 中,Array 建立的實例,length 特性是可讀可寫的。例如:

function print(array) {
    for(var i = 0; i < array.length; i++) {
         console.log(array[i]);
    }
}

var array = [1, 2, 3];
print(array);  // 1 2 3
console.log('........');

array.length = 5;
print(array);  // 1 2 3 undefined undefined
console.log('........');

array.length = 2;
print(array);  // 1 2
console.log('........');

array.length = 3;
print(array);  // 1 2 undefined

在上面的例子中,陣列原本的長度為 3,後來設定 length 為 5,索引 3 與 4 的部份會是空項目(empty item),指定索引取值的話會是 undefined,將 length 設為 2 時,原本索引 2 的資料就沒了,就算設回 3 也找不回來了。

雖然陣列中的空項目取值的話會是 undefined,不過,當陣列中有空項目時,運算或函式會有不一致的行為:

> 0 in [1,, 3];
true
> 0 in [,,];
false
> 0 in [undefined, undefined, undefined];
true
> [undefined, undefined].forEach(function(elem) {
...     console.log(undefined);
... });
undefined
undefined
undefined
> [,,].forEach(function(elem) {
...     console.log(undefined);
... });
undefined
>

從上面的例子來看,陣列中的空項目與 undefined 並不同,可以的話,應避免建立有空項目的陣列。

陣列是記憶體中線性的連續資料,在 JavaScript 中,並沒有實際的陣列,而是以物件來模擬出相似的操作外觀,事實上,用 Array 建構出的物件,索引其實就是以代表數字的特性罷了,事實上索引指定方式也可以用字串,只要它代表數字:

> var array = [1, 2, 3];
undefined
> array['0'];
1
> array['1'];
2
> array['2'];
3
> for(var i in array) {
...     console.log(i);
... }
0
1
2
undefined
> delete array[1];
true
> array;
[ 1, <1 empty item>, 3 ]
> 

也因此,因為索引其實就是陣列物件上的特性,你也可以用 delete 刪除陣列中的元素,也因此,你可以輕易地使用普通物件,來模擬出陣列的操作外觀:

function print(array) {
    for(var i = 0; i < array.length; i++) {
         console.log(array[i]);
    }
}

// 顯示 100 200 300
print({  
    '0' : 100,
    '1' : 200,
    '2' : 300,
    length : 3
});

在 JavaScript 中如上模擬出所謂的陣列,是非常普遍的應用。那麼,建構 Array 實例,或者說使用陣列實字建構陣列的好處是什麼?當然是為了擁有 Array 已定義的行為,例如自動依元素內容來調整 length

> var array = [];
undefined
> array.length;
0
> array[0] = 100;
100
> array.length;
1
> array[10] = 900;
900
> array.length;
11
> 

陣列的長度隨時可以增加或減少,指定索引元素時也不一定要連續指定,例如上例中,直接指定了索引 10 為 900,其它未指定的2到9索引處,全都是 undefined(就像沒有 2 到 9 的特性名稱罷了),在 JavaScript 中,也沒有所謂陣列超出索引的問題,例如上例若指定 array[10000],充其量就是傳回 undefined

直接使用 Array 實例來進行陣列操作,還可以獲得 Array 上已定義的方法。例如排序與迭代:

var names = ["Justin", "Monica", "Irene"];

names.sort(function(n1, n2) {
         return n1.length - n2.length;
      })
      .forEach(function(elem) { // Irene Justin Monica
          console.log(elem);
      });

forEach 是 ECMAScript 5 規範中為 Array 新增的方法,可以指定函式,實際上,只要是類陣列的物件,也可以使用 forEach。例如:

var obj = {  
   '0' : 100,
   '1' : 200,
   '2' : 300,
   length : 3
};

obj.forEach = Array.prototype.forEach;

obj.forEach(function(elem, index, arr) {
    console.log(elem);
});

obj.forEach(console.log);

上面範例的執行結果是:

100
200
300
100 0 { '0': 100,
  '1': 200,
  '2': 300,
  length: 3,
  forEach: [Function: forEach] }
200 1 { '0': 100,
  '1': 200,
  '2': 300,
  length: 3,
  forEach: [Function: forEach] }
300 2 { '0': 100,
  '1': 200,
  '2': 300,
  length: 3,
  forEach: [Function: forEach] }

陣列中每個元素會作為引數傳入函式,這是 JavaScript 風格的走訪陣列方式,ECMAScript 5 規範的 Array 中,有很多這類接受函式的方法,像是 everysome,可以測試陣列元素是否全部符合,以及某個符合條件:

function isLength5(value, index, array) {
    return value.length === 5;
}

function lengthLessThan6(value, index, array) {
    return value.length < 6;
}

var names = ["Justin", "Monica", "Irene"];

console.log(names.every(isLength5));         // false
console.log(names.some(lengthLessThan6));    // true

處理一串資料時常用的 filtermapreduce 方法當然也有:

var names = ["Justin", "Monica", "Irene"];

var sum = names.filter(function(elem) {
                    return elem.length > 5;
               })
               .map(function(elem) {
                   return elem.length;
               })
               .reduce(function(accum, elem) {
                   return accum + elem;
               }, 0);

console.log(sum);  // 12

reduce 是從陣列索引 0 迭代至尾端,相對地,有個 reduceRight 是從陣列尾端往前迭代至索引 0。除此之外,ECMAScript 5 還新增了 indexOflastIndexOf 方法,更多 Array 上可用的方法,可以參考 Array - JavaScript | MDN