JavaScript基礎(chǔ)篇(五) 數(shù)組

本文整理靈感來源掘金大佬的:數(shù)據(jù)結(jié)構(gòu) - 數(shù)組。感謝大佬的整理和分享,此文章主要記錄數(shù)組的主要 API 和自己的一些見解,僅供自己學(xué)習(xí)參考!!!說實(shí)話數(shù)組對于 JavaScript 來說絕對是萬丈高樓的地基,重要性不言而喻,話不多說開整。

數(shù)組的創(chuàng)建


let a = []
let b = new Array()

一般是以上兩種,都是用來創(chuàng)建一個空數(shù)組。如果我們想初始化一個有長度的數(shù)組,并且數(shù)組一開始就有值,如下栗子:

let a = [1, 2, 3]; // [1, 2, 3]
let b = new Array(3); // [undefined, undefined, undefined]
let c = new Array([1, 2, 3]); // [1, 2, 3]
let d = new Array(3).fill(1); // [1, 1, 1]
let e = new Array(3).fill([]); // [Array(0), Array(0), Array(0)]
let f = Array.from({ length : 5}, function() {  // [1, 1, 1, 1, 1]
    return 1
})

前面三個估計(jì)大家都看得懂,后面三個可能大部分沒見過,都屬于 ES6 新增的創(chuàng)建方式,這里了解即可,具體應(yīng)用可以在實(shí)際開發(fā)中碰到在深入了解,其實(shí)基本用的也很少~~~

如何訪問數(shù)組


我們平時使用的 [1, 2, 3] 這種形式,稱為一維數(shù)組。而如果數(shù)組中嵌套數(shù)組,每嵌套多一層,就加一個維度。記住數(shù)組的下標(biāo)是從 0 開始的。如果某個程序猿跟你表白說你是它心中第 0 位的人……你還是拒絕 “他” 吧,已經(jīng)走火入魔沒救了。

  • 一維數(shù)組
let a = [1, 2, 3];
console.log(arr[0]); // 1
console.log(arr[1]); // 2
console.log(arr[2]); // 3
  • 二維數(shù)組
let arr = [[1, 2, 3], [4, 5]]
for (let i = 0; i < arr.length; i++) {
  for (let j = 0; j < arr[i].length; j++) {
    console.log(arr[i][j])
  }
}
  • 三維數(shù)組
let arr = [[1, 2, [3, 4]], [5, 6]]
for (let i = 0; i < arr.length; i++) {
  for (let j = 0; j < arr[i].length; j++) {
    for (let k = 0; k < arr[i][j].length; k++) {
      console.log(arr[i][j][k])
    }
  }
}

更高維度的數(shù)組訪問依次類推即可!但是這里延伸一個問題,多維數(shù)組的訪問異常麻煩,開發(fā)中我們可能經(jīng)常會遇到這種需求:將多維數(shù)組扁平化處理為一維數(shù)組?如何實(shí)現(xiàn)呢,可以自己先研究研究,這里會將代碼放在 forEach() 這個 API 中,畢竟咱要先記錄數(shù)組的一些主要方法在去研究如何扁平化,循序漸進(jìn)+延伸思考才是程序猿精神!!!

數(shù)組的增刪改查


我們知道:數(shù)組是最簡單的內(nèi)存數(shù)據(jù)結(jié)構(gòu),而增刪改查在任何一種數(shù)據(jù)結(jié)構(gòu)中都是基礎(chǔ)。比如前端:DOM 樹形結(jié)構(gòu)的增刪改查,比如服務(wù)端:SQL 的增刪改查。

我們了解數(shù)組增刪改查的同時,應(yīng)該代入自己更深入的思考,使用數(shù)組提供給我們的 API 實(shí)現(xiàn)功能的同時,要同時去了解它的返回值是什么,是否對原數(shù)組造成影響。

  • 數(shù)組的新增

push() 方法:可向數(shù)組的末尾添加一個或多個元素,并返回新的長度,常用語法為:arr.push(newelement1,newelement2,....,newelementX),使用該方法會改變原數(shù)組。

let arr = [1, 2, 3, 4]
console.log(arr.push(5)) // 5
console.log(arr) // [1, 2, 3, 4, 5]
console.log(arr.push(6,7,8)) // 8
console.log(arr) // [1, 2, 3, 4, 5, 6, 7, 8]

unshift() 方法:可向數(shù)組的開頭添加一個或更多元素,并返回新的長度,常用語法為:arr.unshift(newelement1,newelement2,....,newelementX),使用該方法會改變原數(shù)組。

let arr = [1, 2, 3, 4]
console.log(arr.unshift(5)) // 5
console.log(arr) // [5, 1, 2, 3, 4]
console.log(arr.unshift(6,7,8)) // 8
console.log(arr) // [6, 7, 8, 5, 1, 2, 3, 4]

當(dāng)然掘金博主的文章里還介紹了幾種,如:arr[arr.length] = 5 或者 arr[0] = 1 這種;亦或者 splice()concat() 等操作也可以實(shí)現(xiàn)新增。前者其實(shí)也是數(shù)組的一些小技巧應(yīng)用,后者其實(shí)這些 API 有自己更深層次的應(yīng)用,日常開發(fā)中用的最多的新增操作還是 push()。學(xué)而思學(xué)而用才是我們循序進(jìn)步的基礎(chǔ)!!!

  • 數(shù)組的刪除

pop() 方法:刪除數(shù)組的最后一個元素并返回被刪除的元素,使用該方法會改變原數(shù)組。

let arr = [1, 2, 3, 4, 5, 6]
console.log(arr.pop()) // 6
console.log(arr) // [1, 2, 3, 4, 5]
// 嘗試傳參
let arr = [1, 2, 3, 4, 5]
arr.pop(3) // 傳參無效,只會刪除數(shù)組的最后一個元素
console.log(arr) // [1, 2, 3, 4]

shift() 方法:把數(shù)組的第一個元素從其中刪除,并返回第一個元素的值,使用該方法會改變原數(shù)組。

let arr = [1, 2, 3, 4, 5]
console.log(arr.shift()) // 1
console.log(arr) // [2, 3, 4, 5]
// 傳參情況和 pop() 類似,不接受傳參

上面兩個刪除的方式只能適用比較傻瓜的場景,所以數(shù)組也給我們提供了萬金油的刪改 API,接下來認(rèn)識老油條:slice() 和 splice() ,日常開發(fā)中非常常用的兩個 API,因?yàn)樗鼈z實(shí)在太優(yōu)秀了,所以我必須在這里寫一段話~~~

slice() 方法:可從已有的數(shù)組中返回選定的元素。這里要注意:使用 slice() 會返回一個新的數(shù)組而不是修改原數(shù)組。

不得不說,這個方法牛逼哄哄!數(shù)組的拷貝、刪除都可以通過這個方法來實(shí)現(xiàn),具體功能取決于 () 里面?zhèn)鬟f參數(shù)的個數(shù)。

第一種情況:不傳參,此時會對原數(shù)組進(jìn)行深拷貝并生成一個新數(shù)組,即如果我們修改新數(shù)組的值不會影響原數(shù)組。深拷貝淺拷貝搞不明白的可以瞅瞅我寫的:JavaScript基礎(chǔ)篇(一)

let arr = [1, 2, 3, 4, 5]
let newArr = arr.slice()
newArr[0] = 6
console.log(arr) // [1, 2, 3, 4, 5]
console.log(newArr) // [6, 2, 3, 4, 5]

第二種情況:傳入一個參數(shù)且為正值,此時將原數(shù)組從下標(biāo)為傳遞的參數(shù)值開始一直截取到最后。

let arr = [1, 2, 3, 4, 5]
let newArr = arr.slice(1)
console.log(newArr) // [2, 3, 4, 5]

第三種情況:傳入一個參數(shù)且為負(fù)值,此時將截取原數(shù)組末尾的元素,傳遞的參數(shù)為多少,就在后面截取多少個。

let arr = [1, 2, 3, 4, 5]
let newArr = arr.slice(-2)
console.log(newArr) // [4, 5]

第四種情況:傳入兩個參數(shù),第一個參數(shù)為數(shù)組切割的起始值,第二個參數(shù)為數(shù)組切割的結(jié)束值。記住切割的小秘訣:包頭不包尾,例如(1,4)就是下標(biāo)為 1 的元素就要算進(jìn)去,但是下標(biāo)為 4 的元素不會算進(jìn)去。

let arr = [1, 2, 3, 4, 5]
let newArr = arr.slice(1, 4)
console.log(newArr) // [2, 3, 4]

slice() 基本的使用場景就是上面這些,最后總結(jié)一下它的語法:arr.slice(beginSlice, endSlice)

參數(shù) 是否必填 參數(shù)描述
beginSlice 從該索引(以 0 為基數(shù))處開始提取原字符串中的字符。
endSlice 結(jié)束位置(以 0 為基數(shù)),如果不傳,默認(rèn)到數(shù)組末尾。

我看 w3school 中好像規(guī)定 beginSlice 為必傳參數(shù),但是寫法中確實(shí)可以不傳,如不傳會進(jìn)行深拷貝生成一個新的數(shù)組,個人感覺應(yīng)該是該方法的基礎(chǔ)函數(shù)中做了傳參的兼容處理,其實(shí)不一定要傳。(本理解僅供自己參考)

splice() 方法:向/從數(shù)組中添加/刪除項(xiàng)目,然后返回被刪除的項(xiàng)目。

相較于 slice() 多了一個 p,它和 slice() 一樣也是個萬金油的方法,它能適用于新增、修改、刪除這些場景。但是這里一定要記住它和 slice() 區(qū)別最大的一點(diǎn):

splice():所進(jìn)行的操作會影響到原數(shù)組
slice():所進(jìn)行的操作不會影響到原數(shù)組

第一種情況:不傳參,自身返回一個空數(shù)組,原數(shù)組不改變。官方同樣也規(guī)定了第一個參數(shù)和第二個參數(shù)為必傳項(xiàng),此處主要是和 slice() 做對比演示。(不推薦)

let arr = [1, 2, 3, 4, 5]
console.log(arr.splice()) // []
console.log(arr)

第二種情況:傳一個參數(shù),當(dāng) splice() 傳入一個參數(shù)時,它的基礎(chǔ)函數(shù)應(yīng)該做了判斷,第二個參數(shù)不寫應(yīng)該是第一個參數(shù)后面的元素全部刪除。并且同 slice() 一樣,它也支持傳遞負(fù)數(shù),即從項(xiàng)目的末尾位置開始刪除,執(zhí)行 splice() 返回被刪除的元素集合。該方法會改變原數(shù)組。如下栗子:

let arr = [1, 2, 3, 4, 5]
console.log(arr.splice(3)) // [4, 5]
console.log(arr) // [1, 2, 3]
// 如果傳遞負(fù)數(shù)要刷出 4 和 5
console.log(arr.splice(-2)) // [4, 5]
console.log(arr) // [1, 2, 3]

第三種情況:傳兩個參數(shù),第一個參數(shù)代表要刪除項(xiàng)目的位置(使用負(fù)數(shù)可從數(shù)組結(jié)尾處規(guī)定位置。),第二個參數(shù)代表要刪除的項(xiàng)目數(shù)量。官方有規(guī)定使用該方法必須傳入兩個參數(shù),當(dāng)然只傳遞一個參數(shù)也不會報錯,不過可能嚴(yán)謹(jǐn)性不夠。

let arr = [1, 2, 3, 4, 5]
console.log(arr.splice(1, 2)) // [2, 3]
console.log(arr) // [1, 4, 5]
// 第一個參數(shù)為負(fù)值的情況
console.log(arr.splice(-1, 1)) // [5]
console.log(arr) // [1, 2, 3, 4]

第四種情況:傳入三個參數(shù),前面基本都是刪除,但是如果我們傳入三個參數(shù),該方法又解鎖了新增和修改兩個功能,有木有覺得很巧妙,來看看栗子:

// 新增操作
let arr = [1, 2, 3, 4, 5]
console.log(arr.splice(1, 0, 6)) // []
console.log(arr) // [1, 6, 2, 3, 4, 5]
// 同時新增多個元素
let arr = [1, 2, 3, 4, 5]
console.log(arr.splice(1, 0, 6, 7, 8, 9)) // []
console.log(arr) // [1, 6, 7, 8, 9, 2, 3, 4, 5]
// 修改操作
let arr = [1, 2, 3, 4, 5]
console.log(arr.splice(1, 1, 6)) // [2]
console.log(arr) // [1, 6, 3, 4, 5]
// 修改+新增雙合一
let arr = [1, 2, 3, 4, 5]
console.log(arr.splice(1, 1, 6, 7, 8, 9)) // [2]
console.log(arr) // [1, 6, 7, 8, 9, 3, 4, 5]

看了上面的栗子有木有覺得恍然大悟(有點(diǎn)懵逼),簡單總結(jié):

  • 第二個參數(shù)為 0 即代表新增,將第三個(及其后面的參數(shù)) 追加到數(shù)組的下標(biāo)(第一個參數(shù))元素后面去,自身返回一個空數(shù)組,同時該操作會改變原素組。
  • 第二個參數(shù)為 1 即代表修改,要修改的元素為數(shù)組的下標(biāo)(第一個參數(shù))元素,將其改為第三個參數(shù)(及其后面的參數(shù)),自身返回被修改的元素集合(數(shù)組),同時該操作會改變原素組。

用法其實(shí)也不復(fù)雜,可能有點(diǎn)繞,我們也做個表來對該方法做個總結(jié):

參數(shù) 是否必填 參數(shù)描述
index 必需。整數(shù),規(guī)定添加/刪除項(xiàng)目的位置,使用負(fù)數(shù)可從數(shù)組結(jié)尾處規(guī)定位置。
howmany 必需。要刪除的項(xiàng)目數(shù)量。如果設(shè)置為 0,則不會刪除項(xiàng)目。
item1, ..., itemX 可選。向數(shù)組添加的新項(xiàng)目。

對比 pop()shift()slice()splice() 應(yīng)用場景和主要區(qū)別?
pop() 和 shift():使用場景類似,一個尾部刪除,一個頭部刪除,兩者刪除都返回被刪除的元素,返回類型為被刪除的元素自身的類型。
slice() :可以用來做深拷貝、也可以用來刪除數(shù)組中的元素,該方法最大的特點(diǎn)就是它自身返回生成一個新的數(shù)組且所有操作都不影響原數(shù)組。
splice():一個更全面的 API,集合了修改、刪除、新增功能,自身返回類型同樣是一個數(shù)組。唯一的副作用可能就是所有操作都會影響原數(shù)組。

  • 數(shù)組的修改

最簡單場景的修改,不借助任何 API 的方式:

let arr = [1, 2, 3, 4, 5]
// 通過索引下標(biāo)修改元素
arr[0] = 6
console.log(arr) // [6, 2, 3, 4, 5]

使用 splice() 進(jìn)行修改,刪除里面詳細(xì)講解了這個方法,這里就不贅述了!我看掘金的博主大大在修改這個模塊下面整理了 filter() 方法,該方法主要的功能其實(shí)是對數(shù)組進(jìn)行過濾處理,并且該方法不會改變原數(shù)組,而是生成一個新的數(shù)組,所以感覺定義在修改這里是不是有點(diǎn)勉強(qiáng)~~~

  • 數(shù)組的查詢

查詢我們肯定要知道查詢條件是什么,所以其實(shí)數(shù)組查詢一般都是通過遍歷數(shù)組,匹配對應(yīng)的查詢條件來實(shí)現(xiàn)的。其實(shí)這個方法也比較多,例如剛剛說的 filter() 數(shù)組過濾處理,也算是一種簡單查詢;通過 map() 進(jìn)行遍歷匹配也可以算查詢;還有 find()findIndex(),感覺這些 API 名字取得也都比較應(yīng)景;還有 indexOf()lastIndexOf() 等等,這里我們也簡單拿幾個栗子來演示:

indexOf()方法:可返回數(shù)組中某個指定的元素位置,常用語法為:array.indexOf(item,start)

  • item:必須。查找的元素。
  • start:可選的整數(shù)參數(shù)。規(guī)定在數(shù)組中開始檢索的位置。它的合法取值是 0 到 stringObject.length - 1。如省略該參數(shù),則將從字符串的首字符開始檢索。
  • 返回值:Number 類型,元素在數(shù)組中的位置,如果沒有搜索到則返回 -1。
const arr = [1, 2, 3, 4, 5, 1, 3, 4, 2, 5]
// 數(shù)組中有很多個 1,返回的是第一個 1 出現(xiàn)的位置索引
console.log(arr.indexOf(1)) // 0
// 第二個參數(shù)表示從數(shù)組中的第五個元素開始查找,所以索引為 0 的 1 被自動忽略
console.log(arr.indexOf(1, 4)) // 5

lastIndexOf()方法:返回一個指定的元素在數(shù)組中最后出現(xiàn)的位置,從該字符串的后面向前查找。常用語法為:array.lastIndexOf(item,start)

  • item:必須。查找的元素。
  • start:可選的整數(shù)參數(shù)。規(guī)定在字符串中開始檢索的位置。它的合法取值是 0 到 stringObject.length - 1。如省略該參數(shù),則將從字符串的最后一個字符處開始檢索。
  • 返回值:Number 類型,元素在數(shù)組中的位置,返回元素在數(shù)組中最后一次出現(xiàn)的索引位置,如果沒有搜索到則返回 -1
const arr = [1, 2, 3, 4, 5, 1, 3, 4, 2, 5]
// 返回數(shù)組中最后一個 5 所在的位置索引
console.log(arr.lastIndexOf(5)) // 9
console.log(arr.lastIndexOf(9)) // -1
console.log(arr.lastIndexOf(4, 2)) // -1
console.log(arr.lastIndexOf(4, 4)) // 3

有沒有感覺 lastIndexOf(4, 4) 的第二個參數(shù)很奇怪,其實(shí)它的第二個參數(shù)代表數(shù)組中的元素截取點(diǎn),如 arr.lastIndexOf(4, 2) 就是數(shù)組中的前三位里面 [1, 2, 3] 里面有沒有 4 這個元素所以返回 -1,而 arr.lastIndexOf(4, 4) 就是數(shù)組中的前五位里面 [1, 2, 3, 4, 5] 中包含了 4 且它在數(shù)組中的的索引位置為 3 所以返回 3。

includes() 方法:判斷一個數(shù)組是否包含一個指定的值,如果是返回 true,否則false。常用語法:arr.includes(searchElement, fromIndex)

  • searchElement:必須。需要查找的元素值。
  • fromIndex:可選。從該索引處開始查找 searchElement。如果為負(fù)值,則按升序從 array.length + fromIndex 的索引開始搜索。默認(rèn)為 0。
  • 返回值:Boolean 類型,如果找到指定值返回 true,否則返回 false。
const arr = [1, 2, 3, 4, 5, 1, 3, 7, 4, 2, 5]
console.log(arr.includes(2)) // true
// 數(shù)組中前2個元素是否包含2,1為數(shù)組截取的下標(biāo)索引,即會截取[1, 2]在進(jìn)行判定
console.log(arr.includes(2, 1)) // true
// 第二個參數(shù)為負(fù)數(shù)時-2代表截取的數(shù)組長度而不是索引,即會截取[2, 5]來進(jìn)行判定
console.log(arr.includes(2, -2)) // true

當(dāng)然,如果我們打算用 indexOf() 或者 lastIndexOf() 或者 includes() 來對一些復(fù)雜的數(shù)組結(jié)構(gòu)進(jìn)行查找肯定是不行的,就比如下面這個栗子:

const list = [
  { id: 1, name: 'zhangsan' },
  { id: 2, name: 'lisi' },
  { id: 3, name: 'wangwu' }
]

如果我們此時去判斷 list 數(shù)組中是否有 zhangsan,我們首先嘗試用 indexOf() 的方式來驗(yàn)證一下:

list.indexOf('zhangsan') // -1

果然不出所料,這個辦法是不行的!此時我們就需要對數(shù)組進(jìn)行遍歷,然后對每個對象進(jìn)行匹配查找,來看看 find() 這個語義話的方法吧:

const list = [
  { id: 1, name: 'zhangsan' },
  { id: 2, name: 'lisi' },
  { id: 3, name: 'wangwu' }
]
const res = list.find(item => {
  return item.name === 'zhangsan'
})
console.log(res) // {id: 1, name: "zhangsan"}

我們這里先不糾結(jié) find() 的規(guī)則,因?yàn)樗举|(zhì)上是遍歷數(shù)組中的每一項(xiàng)然后去根據(jù)條件匹配,而數(shù)組中的遍歷方式有非常多 API,稍后我們會對這些 API 逐個分析比較其優(yōu)劣勢,這里先只演示查找的用法。上面的栗子中我們根據(jù) name==='zhangsan' 查找對了它所在的元素對象,這里安利一個箭頭函數(shù)返回值的寫法,就是箭頭函數(shù)中如果后面直接跟判斷條件返回的話不需要寫 return,可以直接使用下面這種寫法:

// 一行搞定,直接跟判斷條件這樣寫默認(rèn)直接返回
const res = list.find(item => item.name === 'zhangsan')
console.log(res) // {id: 1, name: "zhangsan"}

再來看看 findIndex() 這個方法,多么語義化的 API 啊,看名字就知道是找對應(yīng)條件元素的索引,直接上栗子:

const list = [
  { id: 1, name: 'zhangsan' },
  { id: 2, name: 'lisi' },
  { id: 3, name: 'wangwu' }
]
const res = list.findIndex(item => item.name == 'wangwu')
// wangwu 在數(shù)組中的第三個元素對象里面,其索引下標(biāo)為 2
console.log(res) // 2

當(dāng)然,正如我們前面所說的,其實(shí)數(shù)組的查詢有很多個 API 可以用,畢竟查詢無非就是遍歷數(shù)組進(jìn)行條件匹配,而數(shù)組遍歷的 API 真的太多了,基礎(chǔ)的查找方法就介紹上面 5 種,接下來讓我們一起認(rèn)識數(shù)組遍歷吧!!!

數(shù)組的遍歷


遍歷不會,學(xué)了也廢~~~感覺 JavaScript 中用的最多的就是遍歷,不管是數(shù)組遍歷還是對象遍歷,基本開發(fā)中各種數(shù)據(jù)的處理都離不開他們,這里我們就羅列一些常用的數(shù)組遍歷的 API,寫一寫,記一記,代碼真容易!!!

find() 方法

返回數(shù)組中滿足提供的測試函數(shù)的第一個元素的值,沒有則返回 undefined。常用語法為:arr.find(callback[, thisArg])。該方法為 ES6 新增方法。

  • callback:在數(shù)組每一項(xiàng)上執(zhí)行的函數(shù),接收三個參數(shù):
    element:必填,當(dāng)前遍歷到的元素
    index:可選,當(dāng)前遍歷到的索引
    array:可選,數(shù)組本身
  • thisArg:可選,執(zhí)行回調(diào)時用作 this 的對象
  • 返回值:數(shù)組中第一個滿足所提供測試函數(shù)的元素的值,沒有則返回 undefinedfind() 方法不會改變原數(shù)組。
// 返回值測試
const array = [5, 12, 8, 130, 44]
console.log(array.find(item => item > 10)) // 12 返回數(shù)組中第一個符合條件的值
// 常用語法測試
const list = [
  { id: 1, name: 'zhangsan' },
  { id: 2, name: 'lisi' },
  { id: 3, name: 'wangwu' }
]
const list1 = [
  { id: 10, name: 'zhaoliu' }
]
function findName(list) {
  return list.find(function (item, index, elem) {
    // item 就是 list 數(shù)組中的每一項(xiàng)
    // index 就是每一項(xiàng)的索引 
    // elem 就是 list 本身
    console.log(item, index, elem)
    console.log(this) // [{ id: 10, name: 'zhaoliu' }]
    console.log(this[0].id) // 10
  }, list1) // list1 就是函數(shù)中的 this 指向,無則使用 undefined
}
findName(list)

活學(xué)活用小測試:尋找數(shù)組中的質(zhì)數(shù),如果找不到質(zhì)數(shù)則返回 undefined。質(zhì)數(shù)是啥......只能被1和自身整除的數(shù)就是質(zhì)數(shù),比如 3,只有 1 和 3 能整除,而 4 有 1、2、4 都能整除,那么 3 就是質(zhì)數(shù),4就是合數(shù)。好吧,原理摸清楚了,該寫代碼了:

const arr = [4, 6, 9]
const arr1 = [4, 11, 6, 8, 9, 10]
function isPrime(item, index, array) {
  let start = 2
  while (Math.sqrt(item) >= start) {
    if (item % start++ < 1) {
      return false
    }
  }
  return item > 1
}
console.log(arr.find(isPrime)) // undefined
console.log(arr1.find(isPrime)) // 11
findIndex() 方法

返回數(shù)組中滿足提供的測試函數(shù)的第一個元素的索引。若沒有找到對應(yīng)元素則返回-1。常用語法為:arr.findIndex(callback[, thisArg])。該方法為 ES6 新增方法。

  • callback:針對數(shù)組中的每個元素, 都會執(zhí)行該回調(diào)函數(shù), 執(zhí)行時會自動傳入下面三個參數(shù):
    element:當(dāng)前函數(shù)
    index:當(dāng)前元素的索引
    array:調(diào)用 findIndex 的數(shù)組
  • thisArg:可選,執(zhí)行回調(diào)時作為 this 對象的值。
  • 返回值:數(shù)組中通過提供測試函數(shù)的第一個元素的索引。否則,返回 -1,findIndex() 不會修改所調(diào)用的數(shù)組。
// 返回值測試
const array = [5, 12, 8, 130, 44]
console.log(array.findIndex(item => item > 10)) // 1 滿足條件的第一個元素為 12,其索引為 1。
// 常用語法測試,基本和 find() 差不多,直接借用過來了
const list = [
  { id: 1, name: 'zhangsan' },
  { id: 2, name: 'lisi' },
  { id: 3, name: 'wangwu' }
]
const list1 = [
  { id: 10, name: 'zhaoliu' }
]
function findName(list) {
  return list.findIndex(function (item, index, elem) {
    // item 就是 list 數(shù)組中的每一項(xiàng)
    // index 就是每一項(xiàng)的索引 
    // elem 就是 list 本身
    console.log(item, index, elem)
    console.log(this) // [{ id: 10, name: 'zhaoliu' }]
    console.log(this[0].id) // 10
  }, list1) // list1 就是函數(shù)中的 this 指向,無則使用 undefined
}
findName(list)

小測試:查找數(shù)組中收個質(zhì)數(shù)元素的索引,如找不到質(zhì)數(shù),則返回-1。

const arr = [4, 6, 9]
const arr1 = [4, 11, 6, 8, 9, 10]
function isPrime(item, index, array) {
  let start = 2
  while (Math.sqrt(item) >= start) {
    if (item % start++ < 1) {
      return false
    }
  }
  return item > 1
}
console.log(arr.findIndex(isPrime)) // -1
console.log(arr1.findIndex(isPrime)) // 1
map() 方法

返回一個新數(shù)組,數(shù)組中的元素為原始數(shù)組元素調(diào)用函數(shù)處理后的值。該方法按照原始數(shù)組元素順序依次處理元素。map() 不會對空數(shù)組進(jìn)行檢測, map() 不會改變原始數(shù)組。常用語法為:arr.map(function(currentValue,index,arr), thisValue)

  • function(currentValue, index,arr):必須。函數(shù),數(shù)組中的每個元素都會執(zhí)行這個函數(shù)
    currentValue:必須。當(dāng)前元素的值
    index:可選。當(dāng)前元素的索引值
    arr:調(diào)用 map 方法的數(shù)組
  • thisValue:可選。對象作為該執(zhí)行回調(diào)時使用,傳遞給函數(shù),用作 "this" 的值。
    如果省略了 thisValue,或者傳入 null、undefined,那么回調(diào)函數(shù)的 this 為全局對象。
  • 返回值:一個由原數(shù)組每個元素執(zhí)行回調(diào)函數(shù)的結(jié)果組成的新數(shù)組。
// 返回值測試
const numbers = [1, 4, 9]
const roots = numbers.map(Math.sqrt)
console.log(roots) // [1, 2, 3]
console.log(numbers) // [1, 4, 9]
// 使用 map 重新格式化數(shù)組中的對象
const kvArray = [{ key: 1, value: 10 },
{ key: 2, value: 20 },
{ key: 3, value: 30 }];
// 其實(shí)只有 obj 有用,只是將所有可選參數(shù)羅列出來
const reformattedArray = kvArray.map((obj, index, array) => {
  let rObj = {}
  rObj[obj.key] = obj.value
  return rObj
})
console.log(reformattedArray) // [{1: 10}, {2: 20}, {3: 30}]

通常情況下,map 方法中的 callback 函數(shù)只需要接受一個參數(shù),就是正在被遍歷的數(shù)組元素本身。但這并不意味著 map 只給 callback 傳了一個參數(shù)。這個思維慣性可能會讓我們犯一個很容易犯的錯誤。參考下例:

['1', '2', '3'].map(parseInt) 返回結(jié)果是什么?

const res = ['1', '2', '3'].map(parseInt)
console.log(res) // [1, NaN, NaN]

我們期望輸出 [1, 2, 3], 而實(shí)際結(jié)果是 [1, NaN, NaN]。我估計(jì)第一眼看都會有點(diǎn)懵,為啥會這樣返回,我們可以將其進(jìn)行拆解:

// 因?yàn)?map 方法本身會返回一個新的數(shù)組,拆解出來后其實(shí)就是分別返回
// parseInt('1', 0) 1
// parseInt('2', 1) NaN
// parseInt('3', 2) NaN
['1', '2', '3'].map((num, index) => {
  return parseInt(num, index)
})

所以其實(shí)這里的問題變成了 parseInt(num, index) 的值返回的是一個什么結(jié)果,我們可以去查一下 parseInt 的用法:

parseInt() 函數(shù)可解析一個字符串,并返回一個整數(shù),常用語法:parseInt(string, radix)

參數(shù) 描述
string 必需。要被解析的字符串。
radix 可選。表示要解析的數(shù)字的基數(shù)。該值介于 2 ~ 36 之間。<br />如果省略該參數(shù)或其值為 0,則數(shù)字將以 10 為基礎(chǔ)來解析。如果它以 “0x” 或 “0X” 開頭,將以 16 為基數(shù)。<br />如果該參數(shù)小于 2 或者大于 36,則 parseInt() 將返回 NaN。

好吧,擴(kuò)展了解一下!!!MDN 上關(guān)于這個問題有更深入的剖析,感興趣的可以去瞅瞅,此理解僅供參考~~~

forEach() 方法

用于調(diào)用數(shù)組的每個元素,并將元素傳遞給回調(diào)函數(shù)。該方法對于空數(shù)組是不會執(zhí)行回調(diào)函數(shù)的。常用語法為:arr.forEach(function(currentValue, index, arr), thisValue)。使用該方法不會改變原數(shù)組。

  • function(currentValue, index,arr):必須。函數(shù),數(shù)組中每個元素需要調(diào)用的函數(shù)
    currentValue:必須。當(dāng)前元素
    index:可選。當(dāng)前元素的索引值
    arr:當(dāng)前元素所屬的數(shù)組對象。
  • thisValue:可選。當(dāng)執(zhí)行回調(diào)函數(shù) callback 時,用作 this 的值。
  • 返回值:undefined
// 返回值測試
const arr = ['a', 'b', 'c']
const res = arr.forEach(item => console.log(item)) // a b c
console.log(res) // undefined

除了拋出異常以外,沒有辦法中止或跳出 forEach() 循環(huán)。如果你需要中止或跳出循環(huán),forEach() 方法不是應(yīng)當(dāng)使用的工具。還有如果數(shù)組中出現(xiàn)空值或者 undefined 時執(zhí)行會被跳過,如下栗子:

function logArrayElements(element, index, array) {
  console.log('a[' + index + '] = ' + element);
}
// 注意索引 2 被跳過了,因?yàn)樵跀?shù)組的這個位置沒有項(xiàng)
[2, 5, , 9].forEach(logArrayElements);
// logs:
// a[0] = 2
// a[1] = 5
// a[3] = 9

延伸思考,通過 forEach() 實(shí)現(xiàn)多維數(shù)組扁平化?

function flatten(arr) {
  let result = []
  arr.forEach(item => {
    if (Array.isArray(item)) { // 判斷遍歷的子元素是否仍是數(shù)組
      // 使用遞歸對子元素進(jìn)行重復(fù)執(zhí)行
      result.push(...flatten(item))
    } else {
      result.push(item)
    }
  })
  return result
}
const arrs = [1, 2, 3, [4, 5, [6, 7], 8, 9]]
const arr = flatten(arrs)
console.log(arr) // [1, 2, 3, 4, 5, 6, 7, 8, 9]

代碼比較簡單,不過最新的 ES2019 語法推出了一個數(shù)組拍平的 API,可以直接拍平數(shù)組,如下栗子:

let arr = [1, 2, 3, [4, 5, 6, [7, 8, 9]], 10].flat()
console.log(arr) //[1, 2, 3, 4, 5, 6, Array(3), 10]

沒有徹底怕平,是 bug 嗎,其實(shí)不是,flat(num)num 可選值默認(rèn)是拍平 1 層,如果是三維數(shù)組可傳入 2 即拍平兩層,依此類推~~~

let arr = [1, 2, 3, [4, 5, 6, [7, 8, 9]], 10].flat(2)
console.log(arr) // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
filter() 方法

創(chuàng)建一個新的數(shù)組,新數(shù)組中的元素是通過檢查指定數(shù)組中符合條件的所有元素。 filter() 不會對空數(shù)組進(jìn)行檢測。filter() 不會改變原始數(shù)組。該方法為 ES6 新增方法。常用語法為:array.filter(function(currentValue,index,arr), thisValue)

  • function(currentValue, index,arr):必須。函數(shù),數(shù)組中的每個元素都會執(zhí)行這個函數(shù)
    currentValue:必須。數(shù)組中當(dāng)前正在處理的元素。
    index:可選。當(dāng)前元素的索引值。
    arr:當(dāng)前元素所屬的數(shù)組對象。
  • thisValue:可選。當(dāng)執(zhí)行回調(diào)函數(shù) callback 時,用作 this 的值。
  • 返回值:一個新的、由通過測試的元素組成的數(shù)組,如果沒有任何數(shù)組元素通過測試,則返回空數(shù)組。。
// 返回值測試
function isBigEnough(item) {
  return item>= 10
}
const arr = [12, 5, 8, 130, 44]
const filterArr = arr.filter(isBigEnough)
console.log(arr) // [12, 5, 8, 130, 44]
console.log(filterArr) // [12, 130, 44]

我們也可以使用 filter() 根據(jù)搜索條件來過濾數(shù)組中的內(nèi)容,如下栗子:

const fruits = ['apple', 'banana', 'grapes', 'mango', 'orange']
function filterItems(query) {
  return fruits.filter(item => {
    // 將所有子項(xiàng)轉(zhuǎn)化成小寫在進(jìn)行比對
    return item.toLowerCase().indexOf(query.toLowerCase()) > -1
  })
}
const filterArr = filterItems('ap')
console.log(filterArr)

小練習(xí):通過 filter() 實(shí)現(xiàn)數(shù)組去重?

function unique(arr) {
  const res = []
  arr.filter(item => {
    if (res.indexOf(item) < 0) {
      res.push(item)
    }
  })
  return res
}
const arr = [10, 20, 30, 20, 50, 40, 30, 60, 20]
const filterArr = unique(arr)
console.log(filterArr) // [10, 20, 30, 50, 40, 60]

知識進(jìn)階:使用 Set 實(shí)現(xiàn)數(shù)組去重~~~

function unique(arr) {
  const set = new Set(arr)
  return [...set]
}
const arr = [10, 20, 30, 20, 50, 40, 30]
const res = unique(arr)
console.log(res) // [10, 20, 30, 50, 40]
every() 方法

測試一個數(shù)組內(nèi)的所有元素是否都能通過某個指定函數(shù)的測試,它返回一個布爾值。如果數(shù)組中檢測到有一個元素不滿足,則整個表達(dá)式返回 false ,且剩余的元素不會再進(jìn)行檢測。如果所有元素都滿足條件,則返回 true。 every() 不會對空數(shù)組進(jìn)行檢測。every() 不會改變原始數(shù)組。該方法為 ES6 新增方法。常用語法為:array.every(function(currentValue,index,arr), thisValue)

  • function(currentValue, index,arr):必須。函數(shù),數(shù)組中的每個元素執(zhí)行一次 callback 函數(shù),直到它找到一個會使 callback 返回 falsy 的元素。如果發(fā)現(xiàn)了一個這樣的元素,every 方法將會立即返回 false 并結(jié)束執(zhí)行。
    currentValue:必須。當(dāng)前元素的值。
    index:可選。當(dāng)前元素的索引值。
    arr:當(dāng)前元素所屬的數(shù)組對象。
  • thisValue:可選。當(dāng)執(zhí)行回調(diào)函數(shù) callback 時,用作 this 的值。
  • 返回值:布爾值。如果所有元素都通過檢測返回 true,否則返回 false。
// 返回值測試
function isBigEnough(element, index, array) {
  return element >= 10;
}
[12, 5, 8, 130, 44].every(isBigEnough);   // false
[12, 54, 18, 130, 44].every(isBigEnough); // true
some() 方法

測試數(shù)組中是不是至少有1個元素通過了被提供的函數(shù)測試。它返回一個布爾值。該方法會依次執(zhí)行數(shù)組的每個元素,如果有一個元素滿足條件,則表達(dá)式返回 true , 剩余的元素不會再執(zhí)行檢測。如果沒有滿足條件的元素,則返回false。some() 不會對空數(shù)組進(jìn)行檢測。some() 不會改變原始數(shù)組。該方法為 ES6 新增方法。常用語法為:array.some(function(currentValue,index,arr),thisValue)

  • function(currentValue, index,arr):必須。函數(shù),數(shù)組中的每個元素執(zhí)行一次 callback 函數(shù),直到它找到一個會使 callback 返回 truthy 的元素。如果發(fā)現(xiàn)了一個這樣的元素,some 方法將會立即返回 true 并結(jié)束執(zhí)行。
    currentValue:必須。當(dāng)前元素的值。
    index:可選。當(dāng)前元素的索引值。
    arr:當(dāng)前元素所屬的數(shù)組對象。
  • thisValue:可選。當(dāng)執(zhí)行回調(diào)函數(shù) callback 時,用作 this 的值。
  • 返回值:布爾值。數(shù)組中有至少一個元素通過回調(diào)函數(shù)的測試就會返回true;所有元素都沒有通過回調(diào)函數(shù)的測試返回值才會為false。
// 檢測在數(shù)組中是否有元素大于 10
function isBiggerThan10(element, index, array) {
  return element > 10;
}

[2, 5, 8, 1, 4].some(isBiggerThan10);  // false
[12, 5, 8, 1, 4].some(isBiggerThan10); // true

有沒有發(fā)現(xiàn) some()every() 很相似,這兩個方法的目的都是為了檢測數(shù)組中的成員是否滿足所給定的條件,只是檢測規(guī)則不一致。

大概總結(jié)了一下,常用的遍歷 API 應(yīng)該就是上面這些.....不知道有沒有遺漏,感覺開發(fā)中主要用到的也就是這些了吧,做個小總結(jié):

find()findIndex() 方法應(yīng)該是對應(yīng)的。find() 返回數(shù)組中滿足提供的測試函數(shù)的第一個元素的值,findIndex() 返回數(shù)組中滿足提供的測試函數(shù)的第一個元素的索引。

map()forEach() 方法應(yīng)該是對應(yīng)的,都是對數(shù)組進(jìn)行循環(huán)遍歷。不同的是 forEach() 返回值為 undefined,而 map() 的返回值是一個由原數(shù)組每個元素執(zhí)行回調(diào)函數(shù)的結(jié)果組成的新數(shù)組。

every()some() 方法應(yīng)該是對應(yīng)的。這兩個方法的目的都是為了檢測數(shù)組中的成員是否滿足所給定的條件,只是 every() 需要數(shù)組中所有元素都滿足條件才返回 truesome() 只需要數(shù)組中一個元素滿足條件就返回 true

其它常用 API


不知不覺感覺大部分 API 已經(jīng)都寫到了,當(dāng)然還有一些非常常用的 API,例如數(shù)組合并、數(shù)組排序等,

concat() 方法

用于合并兩個或多個數(shù)組。此方法不會更改現(xiàn)有數(shù)組,而是返回一個新數(shù)組。常用語法為:array1.concat(array2,array3,...,arrayX)

  • array2, array3, ..., arrayX:必需。該參數(shù)可以是具體的值,也可以是數(shù)組對象。可以是任意多個。
  • 返回值:返回一個新的數(shù)組。
// 基礎(chǔ)應(yīng)用
var num1 = [1, 2, 3],
    num2 = [4, 5, 6],
    num3 = [7, 8, 9];
var nums = num1.concat(num2, num3);
console.log(nums); // [1, 2, 3, 4, 5, 6, 7, 8, 9]

其實(shí)開發(fā)中 concat() 方法用的非常之多,這里延伸拓展用 concat() 進(jìn)行數(shù)組拍平,前面使用 forEach()flat() 都做過數(shù)組拍平,讓我們來看看 concat() 如何實(shí)現(xiàn)的呢~~~

首先使用 concat() 拍平二維數(shù)組,當(dāng)然此方法無法拍平更高維度的數(shù)組,如下栗子:

let arr = [1, 2, 3, 4, [5, 6], 7]
let newArr = [].concat.apply([], arr)
console.log(newArr) // [1, 2, 3, 4, 5, 6, 7]

小伙伴肯定會好奇,說好的用 concat() 來實(shí)現(xiàn)二維數(shù)組拍平,你用個 apply() 搞的我一臉懵逼,也沒看懂為什么~~~如果我們直接使用 concat() 也是可以的,但是需要做如下處理:

let newArr = [].concat(1, 2, 3, 4, [5, 6], 7)
console.log(newArr) // [1, 2, 3, 4, 5, 6, 7]

前面說了,concat() 支持我們傳遞多個值,可以為數(shù)組,也可以直接為值,所以如果我們像上面那樣寫就可以直接得到一個一維數(shù)組,但是 concat() 如果直接合并一個二維數(shù)組得到的結(jié)果其實(shí)還是一個二維數(shù)組:

let arr = [1, 2, 3, 4, [5, 6], 7]
let newArr = [].concat(arr)
console.log(newArr) // [1, 2, 3, 4, [5, 6], 7]

所以這里我們使用 apply() 中轉(zhuǎn)一下,因?yàn)樗旧斫邮艿膫髦捣绞骄褪菙?shù)組,然后內(nèi)部會幫我們進(jìn)行展開,所以最后會得到 [].concat(1, 2, 3, 4, [5, 6], 7) 這種結(jié)構(gòu),即達(dá)到我們的需求。那么我們就可以利用這個特性,加上遞歸來進(jìn)行多維數(shù)組的拍平,如下栗子:

const arr = [1, 2, 3, [4, 5, 6, [7, [8], 9]], 10]
function flatten(arr) {
  const isDeep = arr.some(item => item instanceof Array)
  // 如果徹底拍平,所有子元素都不是數(shù)組,直接返回該數(shù)組
  if (!isDeep) {
    return arr
  }
  // 重復(fù)進(jìn)行拍平
  const res = [].concat.apply([], arr)
  return flatten(res)
}
const newArr = flatten(arr)
console.log(newArr)
reduce() 方法

對數(shù)組中的每個元素執(zhí)行一個由您提供的reducer函數(shù)(升序執(zhí)行),將其結(jié)果匯總為單個返回值。該方法為 ES6 新增方法。常用語法為:array.reduce(function(total, currentValue, currentIndex, arr), initialValue)

  • function(total,currentValue, index,arr):必需。用于執(zhí)行每個數(shù)組元素的函數(shù)。
    total:必需。初始值, 或者計(jì)算結(jié)束后的返回值。
    currentValue:必需。當(dāng)前元素。
    currentIndex:可選。當(dāng)前元素的索引。
    arr:可選。當(dāng)前元素所屬的數(shù)組對象。
  • initialValue:可選。作為第一次調(diào)用 callback函數(shù)時的第一個參數(shù)的值。 如果沒有提供初始值,則將使用數(shù)組中的第一個元素。 在沒有初始值的空數(shù)組上調(diào)用 reduce 將報錯。
  • 返回值:返回計(jì)算結(jié)果。
// 基礎(chǔ)應(yīng)用
const arr = [1, 2, 3, 4, 5]
const res = arr.reduce((total, item) => {
  return total + item
}, 6)
console.log(res) // 21

說實(shí)話,這個方法現(xiàn)在經(jīng)常看到一些地方用到,但是還是有點(diǎn)迷,可能是以前沒用過......感覺 MDN 對于這個方法講的挺詳細(xì)的,大家可以移步過去瞅瞅。這里摘兩個好理解又實(shí)用的栗子:

// 計(jì)算數(shù)組中每個元素出現(xiàn)的次數(shù)
var names = ['Alice', 'Bob', 'Tiff', 'Bruce', 'Alice'];
var countedNames = names.reduce(function (allNames, name) {
  if (name in allNames) {
    allNames[name]++;
  } else {
    allNames[name] = 1;
  }
  return allNames;
}, {});
console.log(names) // { 'Alice': 2, 'Bob': 1, 'Tiff': 1, 'Bruce': 1 }

這里其實(shí)主要用了 initialValue 這個空對象來實(shí)現(xiàn)疊加,其實(shí)疊加的步驟如下:

{}
{'Alice': 1}
{ 'Alice': 1, 'Bob': 1}
{ 'Alice': 1, 'Bob': 1, 'Tiff': 1}
{ 'Alice': 1, 'Bob': 1, 'Tiff': 1, 'Bruce': 1 }
{ 'Alice': 2, 'Bob': 1, 'Tiff': 1, 'Bruce': 1 }

再來看一個小栗子,按屬性對 Object 進(jìn)行分類:

var people = [
  { name: 'Alice', age: 21 },
  { name: 'Max', age: 20 },
  { name: 'Jane', age: 20 }
];
function groupBy(arr, property) {
  return arr.reduce((acc, obj) => {
    let key = obj[property]
    if (!acc[key]) {
      acc[key] = []
    }
    acc[key].push(obj)
    return acc
  }, {})
}
const res = groupBy(people, 'age')
console.log(res)
// { 
//   20: [
//     { name: 'Max', age: 20 }, 
//     { name: 'Jane', age: 20 }
//   ], 
//   21: [{ name: 'Alice', age: 21 }] 
// }

感覺看著代碼理解起來很容易,但是自己思考和實(shí)現(xiàn)思路可能又轉(zhuǎn)不太過來.........不過感覺這個方法確實(shí)挺有用的。

from() 方法

從一個類似數(shù)組或可迭代對象創(chuàng)建一個新的,淺拷貝的數(shù)組實(shí)例。該方法為 ES6 新增方法。常用語法為:Array.from(object, mapFunction, thisValue)

  • Object:必需,要轉(zhuǎn)換為數(shù)組的對象。
  • mapFunction: 可選,數(shù)組中每個元素要調(diào)用的函數(shù)。
  • thisValue:可選,映射函數(shù)(mapFunction)中的 this 對象。
  • 返回值:一個新的數(shù)組實(shí)例。

該方法用的應(yīng)該算比較多的,我感覺日常開發(fā)中可能主要用于將 arguments 轉(zhuǎn)化成數(shù)組。回顧將偽數(shù)組轉(zhuǎn)化為數(shù)組的方式:

// 獲取元素結(jié)合,得到一個偽數(shù)組,有 length 屬性確不能調(diào)用數(shù)組的方法
const img = document.querySelectorAll('img')
console.log(img instanceof Array) // false
// 使用 Array.from
const imgArr = Array.from(img)
console.log(imgArr instanceof Array) // true
// 使用 slice
const imgArr = [].slice.call(img)
console.log(imgArr instanceof Array) // true
// 使用擴(kuò)展運(yùn)算符
const imgArr = [...img]
 console.log(imgArr instanceof Array) // true

再來看下 from() 其它基礎(chǔ)用法:

// 從 String 生成數(shù)組
Array.from('foo') // [ "f", "o", "o" ]
// 數(shù)組去重可以使用 new Set()
const set = new Set(['foo', 'bar', 'baz', 'foo'])
Array.from(set) // [ "foo", "bar", "baz" ]
// 傳入第二個參數(shù) mapFunction
console.log(Array.from([1, 2, 3], x => x + x)) // [2, 4, 6]
toString() 方法

返回一個字符串,表示指定的數(shù)組及其元素。常用語法為:arr.toString()

  • 返回值:一個表示指定的數(shù)組及其元素的字符串。

這個方法比較簡單,但是也比較常用,這里就簡單羅列一下,看個栗子:

const array1 = [1, 2, 'a', '1a']
console.log(array1.toString()) // "1,2,a,1a"
join() 方法

將一個數(shù)組(或一個類數(shù)組對象)的所有元素連接成一個字符串并返回這個字符串。如果數(shù)組只有一個項(xiàng)目,那么將返回該項(xiàng)目而不使用分隔符。常用語法為:arr.join(separator)

  • separator:可選。指定要使用的分隔符。如果省略該參數(shù),則使用逗號作為分隔符。
  • 返回值:一個所有數(shù)組元素連接的字符串。如果 arr.length 為0,則返回空字符串。

這個方法和 toString() 一樣比較簡單,也比較常用,這里也簡單羅列一下,看個栗子:

var a = ['Wind', 'Rain', 'Fire'];
var myVar1 = a.join();      // myVar1的值變?yōu)?Wind,Rain,Fire"
var myVar2 = a.join(', ');  // myVar2的值變?yōu)?Wind, Rain, Fire"
var myVar3 = a.join(' + '); // myVar3的值變?yōu)?Wind + Rain + Fire"
var myVar4 = a.join('');    // myVar4的值變?yōu)?WindRainFire"
reverse() 方法

將數(shù)組中元素的位置顛倒,并返回該數(shù)組。數(shù)組的第一個元素會變成最后一個,數(shù)組的最后一個元素變成第一個。該方法會改變原數(shù)組。常用語法為:arr.reverse()

  • 返回值:顛倒后的數(shù)組。
let arr = [1, 2, 3];
arr.reverse();
console.log(arr); // [3, 2, 1]
sort() 方法

原地算法對數(shù)組的元素進(jìn)行排序,并返回數(shù)組。默認(rèn)排序順序是在將元素轉(zhuǎn)換為字符串,然后比較它們的UTF-16代碼單元值序列時構(gòu)建的。常用語法為:array.sort(sortfunction)

  • sortfunction:可選。規(guī)定排序順序。必須是函數(shù)。
  • 返回值:Array。數(shù)組在原數(shù)組上進(jìn)行排序,不生成副本。
// 注意,如果直接使用 sort() 并不會得到我們想要的排序結(jié)果
const array1 = [1, 30, 4, 21, 100000]
array1.sort()
console.log(array1) // [1, 100000, 21, 30, 4]

如果你打算使用 sort() 直接進(jìn)行數(shù)字排序,可能會大失所望,因?yàn)樗呐判蛞?guī)則并不是按照數(shù)字大小來的.......此時我們可以巧妙使用如下方法:

var numbers = [1, 30, 4, 21, 100000]
// 升序排列 箭頭函數(shù)返回值簡寫
numbers.sort((a, b) => a - b)
console.log(numbers) // [1, 4, 21, 30, 100000]
// 降序排列
numbers.sort((a, b) => b - a)
console.log(numbers) // [100000, 30, 21, 4, 1]

日常開發(fā)中,可能會碰到要求我們使用商品金額排序或者商品庫存排序或者商家距離排序,模擬演示實(shí)現(xiàn)該功能?

const friuts = [
  {
    name: 'apple',
    price: 18.5,
    count: 10,
    juli: 10
  },
  {
    name: 'pear',
    price: 1.5,
    count: 5,
    juli: 20
  },
  {
    name: 'banana',
    price: 20.5,
    count: 20,
    juli: 5
  },
]
// 對象排序的 key 值和是否升序還是降序
function sortExp(key, isAsc) {
  return function (x, y) {
    return (x[key] - y[key]) * (isAsc ? 1 : -1)
  }
}
// 按價格升序
friuts.sort(sortExp('price', true))
console.log(JSON.stringify(friuts))
// 按價格降序
friuts.sort(sortExp('price', false))
console.log(JSON.stringify(friuts))
// 按庫存升序
friuts.sort(sortExp('count', true))
console.log(JSON.stringify(friuts))
// 按庫存降序
friuts.sort(sortExp('count', false))
console.log(JSON.stringify(friuts))
// 按距離升序
friuts.sort(sortExp('juli', true))
console.log(JSON.stringify(friuts))
// 按距離降序
friuts.sort(sortExp('juli', false))
console.log(JSON.stringify(friuts))

當(dāng)然,排序并不只有 sort排序,隨便百度都有 冒泡排序插入排序希爾排序 等等多種排序方法。這里簡單羅列兩種。

  • 冒泡排序

什么叫冒泡?類似于水中冒泡,較大的數(shù)沉下去,較小的數(shù)慢慢冒起來,假設(shè)從小到大,即為較大的數(shù)慢慢往后排,較小的數(shù)慢慢往前排。所以冒泡的實(shí)現(xiàn)原理其實(shí)就是:

數(shù)組中有 n 個數(shù),比較每相鄰兩個數(shù),如果前者大于后者,就把兩個數(shù)交換位置;這樣一來,第一輪就可以選出一個最大的數(shù)放在最后面;那么經(jīng)過 n-1(數(shù)組的 length - 1) 輪,就完成了所有數(shù)的排序。

知道了冒泡的基本原理,那么我們先來摸清楚數(shù)組中相鄰兩個數(shù)的位置交換如何實(shí)現(xiàn),看如下代碼:

// 我們希望 arr 中的 1 和 2 交換位置
const arr = [1, 2]
// 定義中轉(zhuǎn)變量,將第一個值賦給它,第二個值賦給第一個值,在將中轉(zhuǎn)變量賦給第二個值從而完成交換。
let temp = arr[0]
arr[0] = arr[1]
arr[1] = temp
console.log(arr) // [2, 1]

好吧,前奏已經(jīng)鋪墊好,我們先來實(shí)現(xiàn)第一輪,找出數(shù)組中的最大數(shù),并把它放到數(shù)組的最后面。

const arr = [3, 4, 1, 2]
for (let i = 0; i < arr.length - 1; i++) {
  // 如果前一個數(shù)大于后一個數(shù),就交換兩個數(shù)的位置
  if (arr[i] > arr[i + 1]) {
    let temp = arr[i]
    arr[i] = arr[i + 1]
    arr[i + 1] = temp
  }
}
console.log(arr) // [3, 1, 2, 4]

看上面的代碼,我們已經(jīng)成功完成了第一輪,將最大的數(shù)字 4 放在了最后面。那我們直接將這個操作重復(fù) n-1 輪( n 為數(shù)組的長度)。此時我們在上面循環(huán)的外層直接在套一層循環(huán)。

const arr = [3, 4, 1, 2]
// 總共會執(zhí)行的輪數(shù)
for (let j = 0; j < arr.length - 1; j++) {
  // 每一輪前后兩個數(shù)都進(jìn)行比較
  for (let i = 0; i < arr.length - 1; i++) {
    if (arr[i] > arr[i + 1]) {
      let temp = arr[i]
      arr[i] = arr[i + 1]
      arr[i + 1] = temp
    }
  }
}
console.log(arr) // [1, 2, 3, 4]

排序成功,有沒有發(fā)現(xiàn)如果將其拆解,其實(shí)理解起來一點(diǎn)都不復(fù)雜......那么還有沒有啥遺留的問題可以優(yōu)化呢?我們知道在每一輪的比較過程中,我們都已經(jīng)將最大的數(shù)放到了最后,所以我們在內(nèi)層遍歷的時候,因?yàn)樽詈竺嬉粋€數(shù)已經(jīng)是最大的了,我們是不是不用將最后一個數(shù)來進(jìn)行比較。并且我們內(nèi)外兩層循環(huán)都要重復(fù)計(jì)算 arr.length 的長度,但是數(shù)組的長度本身是固定不會改變的,我們是否可以把它抽離到循環(huán)的外面賦值給一個常量從而減少計(jì)算的性能消耗。好的,考慮的不錯,你已經(jīng)是一個優(yōu)秀的 boy 了,于是我們代碼優(yōu)化如下:

const arr = [3, 4, 1, 2, 30, 21, 100000]
// 數(shù)組長度抽離成常量
const length = arr.length - 1
for (let j = 0; j < length; j++) {
  // 這里要根據(jù)外層for循環(huán)的 j,逐漸減少內(nèi)層 for循環(huán)的次數(shù)
  for (let i = 0; i < length - j; i++) {
    if (arr[i] > arr[i + 1]) {
      let temp = arr[i]
      arr[i] = arr[i + 1]
      arr[i + 1] = temp
    }
  }
}
console.log(arr) // [1, 2, 3, 4, 21, 30, 100000]

冒泡排序比較穩(wěn)定簡單,但是因?yàn)閮蓪忧短籽h(huán),所以一般處理的數(shù)據(jù)量不宜過大。如果遇到大量數(shù)據(jù)的排序可以選擇快速排序。

  • 快速排序

感覺快速排序可以看 阮一峰老師的快排js實(shí)現(xiàn) ,講的還是很明白的,主要思想為一下三步:

1、選擇數(shù)組中間數(shù)作為基數(shù),并從數(shù)組中取出此基數(shù);
2、準(zhǔn)備兩個數(shù)組容器,遍歷數(shù)組,逐個與基數(shù)比對,較小的放左邊容器,較大的放右邊容器;
3、遞歸處理兩個容器的元素,并將處理后的數(shù)據(jù)與基數(shù)按大小合并成一個數(shù)組,返回。

整體實(shí)現(xiàn)代碼栗子:

const arr = [2, 4, 3, 4, 6, 3, 2, 5, 6, 2, 3, 6, 5, 4]
function quickSort(arr) {
  if (arr.length <= 1) {
    return arr
  }
  // 基準(zhǔn)位置,理論上可以任意選取
  let pivotIndex = Math.floor(arr.length / 2)
  // 基準(zhǔn)值
  let pivot = arr.splice(pivotIndex, 1)[0]
  let left = []
  let right = []
  for (let i = 0; i < arr.length; i++) {
    if (arr[i] < pivot) {
      left.push(arr[i])
    } else {
      right.push(arr[i])
    }
  }
  // 使用遞歸不斷重復(fù)這個過程,就可以得到排序后的數(shù)組。
  return quickSort(left).concat([pivot], quickSort(right))
}
const res = quickSort(arr)
console.log(res)

通過阮老師對快排的文章,確實(shí)簡單易懂,感覺大佬果然是大佬,膜拜一下~~~當(dāng)然面試中如果碰到排序要求感覺還是可以使用冒泡排序和 sort() 排序,這兩個比較簡單,也容易理解......更多算法練習(xí)可以參考 leedcode

感覺這篇文章真是大雜燴,整理了好幾天,中間還休了一個八天的十一假期,出去浪了~~~寫到這里感覺也差不多可以停了,后續(xù)遇到相關(guān)數(shù)組問題在進(jìn)行補(bǔ)充。如果文中有不對的地方或者理解有誤的地方歡迎大家提出并指正。每一天都要相對前一天進(jìn)步一點(diǎn),加油!!!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。