前面的話
除了物件之外,陣列Array型別可能是javascript中最常用的型別了。而且,javascript中的陣列與其他多數語言中的陣列有著相當大的區別。本文將介紹javascript中的陣列Array型別
建立陣列
有兩種建立陣列的方法:使用字面量語法和使用Array()建構函式
【字面量】
使用陣列字面量是建立陣列最簡單的方法,在方括號中將陣列元素用逗號隔開即可
var empty = []; //沒有元素的陣列 var primes = [2,3,5,7,11]; //有5個數值的陣列
雖然javascript陣列與其他語言中的陣列都是資料的有序列表,但與其他語言不同的是,javascript陣列的每一項可以儲存任何型別的資料
var misc = [1.1,true, "a"]; //3個不同型別的元素
陣列字面量中的值不一定要是常量,它們可以是任意的表示式
var base = 1024; var table = [base,base+1,base+2,base+3];
它可以包含物件字面量或其他陣列字面量
var b = [ [1,{x:1,y:2}],[2,{x:3,y:4}] ];
如果陣列的元素還是陣列,就形成了多維陣列
var a = [[1, 2], [3, 4]];
[注意]使用數字字面量表示法時,不會呼叫Array建構函式
【建構函式】
有三種方式呼叫建構函式
【1】沒有引數,建立一個空陣列
//該方法建立一個沒有任何元素的空陣列,等同於陣列直接量[] var a = new Array();
【2】有一個數值引數,該引數用於指定陣列的長度
var a = new Array(10); console.log(a);//[] console.log(a[0],a.length);//undefined 10
[注意]若存在一個其他型別的引數,則會建立包含那個值的只有一項的陣列
var a = new Array('10'); console.log(a);//['10'] console.log(a[0],a.length);//10 1
【3】有多個引數時,參數列示為陣列的具體元素
var a = new Array(1,2,3); console.log(a);//[1,2,3] console.log(a[0],a[1],a[2]);//1 2 3
使用Array()建構函式時,可以省略new操作符
var a1 = Array(); var a2 = Array(10); var a3 = Array(1,2,3); console.log(a1,a2,a3);//[] [] [1,2,3]
陣列本質
陣列是按次序排列的一組值,本質上,陣列是一種特殊的物件
typeof [1, 2, 3] // "object"
陣列的特殊性體現在,它的鍵名是按次序排列的一組整數(0,1,2…)。由於陣列成員的鍵名是固定的,因此陣列不用為每個元素指定鍵名,而物件的每個成員都必須指定鍵名
var arr = ['a', 'b', 'c']; console.log(Object.keys(arr));// ["0", "1", "2"] var obj = { name1: 'a', name2: 'b', name3: 'c' };
陣列是物件的特殊形式,使用方括號訪問陣列元素就像用方括號訪問物件的屬性一樣
javascript語言規定,物件的鍵名一律為字串,所以,陣列的鍵名其實也是字串。之所以可以用數值讀取,是因為非字串的鍵名會被轉為字串,然後將其作為屬性名來使用
o={}; //建立一個普通的物件 o[1]="one"; //用一個整數來索引它 //數值鍵名被自動轉成字串 var arr = ['a', 'b', 'c']; arr['0'] // 'a' arr[0] // 'a'
但是,一定要區分陣列索引和物件的屬性名:所有的索引都是屬性名,但只有在0~232-2(4294967294)之間的整數屬性名才是索引
var a = []; //索引 a['1000'] = 'abc'; a[1000] // 'abc' //索引 a[1.00] = 6; a[1] // 6
[注意]單獨的數值不能作為識別符號(identifier)。所以,陣列成員只能用方括號法表示
var arr = [1, 2, 3]; arr[0];//1 arr.0;//SyntaxError
可以使用負數或非整數來索引陣列。但由於其不在0~2的32次方-2的範圍內,所以其只是陣列的屬性名,而不是陣列的索引,明顯的特徵是不改變陣列的長度
var a = [1,2,3]; //屬性名 a[-1.23]=true; console.log(a.length);//3 //索引 a[10] = 5; console.log(a.length);//11 //屬性名 a['abc']='testing'; console.log(a.length);//11
稀疏陣列
稀疏陣列就是包含從0開始的不連續索引的陣列
【1】製造稀疏陣列最直接的方法就是使用delete操作符
var a = [1,2,3,4,5]; delete a[1]; console.log(a[1]);//undefined console.log(1 in a);//false
【2】陣列的逗號之間可以省略元素值,通過省略元素值也可以製造稀疏陣列
var a =[1,,3,4,5]; console.log(a[1]);//undefined console.log(1 in a);//false
[注意]省略的元素值和值為undefined的元素值是有區別的
var a =[1,,3,4,5]; console.log(a[1]);//undefined console.log(1 in a);//false var a =[1,undefined,3,4,5]; console.log(a[1]);//undefined console.log(1 in a);//true
如果在陣列的末尾使用逗號時,瀏覽器之間是有差別的。標準瀏覽器會忽略該逗號,而IE8-瀏覽器則會在末尾新增undefined值
//標準瀏覽器輸出[1,2],而IE8-瀏覽器輸出[1,2,undefined] var a = [1,2,]; console.log(a); //標準瀏覽器輸出2,而IE8-瀏覽器輸出3 var a = [,,]; console.log(a.length);
足夠稀疏的陣列通常在實現上比稠密的陣列更慢,記憶體利用率更高,在這樣的陣列中查詢元素的時間與常規物件屬性的查詢時間一樣長
陣列長度
每個陣列有一個length屬性,就是這個屬性使其區別於常規的javascript物件。針對稠密(也就是非稀疏)陣列,length屬性值代表陣列中元素的個數,其值比陣列中最大的索引大1
[].length //=>0:陣列沒有元素 ['a','b','c'].length //=>3:最大的索引為2,length為3
當陣列是稀疏陣列時,length屬性值大於元素的個數,同樣地,其值比陣列中最大的索引大1
[,,,].length; //3 (Array(10)).length;//10 var a = [1,2,3]; console.log(a.length);//3 delete a[1]; console.log(a.length);//3
陣列的特殊性主要體現在陣列長度是可以動態調整的:
【1】如果為一個陣列元素賦值,索引i大於等於現有陣列的長度時,length屬性的值將設定為i+1
var arr = ['a', 'b']; arr.length // 2 arr[2] = 'c'; arr.length // 3 arr[9] = 'd'; arr.length // 10 arr[1000] = 'e'; arr.length // 1001
【2】設定length屬性為小於當前長度的非負整數n時,當前陣列索引值大於等於n的元素將從中刪除
a=[1,2,3,4,5]; //從5個元素的陣列開始 a.length = 3; //現在a為[1,2,3] a.length = 0; //刪除所有的元素。a為[] a.length = 5; //長度為5,但是沒有元素,就像new Array(5)
[注意]將陣列清空的一個有效方法,就是將length屬性設為0
var arr = [ 'a', 'b', 'c' ]; arr.length = 0; arr // []
【3】將陣列的length屬性值設定為大於其當前的長度。實際上這不會向陣列中新增新的元素,它只是在陣列尾部建立一個空的區域
var a = ['a']; a.length = 3; console.log(a[1]);//undefined console.log(1 in a);//false
如果人為設定length為不合法的值(即0——232-2範圍以外的值),javascript會報錯
// 設定負值 [].length = -1// RangeError: Invalid array length // 陣列元素個數大於等於2的32次方 [].length = Math.pow(2,32)// RangeError: Invalid array length // 設定字串 [].length = 'abc'// RangeError: Invalid array length
由於陣列本質上是物件,所以可以為陣列新增屬性,但是這不影響length屬性的值
var a = []; a['p'] = 'abc'; console.log(a.length);// 0 a[2.1] = 'abc'; console.log(a.length);// 0
陣列遍歷
使用for迴圈遍歷陣列元素是最常見的方法
var a = [1, 2, 3]; for(var i = 0; i < a.length; i++) { console.log(a[i]); }
當然,也可以使用while迴圈
var a = [1, 2, 3]; var i = 0; while (i < a.length) { console.log(a[i]); i++; } var l = a.length; while (l--) { console.log(a[l]); }
但如果陣列是稀疏陣列時,使用for迴圈,就需要新增一些條件
//跳過不存在的元素 var a = [1,,,2]; for(var i = 0; i < a.length; i++){ if(!(i in a)) continue; console.log(a[i]); }
還可以使用for/in迴圈處理稀疏陣列。迴圈每次將一個可列舉的屬性名(包括陣列索引)賦值給迴圈變數。不存在的索引將不會遍歷到
var a = [1,,,2]; for(var i in a){ console.log(a[i]); }
由於for/in迴圈能夠列舉繼承的屬性名,如新增到Array.prototype中的方法。由於這個原因,在陣列上不應該使用for/in迴圈,除非使用額外的檢測方法來過濾不想要的屬性
var a = [1,,,2]; a.b = 'b'; for(var i in a){ console.log(a[i]);//1 2 'b' }
//跳過不是非負整數的i var a = [1,,,2]; a.b = 'b'; for(var i in a){ if(String(Math.floor(Math.abs(Number(i)))) !== i) continue; console.log(a[i]);//1 2 }
javascript規範允許for/in迴圈以不同的順序遍歷物件的屬性。通常陣列元素的遍歷實現是升序的,但不能保證一定是這樣的。特別地,如果陣列同時擁有物件屬性和陣列元素,返回的屬性名很可能是按照建立的順序而非數值的大小順序。如果演算法依賴於遍歷的順序,那麼最好不要使用for/in而用常規的for迴圈
類陣列
擁有length屬性和對應非負整數屬性的物件叫做類陣列(array-like object)
//類陣列演示 var a = {}; var i = 0; while(i < 10){ a[i] = i*i; i++; } a.length = i; var total = 0; for(var j = 0; j < a.length; j++){ total += a[j]; }
有三個常見的類陣列物件:
【1】arguments物件
// arguments物件 function args() { return arguments } var arrayLike = args('a', 'b'); arrayLike[0] // 'a' arrayLike.length // 2 arrayLike instanceof Array // false
【2】DOM方法(如document.getElementsByTagName()方法)返回的物件
// DOM元素 var elts = document.getElementsByTagName('h3'); elts.length // 3 elts instanceof Array // false
【3】字串
// 字串 'abc'[1] // 'b' 'abc'.length // 3 'abc' instanceof Array // false
[注意]字串是不可變值,故當把它們作為陣列看待時,它們是隻讀的。如push()、sort()、reverse()、splice()等陣列方法會修改陣列,它們在字串上是無效的,且會報錯
var str = 'abc'; Array.prototype.forEach.call(str, function(chr) { console.log(chr);//a b c }); Array.prototype.splice.call(str,1); console.log(str);//TypeError: Cannot delete property '2' of [object String]
陣列的slice方法將類陣列物件變成真正的陣列
var arr = Array.prototype.slice.call(arrayLike);
javascript陣列方法是特意定義為通用的,因此它們不僅應用在真正的陣列而且在類陣列物件上都能正確工作。在ECMAScript5中,所有的陣列方法都是通用的。在ECMAScript3中,除了toString()和toLocaleString()以外的所有方法也是通用的
var a = {'0':'a','1':'b','2':'c',length:3}; Array.prototype.join.call(a,'+');//'a+b+c' Array.prototype.slice.call(a,0);//['a','b','c'] Array.prototype.map.call(a,function(x){return x.toUpperCase();});//['A','B','C']
陣列亂序
陣列亂序的英文為shuffle,也稱為洗牌。一般地,有如下兩種方法
1、給陣列原生的sort()方法傳入一個函式,此函式隨機返回1或-1,達到隨機排列陣列元素的目的
var array = [1,2,3,4,5];
console.log(array.sort(function(){return Math.random() - 0.5}));//[2,1,5,4,3]
如果打亂100000個元素的陣列,則需要100ms左右
var arr = []; var NUM = 100000; for(var i = 0; i < NUM; i++){ arr.push(i); } var startTime = +new Date(); arr.sort(function(){return Math.random() - 0.5}); console.log(+new Date() - startTime);//100
2、第二種方法是依次遍歷陣列中的每個元素,遍歷到的元素與一個隨機位置的元素交換值
var arr = [1,2,3,4,5]; for(var i = 0 ; i < arr.length; i++){ var randomIndex = Math.floor(Math.random()*arr.length); [arr[i],arr[randomIndex]] = [arr[randomIndex],arr[i]]; } console.log(arr);//[2, 3, 1, 4, 5]
如果打亂100000個元素的陣列,需要13ms左右,因此第二種方法效率較高
var arr = []; var NUM = 100000; for(var i = 0; i < NUM; i++){ arr.push(i); } var startTime = +new Date(); for(var i = 0 ; i < arr.length; i++){ var randomIndex = Math.floor(Math.random()*arr.length); [arr[i],arr[randomIndex]] = [arr[randomIndex],arr[i]]; } console.log(+new Date() - startTime);//13
參考資料
【1】 ES5/array物件 https://www.w3.org/html/ig/zh/wiki/ES5/builtins#Array_.E5.AF.B9.E8.B1.A1
【2】 阮一峰Javascript標準參考教程——基本語法 http://javascript.ruanyifeng.com/grammar/array.html
【3】《javascript權威指南(第6版)》第7章 陣列
【4】《javascript高階程式設計(第3版)》第5章 引用型別
【5】《javascript DOM程式設計藝術(第2版)》第2章 javascript語法
【6】《javascript語句精粹》第6章 陣列