陣列,是一段線性分配的,具有非常高效能的資料結構。簡單地說,陣列以連續的空間儲存,通過整數地計算偏移量訪問其中的元素,將讀取修改的時間複雜度降低至O(1),我們稱之為猝發式存取。是不是非常期待?沒錯,像這樣的好東西,JavaScript沒有。
1. Array簡介
但作為替代,JavaScript設計者想出了一個更方便但效能相對較低的方案,列印觀察Array.prototype,會發現,設計者為我們提供的是一個array-like(類陣列)的物件。在檢索和更新屬性上,Array就和普通的物件一模一樣(也就是說要遍歷所有屬性),只是多了一個不可列舉的屬性length來記錄這個物件所表示的陣列的長度。儘管Array物件的效能明顯比陣列要差,但是搭配上弱型別的JavaScript語言(當然,JavaScript中不存在傳統的陣列一部分原因也是因為這一點),它在使用上非常的方便。更貼心的是,設計者還為我們提供了許多內建的方法,可以快速解決其他語言費很大勁才能解決的問題。
2. 宣告
陣列的宣告跟物件的宣告很類似。我們可以用兩種方法初始化一個陣列:① 直接用 var array = []; 我們稱之為陣列字面量的方式來初始化;② 使用建構函式 var array = new Array(); 如果引數填入一個數字,則返回一個長度為這個數字的空陣列,如果引數填入多個值,則返回一個按順序儲存了這些值的Array物件。
3. 修改
上面我們已經搞清楚了Array物件總體的結構,這樣修改一個陣列就可以轉化為我們以前學到的修改一個物件屬性的知識了。由於JavaScript的靈活性,除非你定義一個大到Infinity的陣列,其他情況下均不會因為越界報錯(Runtime Error也許是很多人的噩夢,反正是我的噩夢)。因此,假如我們現在有這樣一個陣列:
1 var myArray = [0, '1', true]
利用JavaScript會幫我們維護length屬性這一特點(但為了編寫易維護的程式碼,不推薦這些簡單粗暴的做法),我們也可以做一些在別的語言中看起來不可思議的操作,例如:① myArray[8] = undefined 會直接把Array的長度擴充套件到8;② myArray.length = 0; 能直接清空陣列。除了可以這樣清空陣列,還可以通過Array.prototype.splice方法完成清空,因此我們把目光放到陣列的原型方法上來。
4. 原型方法(均以上面的myArray舉例)
① indexOf
indexOf方法同時存在於Array.prototype和String.prototype中,可以用它來檢測陣列或字串中是否存在對應的元素,如果存在,則返回它的下標,如果不存在,則返回-1:
1 console.log(myArray.indexOf(0)); //0 2 console.log(myArray.indexOf(1)); //-1
由於在陣列中,1和'1'是兩個不同的值,因此第二句返回結果-1。
② push、pop、unshift、shift
push朝陣列末尾推入若干新元素,返回加入後陣列的length。
pop彈出陣列末尾的一個元素,返回被彈出的元素。
unshift朝陣列開頭推入若干新元素,返回加入後陣列的length。
shift彈出陣列開頭的一個元素,返回被彈出的元素。
1 console.log(myArray.push('A', 'B')); //5 2 console.log(myArray.pop()); //'B' 3 console.log(myArray.unshift('A', 'B')); //6 4 console.log(myArray.shift()); //'A'
③ sort(這時候我們重新定義一下 myArray = [5, 2, 0, 10, 17, 25]; )
sort方法在原陣列上動刀,這裡我們期望將陣列中的數按從小到大的順序排列,sort函式可以幫我們做到這一點,但需要注意的是,sort函式預設把這些元素轉化為字串進行比較。因此這個陣列排序以後的結果是這樣的:
1 console.log(myArray.sort()); // [0, 10, 17, 2, 25, 5]
因此通常需要填入一個比較判斷函式作為引數,下面傳入一個箭頭函式,按照我所定義的這個函式進行判斷大小再排序:
1 console.log(myArray.sort((x, y)=>{return x - y;})); // [0, 2, 5, 10, 17, 25]
④ reverse和join
reverse方法在原陣列上動刀,返回跟原來相反的陣列;join方法相當於String.prototype.split方法的反函式,填入一個字串引數,以這個引數將每個元素分隔開,合併成一個字串並返回。這兩個方法可以搭配使用來處理反向輸出字串的問題:
1 var str = "Hello world!"; 2 3 console.log(str.split('').reverse().join('')); 4 //"!dlrow olleH" 5 console.log(str.split(' ').reverse().join(' ')); 6 //"world! Hello"
這裡由於每個函式返回值都是與其相對應的陣列或字串,可以直接在這個返回值上進行操作,因此我們還用到了鏈式呼叫的技巧。
⑤ slice和splice
這兩個方法的區別和使用非常重要,又由於它們名字之間只差一個字母,缺少練習時我們很容易會將其混淆。
slice方法可以類比String.prototype.substring。指定一個引數n,它將返回一個新陣列,這個陣列中含有原陣列下標從n到末尾的所有元素。指定兩個引數a和b時,它將返回一個新陣列,這個陣列含有原陣列下標從a到b的所有元素(不得不說,用中文來描述真的非常蹩腳):
1 var myArray = [1, 2, 3, 4, 5, 6, 7, 8]; 2 3 var aNewArray = myArray.slice(3); 4 var aNewNewArray = myArray.slice(3, 5); 5 6 console.log(aNewArray); 7 //[4, 5, 6, 7, 8] 8 console.log(aNewNewArray); 9 //[4, 5]
當然了,這兩種操作都是含頭不含尾的。如果不指定引數地使用slice,它將返回一個跟原陣列一模一樣的陣列,利用這一點,我們可以用一句程式碼複製一個陣列。
splice是修改一個陣列的“萬能方法”,要注意它將直接在原陣列上動刀,返回值是被刪除的元素組成的陣列:
1 var myArray = ['CapAmerica', 'IronMan', 'Hulk', 'Thor']; 2 // param: 從第4個元素開始操作,刪除0個元素,加入新元素'BlackWidow' 3 myArray.splice(3, 0, 'BlackWidow'); 4 // 由於刪除0個元素,它將返回一個空陣列 5 6 console.log(myArray); 7 // ['CapAmerica', 'IronMan', 'Hulk', 'Thor', 'BlackWidow'] 8 9 // param:從第二個元素開始,刪除三個元素,加入這些新元素 10 myArray.splice(2, 3, 'ScarletWitch', 'Vision', 'CapMarvel'); 11 //返回['Hulk', 'Thor', 'BlackWidow'] 12 13 console.log(myArray); 14 //['CapAmerica', 'IronMan', 'ScarletWitch', 'Vision', 'CapMarvel']
⑥ concat
concat方法不會動原陣列,而是返回新陣列。它返回一個將原陣列和所有你填入的引數都合併在一起的新陣列,因此我們想到了可以用它搭配splice方法來實現一個JavaScript版本的快速排序:
1 function quickSort(arr) { 2 if(arr.length <=1) return arr; 3 var pivotIndex = Math.floor(arr.length / 2); 4 var pivot = arr.splice(pivotIndex, 1)[0]; 5 var left = [], right = []; 6 for(var i = 0; i < arr.length; i++) { 7 if(arr[i] < pivot) left.push(arr[i]); 8 else right.push(arr[i]); 9 }; 10 return quickSort(left).concat(pivot, quickSort(right)); 11 }
總結:① JavaScript中的Array物件只是一個內建的物件,並不是傳統意義上的陣列,它在記憶體中不是連續的空間,因此只能遍歷元素來進行查詢修改,效能較差但靈活性非常好。
② Array.prototype中有非常多的方法,常用的有以上這些:indexOf、push、pop、unshift、shift、sort、reverse、join、slice、splice和concat。還有很多其他的方法,要善用這些方法只能靠多練習,慢慢積累。