上篇介紹了陣列的基本概念和一些簡單的陣列元素運算函數,實際上,陣列提供的函數還有很多。
push
、 pop
、 shift
和unshift
是操作陣列首尾兩端的函數,上文已經講過,本文不再贅述。
上篇已經簡單介紹過,數組就是一個特殊的對象,因此我們可以嘗試使用對象的屬性刪除方法: delete
。
舉個例子:
let arr = [1,2,3,4,5];delete arr[2];console.log(arr);
程式碼執行結果如下:
注意觀察圖中標黃的位置,雖然元素被刪除了,但是陣列的長度仍然是5
,而且刪除掉的位置多了一個空
。如果我們訪問下標為2
的元素,會得到如下的結果:
造成這種現象的原因是, delete obj.key
是透過key
移除對應值的,也就是說delete arr[2]
刪除了數組中的2:3
鍵值對,當我們訪問下標2
時,就是undefined
了。
而在數組中,我們常常希望刪除元素後,元素的位置會被後繼的元素填補,數組的長度變短。
這時候,我們需要splice()
方法。
需要事先說明的是, splice()
方法的功能相當豐富,並非只能刪除元素,以下是語法:
arr.splice(start[,deleteCount,e1,e2,...,eN])
splice
方法從start
位置開始,刪除deleteCount
個元素,然後原地插入e1,e2,e3
等元素。
以下實例可以從陣列中刪除一個元素:
let arr = [1,2,3,4,5]arr.splice(0,1);//刪除掉第一個元素1console.log(arr)
以上程式碼刪除數組中第一個位置的1
個元素,執行結果如下:
刪除多個元素和刪除一個元素用法相同,只需要將第二個參數改為指定數量就可以了,舉例如下:
let arr = [1,2,3,4,5];arr. splice(0,3);//刪除前三個元素console.log(arr);//[4,5]
程式碼執行結果如下:
如果我們只提供一個參數start
,那麼就會刪除陣列start
位置後面的所有元素,舉個例子:
let arr = [1,2,3,4,5]arr.splice(2);//刪除從下標為2以及後面的所有元素console.log(arr);//[1,2]
程式碼執行結果:
如果我們提供了超過兩個參數,那麼就可以替換數組元素,舉個例子:
let arr = [1,2,3,4,5];arr.splice(0,2,'itm1',' itm2','itm3');console.log(arr);//['itm1','itm2','itm3',3,4,5]
程式碼執行結果如下:
以上程式碼實際上執行了兩步驟操作,先刪除從0
開始的2
個元素,然後在0
位置插入三個新的元素。
如果我們把第二個參數(刪除數量)改為0
,那麼就可以只插入元素,不刪除元素,舉個栗子:
let arr = [1,2,3,4,5]arr.splice( 0,0,'x','y','z')console.log(arr);//['x','y','z'1,2,3,4,5]
splice()
函數會傳回被刪除的元素數組,舉例:
let arr = [1,2,3,4,5]let res = arr.splice(0,3,'x','y' )console.log(arr)//['x','y',4,5]console.log(res)//[1,2,3]
程式碼執行結果:
我們可以使用負數指示開始操作元素的位置,舉例:
let arr = [1,2,3,4,5]arr.splice(-1,1,'x','y','z ')console.log(arr)//[1,2,3,4,'x','y','z']
程式碼執行結果如下:
slice()
方法可以截取指定範圍的數組,語法如下:
arr.slice([start],[end])
傳回一個新數組,新數組從start
開始,到end
結束,但不包括end
。
範例:
let arr = [1,2,3,4,5]console.log(arr.slice(2,5))//[3,4,5]console.log(arr.slice(1,3) )//[2,3]
程式碼執行結果:
slice()
同樣可以用負數下標:
let arr = [1,2,3,4,5]console.log(arr.slice(-3))//[3,4,5]console.log(arr .slice(-5,-1))//[1,2,3,4]
程式碼執行結果如下:
如果只為slice()
方法提供一個參數,就會和splice()
一樣截斷到陣列結尾。
concat()
函數可以將多個數組或其他類型的值拼接稱一個長數組,語法如下:
arr.concat(e1, e2, e3)
以上程式碼將傳回一個新的數組,新數組由arr
拼接e1
、 e2
、 e3
而成。
範例:
let arr = [1,2,3]console.log(arr.concat([4,5],6,7,[8,9]))
程式碼執行結果如下:
普通的對象,即使它們看起來和對像一樣,仍然會被作為一個整體插入到數組中,例如:
let arr = [1,2]let obj = {1:'1',2:2}console.log (arr.concat(obj))
程式碼執行結果:
但是,如果物件具有Symbol.isConcatSpreadable
屬性,就會被當作陣列處理:
let arr = [1,2]let obj = {0:'x', 1:'y', [Symbol.isConcatSpreadable]:true, length:2 }console.log(arr.concat(obj))
程式碼執行結果:
遍歷整個數組,為每個數組元素提供一個操作函數,語法:
let arr = [1,2]arr.forEach((itm,idx,array)=>{ ...})
應用範例:
let arr = [1,2,3,4,5]arr.forEach((itm)=>{ console.log(itm)})
程式碼執行結果:
let arr = [1,2,3,4,5]arr.forEach((itm,idx,array)=>{ console.log(`arr[${idx}] in [${array}] is ${itm}`)})
程式碼執行結果:
類似字串, indexOf
、 lastIndexOf
、 includes
可與查詢數組中指定元素的下標:
arr.indexOf(itm,start)
:從start
位置開始搜尋itm
,如果找到返回下標,否則返回-1
;arr.lastIndexOf(itm,start)
:倒序查找整個數組,直到start
處,返回第一個查到的下標(也就是數組最後一個匹配項),找不到返回-1
;arr.includes(itm,start)
:從start
位置開始搜尋itm
,找到回傳true
,否則回傳false
;範例:
let arr = [1,2,3,4,5,6,"7","8","9" ,0,0,true,false]console.log(arr.indexOf(0))//9console.log(arr.lastIndexOf(0))//10console.log(arr.includes(10))//falseconsole. log(arr.includes(9))//false
這些方法在比較陣列元素的時候使用的是===
,所以false
和0
是不一樣的。
NaN的處理
NaN
是一個特殊的數字,三者在處理NaN
有細微差別:
let arr = [NaN,1,2,3,NaN]console.log(arr.includes(NaN))//trueconsole.log( arr.indexOf(NaN))//-1console.log(arr.lastIndexOf(NaN))//-1
產生這種結果的原因和NaN
本身的特性有關,即NaN
不等於任何數字,包括他自己。
這些內容在前面的章節已經講過了,遺忘的童鞋記得溫故知新呀。
在程式設計過程中常常會遇到物件數組,而物件是不能直接使用===
比較的,如何從數組中查找到滿足條件的物件呢?
這時候就要使用find
和findIndex
方法,語法如下:
let result = arr.find(function(itm,idx,array){ //itm陣列元素//idx元素下標//array陣列本身//傳入一個判斷函數,如果函數傳回true,就傳回目前物件itm})
舉個栗子,我們找出name
屬性等於xiaoming
的物件:
let arr =[ {id:1,name:'xiaoming'}, {id:2,name:'xiaohong'}, {id:3,name:'xiaojunn'},]let xiaoming = arr.find(function(itm,idx,array){ if(itm.name == 'xiaoming')return true;})console.log(xiaoming)
程式碼執行結果:
如果沒有符合條件的對象,就會傳回undefined
。
以上程式碼也可以簡化為:
let xiaoming = arr.find((itm)=> itm.name == 'xiaoming')
執行效果是完全相同的。
arr.findIndex(func)
的用途和arr.find(func)
幾乎相同,唯一不同的地方在於, arr.findIndex
返回符合條件對象的下標而不對象本身,找不到返回-1
。
find
和findIndex
只能找一個符合要求的對象,如果一個陣列中存在多個符合要求的對象,就需要使用filter
方法,語法如下:
let results = arr.filter(function(itm,idx,array){ //和find的用法相同,不過會傳回符合要求的物件陣列//找不到回傳空數組})
舉個例子:
let arr =[ {id:1,name:'xiaoming'}, {id:2,name:'xiaohong'}, {id:3,name:'xiaojunn'},]let res = arr.filter(function(itm,idx,array){ if(itm.name == 'xiaoming' || itm.name == 'xiaohong')return true;})console.log(res)
程式碼執行結果:
arr.map
方法可以對數組的每個物件都呼叫函數,然後傳回處理後的數組,這是數組最有用的、最重要的方法之一。
語法:
let arrNew = arr.map(function(itm,idx,array){ //傳回新的結果})
舉例,傳回字串陣列對應的長度陣列:
let arr = ['I','am','a','student']let arrNew = arr.map((itm)= >itm.length)//return itm.lengthconsole.log(arrNew)//[1,2,1,7]
程式碼執行結果:
arr.sort
對數組進行原地排序,並傳回排序後的數組,但是,由於原始數組已經發生了改變,返回值實際上沒有什麼意義。
所謂原地排序,就是在原始數組空間內排序,而不是新建一個數組
let arr = ['a','c','b']arr.sort()console.log(arr)
程式碼執行結果:
注意,預設情況下
sort
方法是以字母序進行排序的,也就是適用於字串排序,如果要排列其他類型的數組,需要自訂比較方法
數字數組
let arr = [1,3,2]arr. sort(function(a,b){ if(a > b)return 1; if(a < b)return -1; return 0;})
程式碼執行結果:
sort
函數內部採用了快速排序演算法,也可能是timsort
演算法,但是這些我們都不需要關心,我們只需要關注比較函數就可以了。
比較函數可以傳回任何數值,正數表示>
,負數表示<
, 0
表示等於,所以我們可以簡化數字比較方法:
let arr = [1,3,2]arr.sort((a,b)=> a - b)
如果想要逆序排列只需要交換a
和b
的位置既可以了:
let arr = [1,3,2]arr.sort((a,b)=> b - a)
字串排序
別忘了字串比較要使用str.localeCompare(str1)
方法呦
let arr = ['asdfas','success','failures']arr.sort((a,b)=>a.localeCompare(b))
程式碼執行結果:
arr.reverse
用於逆序數組
let arr = [1,2,3]arr.reverse()console.log(arr)//[3,2,1]
這個沒啥好說的。
還記得字串分割函數嗎?字串分割函數可以將字串分割成一個字元陣列:
let str = 'xiaoming,xiaohong,xiaoli'let arr = str.split(',')//['xiaoming','xiaohong','xiali']
冷門知識,
split
函數有第二個參數,可以限制產生數組的長度let str = 'xiaoming,xiaohong,xiaoli'let arr = str.split(',',2)//['xiaoming','xiaohong' ]
arr.join()
方法用途和split
方法相反,可以將一個陣列組合成一個字串。
舉個栗子:
let arr = [1,2,3]let str = arr.join(';')console.log(str)
程式碼執行結果:
arr.reduce
方法和arr.map
方法類似,都是傳入一個方法,然後依序對數組元素呼叫這個方法,不同的地方在於, app.map
方法在處理數組元素時,每次元素調用都是獨立的,而arr.reduce
會把上一個元素的呼叫結果傳到目前元素處理方法中。
語法:
let res = arr.reduce(function(prev,itm,idx,array){ //prev是上一個元素呼叫傳回的結果//init會在第一個元素執行時充當上一個元素呼叫結果},[init])
試想一下,如何實作一個數字組成的陣列元素和呢? map是沒有辦法實現的,這時候就需要使用arr.reduce
:
let arr = [1,2,3,4,5]let res = arr.reduce((sum,itm)=>sum+itm,0) console.log(res)//15
程式碼執行流程如下圖:
arr.reduceRight
和arr.reduce
用途相同,只不過從右往左對元素呼叫方法。
數組是物件的一種特例,使用typeof
無法準確的分辨二者的區別:
console.log(typeof {})//objectconsole.log(typeof [])//object
二者都是對象,我們需要使用Array.isArray()
方法進一步做判斷:
console.log(Array.isArray({}))//falseconsole.log(Array.isArray([]))//true
arr.some(func)
和arr.every(func)
方法用來檢查數字,執行機制和map
類似。
some
對每個數組元素執行傳入的方法,如果方法回傳true
,立即傳回true
,如果所有的元素都不回傳true
,就回傳false
。
every
對陣列的每個元素執行傳入的方法,如果所有元素都傳回true
,則傳回true
,否則傳回false
。
舉例:
let arr = [1,2,3,4,5]//判斷數組是否存在大於2的元素console.log(arr.some((itm)=>{ if(itm > 2)return true;}))//true//判斷是否所有的元素都大於2console.log(arr.every((itm)=>{ if(itm > 2)return true;}))//false
在所有的陣列方法中,除了sort
,都有一個不常用固定參數thisArg
,語法如下:
arr.find(func,thisArg)arr.filter( func,thisArg)arr.map(func,thisArg)
如果我們傳入了thisArg
,它就會在func
變成this
。
這個參數在常規情況下是沒什麼用處的,但是如果func
是一個成員方法(對象的方法),而且方法中使用了this
那麼thisArg
就會非常有意義。
舉例:
let obj = { num : 3, func(itm){ console.log(this) return itm > this.num;//找出大於3的數字}}let arr = [1,2,3,4,5,6,7]let newArr = arr.filter(obj.func,obj)console.log (newArr)
程式碼執行結果:
這裡我們可以看到, func
中輸出的this
就是我們傳入的thisArg
值。
如果我們使用物件成員方法,同時不指定thisArg
的值,就會造成this
為undefined
,進而導致程式錯誤。