林大媽的JavaScript基礎知識(三):JavaScript編程(4)數組
- 2019 年 10 月 3 日
- 筆記
數組,是一段線性分配的,具有非常高性能的數據結構。簡單地說,數組以連續的空間存儲,通過整數地計算偏移量訪問其中的元素,將讀取修改的時間複雜度降低至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 }
拓展:1. ES6 Map
我們都知道,在Array對象中,每個元素就是一個對象,通過鍵值對存儲數據。但其中的鍵只能是字符串類型。我們渴求能用類似Number或其他基本數據類型作為鍵,這樣的表達會更清晰。因此ES6為我們帶來了Map數據結構,它還具有極高的查詢效率。且看它的使用語法:
1 var myMap = new Map([['name', 'MotherLyn'],['score', 51]]);
觀察發現,我們完全可以把它當做是一個兩列n行的,對數據類型有規範的二維Array。它還有許多內置方法:set、get、has、delete。
要注意的是,Map中所有的鍵類似數據庫中的primary key,也就是他們都是不可重複的,填入相同的鍵不同的值只會把以往的數據衝掉。
2. ES6 Set
Set與Map類似但又十分不同。說它相同是因為Set對數據類型也是存在劃分的(3和‘3’是不同的兩個鍵);說它不同是因為Set只一個值而不是一個鍵值對。因此使用Set時只需要傳入一個一維的Array即可:
1 var mySet = new Set(['MotherLyn', 51]);
通過使用內置方法add、delete和has可以進行增刪查改操作。
3. ES6 for of遍歷方法
前面我們提到,使用for in遍歷會將可枚舉的原型屬性一塊遍歷到。為了解決這個問題,ES6提出了for of遍歷。它在使用上和for in是相同的,只是把in改成of而已。
總結:① JavaScript中的Array對象只是一個內建的對象,並不是傳統意義上的數組,它在內存中不是連續的空間,因此只能遍曆元素來進行查找修改,性能較差但靈活性非常好。
② Array.prototype中有非常多的方法,常用的有以上這些:indexOf、push、pop、unshift、shift、sort、reverse、join、slice、splice和concat。還有很多其他的方法,要善用這些方法只能靠多練習,慢慢積累。
③ ES6中的Map和Set都是類似Array的數據結構,它們都嚴格管理數據類型,但區別是Map以鍵值對的方式存儲,Set只能存儲值而不能存儲鍵。