陣列的方法真的很多,多到可以自成一個鐵人賽系列(咦找一下還真的有,太厲害了!!) 但這裏因為內容有限,所以只能重點講比較常用的方法,以及一些應用例子。
今天會針對整理關於indexOf
、splice
、concat
與展開運算子的知識。
indexOf
的用法很直接,就是尋找在陣列中的某個值。
array.indexOf(想尋找的元素,索引值起點)
indexOf
就只會回傳最先出現的那個值===
全等的比較方式去尋找你指定想尋找的值splice
可以刪除陣列中的值。它的回傳值是被刪除的值:
array.splice(要開始改動的索引值,刪除多少個元素,想加入的元素)
新手的我平時很少用第3個参數,看看MDN才知道原來也可以這樣去用,例子如下:
const cartoon = ['pikachu','rickAndMorty','simpsons']
//不刪除值,只把'onePiece'加到索引值是1的位置
cartoon.splice(1,0,'onePiece')
console.log(cartoon) //["pikachu", "onePiece", "rickAndMorty", "simpsons"]
//刪除值,以及換上另一個值
cartoon.splice(2,1,'naruto')
console.log(cartoon) //["pikachu", "rickAndMorty", "naruto"]
Q1) 我想刪除一筆資料,但不確定該陣列有沒有該筆資料
const city = ['Hong Kong', 'Tokyo', 'Seoul', 'Taipei', 'BangKok', 'Singapore']
const deleteCity = city.indexOf('BangKok');
if(deleteCity > -1){
city.splice(deleteCity,1)
}
console.log(city) //["Hong Kong", "Tokyo", "Seoul", "Taipei", "Singapore"]
以上例子,利用不存在陣列裏的index會等於-1的特性,判斷該陣列裏是否有想刪除的資料,如有,就用splice
語法把它刪除。
當然方法也有多種,例如我們也可以用filter
:
const newCity = city.filter( (cityName) => {
return cityName !== 'BangKok'
})
console.log(newCity) //["Hong Kong", "Tokyo", "Seoul", "Taipei", "Singapore"]
這樣就能避免修改到原陣列了。
Q2) 我想刪除重覆的資料
const city = ['Taipei', 'Hong Kong', 'Tokyo', 'Seoul', 'Seoul', 'Taipei', 'BangKok', 'Singapore']
const uniqueCity = city.filter( (cityName,index) => {
return city.indexOf(cityName) === index
})
console.log(uniqueCity) //["Taipei", "Hong Kong", "Tokyo", "Seoul", "BangKok", "Singapore"]
這裏用到indexOf
是找出該資料在陣列裏,第一次出現的索引值,而()
裏index是指正在處理中的該筆資料,在陣列中的索引值。
例如'Taipei'
這個字串,在陣列中第一次出現的索引值是0,但當這程式跑到那個重複的'Taipei'
時,它此時在陣列的索引值是5,並不等於第一次出現的索引值,因此可見它是重複的資料,可以被刪除掉。
forEach
和splice
寫好嗎?在寫這篇文章的同時,我也考慮過能不能用forEach
和splice
的寫法:
let city = ['Taipei', 'Hong Kong', 'Tokyo', 'Seoul', 'Seoul', 'Taipei', 'BangKok', 'Singapore']
city.forEach( (cityName, index) => {
console.log(cityName, index, city.indexOf(cityName))
if(index !== city.indexOf(cityName)){
city.splice(index,1)
}
})
console.log(city) //["Taipei", "Hong Kong", "Tokyo", "Seoul", "Taipei", "BangKok", "Singapore"]
結果只是有成功刪掉重覆的'Seoul'
,但'Taipei'
沒有。之後再查一下console:
發現它竟然沒有跑到第二個'Taipei'
!它是跑完第二個'Seoul'
之後,就跳過第二個'Taipei'
,直接去跑'BangKok'
。可想而知為什麼第二個'Taipei'
沒有被刪掉,因為它已經被忽略掉了。
仔細想想,這是因為第二個'Taipei'
前面的那個'Seoul'
被刪除了,所以所有元素的索引值都變了。當前面的那個'Seoul'
被刪除後,緊接住的第二個'Taipei'
的索引值變成了4,但因為對forEach
迴圈來說,它已經跑過4了,下一次會跑5,而這時候的5已經不是'Taipei'
,而是'BangKok'
。所以第二個'Taipei'
就直接被跳過了。
總括而言,這個方法就是很容易會埋下一些問題,因為我在跑forEach
同時又修改了forEach
正在迭代的資料,所以並不建議這樣做~(但好像只有我在想這些蠢問題XD
concat
會合併陣列或值。使用時要注意:它會淺拷貝原本的陣列,所以當原陣列被修改後,它的拷貝都會被修改。
const a = [2,[3],4]
const b = [5,6,[7]]
const sum = a.concat(b)
console.log(sum) //[2,[3],4,5,6,[7]]
//a被修改
a[1].push('x','y')
//sum也會被修改
console.log(sum)//[2,[3,"x","y"],4,5,6,[7]]
在閱讀有關concat
的文章時,不少文章都會一併談及展開運算子(Spread syntax(...)),因為展開運算子與concat
一樣能合併陣列,而且使程式碼更易閱讀。
展開運算子可以用來:
concat
一樣)apply
)展開運算子可以展開可迭代的元素,除了陣列,還包括物件、字串。但這裏只會針對陣列的情況作解釋為主。
除了concat
,我們也可以用展開運算子來合併陣列,而且跟concat
一樣,展開運算子是屬於淺拷貝的做法。
寫法範例如下:
/*--- 合併陣列 ---*/
const arrayA = ['JavaScript','HTML','CSS']
const arrayB = [100,200,300]
const arrayC = [[2],[4]]
const combinedArray1 = [...arrayA,...arrayB,...arrayC]
const combinedArray2 = arrayA.concat(arrayB,arrayC)
//上面兩者都會回傳 ["JavaScript", "HTML", "CSS", 100, 200, 300, [2], [4]]
/*--- 合併陣列(在中間插入) ---*/
const arrayD = ['Vue','React',...arrayB,'Angular']
console.log(arrayD)
//["Vue", "React", 100, 200, 300, "Angular"]
從例子可見,用展開運算子的寫法比concat
的寫法更易閱讀!
合併物件也可以,但不同於陣列,如果有屬值重複,就會被蓋掉:
//合併物件
const foodA = {name: 'apple', color: 'red'};
const foodB = {name: 'banana', color: 'yellow', price: 14};
const foodMerge = {...foodA,...foodB}
//注意,如果屬性重複,就會被最後一個物件的屬性覆蓋掉
console.log(foodMerge) //{name: "banana", color: "yellow", price: 14}
const num = [100,200]
function sum(a,b){
console.log( a + b )
}
//以下兩者回傳結果都是一樣
sum(...num) //300
sum.apply(null, num) //300
每個函式(除了箭頭函式)裏,都會有隱藏物件arguments,它以類陣列的形式呈現每一個被傳進函式裏的参數。以下例子可見,我們可以用展開運算子把類陣列轉為陣列:
function func(x,y){
console.log(arguments) //回傳類陣列
console.log(Array.isArray(arguments)) //false
const convertToArr = [...arguments];
console.log(convertToArr); //[123,'456']
console.log(Array.isArray(convertToArr)); //true
}
func(123,'456')
上文曾經提及我們還可以展開物件或字串,例如:
const word = 'hello'
const wordArr = [...'hello']
console.log(wordArr) //["h", "e", "l", "l", "o"]
當展開物件時,前題是物件能夠被迭代,物件本身是不能被迭代的,但有些情況下除外,例如物件本身是被放在陣列裏時就可以被迭代。MDN的解釋:
Objects themselves are not iterable, but they become iterable when used in an Array, or with iterating functions such as map(), reduce(), and assign(). When merging 2 objects together with the spread operator, it is assumed another iterating function is used when the merging occurs.
例如我把單純地把一個物件與陣列合併,這時候該物件是不能被展開的,因為它是不能被迭代。
const obj = {name:'Mary',age: 13,address:'abc st.'};
const objCombineArr = [...obj,12,3]
//Uncaught TypeError: object is not iterable (cannot read property Symbol(Symbol.iterator)
Q1) 把二維陣列合併成一個陣列,並找出有Cabin此字的行李箱
const luggage = [
['Original Cabin','Classic Cabin','Original Trunk S'],
['Essential Cabin', 'Original Check-in M', 'Classic Check-in M'],
['Original Trunk S', 'Hybrid Cabin S'],
]
const allLuggage = [].concat(...luggage)
console.log(allLuggage)
const result = allLuggage.filter( (item) => {
return item.match(/Cabin/g)
})
console.log(result) //["Original Cabin", "Classic Cabin", "Essential Cabin", "Hybrid Cabin S"]
Q2) 自訂new Date
產生的時間
let today = new Date(...[2020,5,1])
console.log(today)
//Mon Jun 01 2020 00:00:00 GMT+0800 (Hong Kong Standard Time)
indexOf:
indexOf
就只會回傳最先出現的那個值splice:
concat:
展開運算子:
concat
一樣)apply
)從ES6開始的JavaScript學習生活 - 陣列
從ES6開始的JavaScript學習生活 - 展開運算符與其餘運算符
[ES6-重點紀錄] 擴展運算子 Spread Operator
ES6-展開運算子(Spread Operator)、其餘參數(Rest Operator)
展開運算符與其餘運算符
JavaScript 陣列處理:找東西 - indexOf、$.inArray 與 filter