【建議收藏】徒手實現24+陣列方法,誰說你只是“會用”陣列

前端胖頭魚發表於2021-09-30

前言

陣列是我們日常工作中用的最頻繁的一類資料結構,能幫助我們解決許多問題,而其本身也包含接近33個之多的方法,做了一個腦圖分類如下,熟練使用陣列的你,是否想知道他們內部的實現原理呢?

這篇文章會和你一起探究24+原生陣列方法的內部實現,相信你看完一定會有屬於自己不一樣的收穫。

陣列方法.png

遍歷類

1. forEach

基本使用

forEach一個日常用的非常多的遍歷函式,你一定熟悉到不能再熟悉啦!這裡我們著重看一些比較重要且容易忽略的點。mdn
  1. 該方法對陣列的每個元素執行一次給定的函式,返回值是undefiend
  2. 該方法按升序為陣列中含有效值的每一項執行一次 callback 函式,未初始化的項將被跳過(例如在稀疏陣列上)。
  3. 如果已經存在的值被改變,則傳遞給 callback 的值是 forEach() 遍歷到他們那一刻的值。
  4. 已刪除的項不會被遍歷到

舉個小例子



let demoArr = [ 1, 2, 3, 4, , 5 ]

demoArr.forEach((it, i) => {
  if (i === 1) {
    // 後新增進去的不會被訪問到
    demoArr.push(5)
  } else if (i === 2) {
    // 4將不會被訪問到,而4-4會被訪問到
    demoArr.splice(3, 1, '4-4')
  }

  console.log(it)
})

/*
 1
 2
 3
 4-4
 5
*/

程式碼實現

點選檢視原始碼實現

Array.prototype.forEach2 = function (callback, thisCtx) {
  if (typeof callback !== 'function') {
    throw `${callback} is not a function`
  }

  const length = this.length
  let i = 0

  while (i < length) {
    // 被刪除的,新增的元素索引i不在陣列內,所以不會被訪問到
    if (i in this) {
      callback.call(thisCtx, this[ i ], i, this)
    }

    i++
  }
}

同樣用剛才的例子,改下後的輸出是一樣的

測試一把


// 測試
let demoArr = [ 1, 2, 3, 4, , 5 ]

demoArr.forEach2((it, i) => {
  if (i === 1) {
    // 後新增進去的不會被訪問到
    demoArr.push(5)
  } else if (i === 2) {
    // 4將不會被訪問到,相仿4-4會被訪問到
    demoArr.splice(3, 1, '4-4')
  }

  console.log(it)
})

/*
 1
 2
 3
 4-4
 5
*/

2. map

基本使用

map 方法建立一個新陣列,其結果是該陣列中的每個元素是呼叫一次提供的函式後的返回值。mdn

注意點

  1. callback 函式只會在有值的索引上被呼叫
  2. 從來沒被賦過值或者使用 delete 刪除的索引則不會被呼叫。
// 注意索引為2的位置沒有賦值
let arr = [ 1, 2, ,4, 5 ]

// 刪除索引3
delete arr[3]

console.log(arr.map((it) => it * it))
// [ 1, 4, 25 ]

程式碼實現

點選檢視原始碼實現

Array.prototype.map2 = function (callback, thisCtx) {
  if (typeof callback !== 'function') {
    throw `${callback} is not a function`
  }

  const length = this.length
  let i = 0
  // map返回值是一個新的陣列
  let newArray = []

  while (i < length) {
    // 被刪除的,未初始化的都不會被遍歷到
    if (i in this) {
      newArray.push(callback.call(thisCtx, this[ i ], i, this))
    }

    i++
  }
  // 返回新的陣列
  return newArray
}

測試一把

let arr = [ 0, 1, 2, 3, 4,, 5 ]

let arr2 = arr.map2(function (it, i, array) {
  console.log(it, i, array, this)
  return it * it
}, { name: '前端胖頭魚' })

console.log(arr2)

image.png

3. every

基本使用

every 方法測試一個陣列內的所有元素是否都能通過某個指定函式的測試。它返回一個布林值。mdn

注意點

  1. 若收到一個空陣列,此方法在一切情況下都會返回 true
  2. callback 只會為那些已經被賦值的索引呼叫
  3. 不會為那些被刪除或從未被賦值的索引呼叫
// 舉例
let emptyArr = []
// 空陣列直接返回true
console.log(emptyArr.every((it) => it > 0)) // true
// 有未被賦值的
let arr = [ 0, 1, 2, 3, 4,, 5, -1 ]
// 刪除元素
delete arr[7]

console.log(arr.every((it) => it >= 0)) // true

程式碼實現

點選檢視原始碼實現


Array.prototype.every2 = function (callback, thisCtx) {
  if (typeof callback !== 'function') {
    throw `${callback} is not a function`
  }

  const length = this.length
  let i = 0
  // 空函式不會走進迴圈
  while (i < length) {
    // 只要有一個值不符合callback預期就返回false
    if (i in this && !callback.call(thisCtx, this[ i ], i, this)) {
      return false
    }

    i++
  }

  return true
}

測試一把

還是拿例子做測試


let emptyArr = []

console.log(emptyArr.every2((it) => it > 0)) // true

let arr = [ 0, 1, 2, 3, 4,, 5, -1 ]

delete arr[7]

console.log(arr.every2((it) => it >= 0)) // true

4. some

基本使用

some 方法測試陣列中是不是至少有1個元素通過了被提供的函式測試。它返回的是一個Boolean型別的值。 mdn

注意點

  1. callback 只會在那些”有值“的索引上被呼叫,不會在那些被刪除或從來未被賦值的索引上呼叫。

舉個例子


let emptyArr = []
// 空陣列直接返回false
console.log(emptyArr.some((it) => it > 0)) // false
let arr = [ 0, 1, 2, 3, 4,, 5, -1 ]
// 還沒有遍歷前把-1刪除了,唯一小於0的值不存在了,即返回false
delete arr[7]

console.log(arr.some((it) => it < 0)) // false

程式碼實現

點選檢視原始碼實現

Array.prototype.some2 = function (callback, thisCtx) {
  if (typeof callback !== 'function') {
    throw `${callback} is not a function`
  }

  const length = this.length
  let i = 0

  while (i < length) {
    // 只要有一個元素符合callback條件,就返回true
    if (i in this && callback.call(thisCtx, this[ i ], i, this)) {
      return true
    }

    i++
  }

  return false
}

測試一把


let emptyArr = []
// 空陣列直接返回true
console.log(emptyArr.some2((it) => it > 0)) // false
let arr = [ 0, 1, 2, 3, 4,, 5, -1 ]

delete arr[7]

console.log(arr.some2((it) => it < 0)) // false
console.log(arr.some2((it) => it > 0)) // true

5. filter

基本使用

filter 方法建立一個新陣列, 其包含通過所提供函式測試的所有元素。 mdn

注意點

  1. filter 為陣列中的每個元素呼叫一次 callback 函式,並利用所有使得 callback 返回 true 或等價於 true 的值的元素建立一個新陣列。
  2. callback 只會在已經賦值的索引上被呼叫,對於那些已經被刪除或者從未被賦值的索引不會被呼叫。
  3. 那些沒有通過 callback 測試的元素會被跳過,不會被包含在新陣列中。
// 索引為5的位置,沒有初始化值,不會被遍歷
let arr = [ 0, 1, 2, -3, 4,, 5 ]
// 刪除掉最後一個元素
delete arr[6]
// 過濾出大於0的值
let filterArr = arr.filter((it) => it > 0)

console.log(filterArr) // [ 1, 2, 4 ]

程式碼實現

點選檢視原始碼實現

Array.prototype.filter2 = function (callback, thisCtx) {
  if (typeof callback !== 'function') {
    throw `${callback} is not a function`
  }

  const length = this.length
  let newArray = []
  let i = 0

  while (i < length) {
    if (i in this && callback.call(thisCtx, this[ i ], i, this)) {
      newArray.push(this[ i ])
    }
    i++
  }

  return newArray
}

測試


// 索引為5的位置,沒有初始化值,不會被遍歷
let arr = [ 0, 1, 2, -3, 4,, 5 ]
// 刪除掉最後一個元素
delete arr[6]
// 過濾出大於0的值
let filterArr = arr.filter2((it) => it > 0)

console.log(filterArr) // [ 1, 2, 4 ]

6. reduce

基本使用

reduce 方法對陣列中的每個元素執行一個由您提供的reducer函式(升序執行),將其結果彙總為單個返回值 mdn

這個函式稍微複雜一些,我們用一個例子來看一下他是怎麼用的。


const sum = [1, 2, 3, 4].reduce((prev, cur) => {
  return prev + cur;
})

console.log(sum) // 10

// 初始設定
prev = initialValue = 1, cur = 2

// 第一次迭代
prev = (1 + 2) =  3, cur = 3

// 第二次迭代
prev = (3 + 3) =  6, cur = 4

// 第三次迭代
prev = (6 + 4) =  10, cur = undefined (退出)

程式碼實現

點選檢視原始碼實現

Array.prototype.reduce2 = function (callback, initValue) {
  if (typeof callback !== 'function') {
    throw `${callback} is not a function`
  }

  let pre = initValue
  let i = 0
  const length = this.length
  // 當沒有傳遞初始值時,取第一個作為初始值  
  if (typeof pre === 'undefined') {
    pre = this[0]
    i = 1
  }

  while (i < length) {
    if (i in this) {
      pre = callback(pre, this[ i ], i, this)
    }
    i++
  }

  return pre
}

測試一把

const sum = [1, 2, 3, 4].reduce2((prev, cur) => {
  return prev + cur;
})

console.log(sum) // 10

7. reduceRight

基本使用

reduceRight 方法對陣列中的每個元素執行一個由您提供的reducer函式(降序執行),將其結果彙總為單個返回值 mdn

和reduce很類似,唯一不同的是reduceRight從右往左遍歷

const sum = [1, 2, 3, 4].reduce((prev, cur) => {
  console.log(cur)
  return prev + cur;
})

// 2 1
// 3 2
// 4 3

console.log(sum) // 10

const sum2 = [1, 2, 3, 4].reduceRight((prev, cur) => {
  console.log(cur)
  return prev + cur;
})
// 3 2 
// 2 1
// 1 0

console.log(sum2) // 10

程式碼實現

點選檢視原始碼實現

Array.prototype.reduceRight2 = function (callback, initValue) {
  if (typeof callback !== 'function') {
    throw `${callback} is not a function`
  }

  let pre = initValue
  const length = this.length
  // 從最後一個元素開始遍歷
  let i = length - 1
  // 如果沒有傳遞初始值,則取最後一個作為初始值
  if (typeof pre === 'undefined') {
    pre = this[i]
    i--
  }

  while (i >= 0) {
    if (i in this) {
      pre = callback(pre, this[ i ], i, this)
    }
    i--
  }

  return pre
}

測試一把

const sum = [1, 2, 3, 4].reduceRight2((prev, cur) => {
  console.log(cur)
  return prev + cur;
})

// 3 2
// 2 1
// 1 0

console.log(sum) // 10

查詢類

8. find

基本使用

find 方法返回陣列中滿足測試函式的第一個元素的值。否則返回 undefined, mdn

注意點

  1. find方法對陣列中的每一項元素執行一次 callback 函式,直至有一個 callback 返回 true
  2. 當找到了這樣一個元素後,該方法會立即返回這個元素的值,否則返回 undefined
  3. callback 函式會為陣列中的每個索引呼叫即從 到 length - 1,而不僅僅是那些被賦值的索引。(這個點是和前面幾個函式不一樣的地方)
let arr = [ 0, 1, 2, 3, 4,, 5 ]

let index = arr.find((it) =>  {
  return it > 3
}, { name: '前端胖頭魚' })

console.log(index) // 4

程式碼實現

點選檢視原始碼實現


Array.prototype.find2 = function (callback, thisCtx) {
  if (typeof callback !== 'function') {
    throw `${callback} is not a function`
  }

  const length = this.length
  let i = 0

  while (i < length) {
    const value = this[ i ]
    // 只要有一個元素符合callback回撥函式的邏輯,就返回元素value
    if (callback.call(thisCtx, value, i, this)) {
      return value
    }

    i++
  }
  // 否則返回undefined  
  return undefined
}

測試一把


let arr = [ 0, 1, 2, 3, 4,, 5 ]

let index = arr.find2(function (it, i, array) {
  console.log(it, i, array, this)
  return it > 3
}, { name: '前端胖頭魚' })

console.log(index) // 4

image.png

9. findIndex

基本使用

findIndex方法返回陣列中滿足提供的測試函式的第一個元素的索引。若沒有找到對應元素則返回-1。 mdn

和find函式不同的地方在於,findIndex是返回索引而非值, 注意點也和find基本一樣

  1. findIndex方法對陣列中的每個陣列索引0 ~ length-1(包括)執行一次callback函式,直到找到一個callback函式返回true的值。
  2. 如果找到這樣的元素,findIndex會立即返回該元素的索引。如果回撥從不返回真值,或者陣列的length為0,則findIndex返回-1
  3. 與某些其他陣列方法(如Array#some)不同,在稀疏陣列中,即使對於陣列中不存在的條目的索引也會呼叫回撥函式
let arr = [ 0, 1, 2, 3, 4,, 5 ]

let index = arr.findIndex((it, i, array) => {
  return it > 2
})

console.log(index) // 3

程式碼實現

點選檢視原始碼實現

Array.prototype.findIndex2 = function (callback, thisCtx) {
  if (typeof callback !== 'function') {
    throw `${callback} is not a function`
  }

  const length = this.length
  let i = 0

  while (i < length) {
    // 符合callback邏輯的直接返回索引i
    if (callback.call(thisCtx, this[ i ], i, this)) {
      return i
    }

    i++
  }
  // 否則返回-1  
  return -1
}

測試一把

let arr = [ 0, 1, 2, 3, 4,, 5 ]

let index = arr.findIndex2(function (it, i, array) {
  console.log(it, i, array, this)
  return it > 2
}, { name: '前端胖頭魚' })

console.log(index) // 3

image.png

10. indexOf

基本使用

indexOf方法返回在陣列中可以找到一個給定元素的第一個索引,如果不存在,則返回-1。 mdn
arr.indexOf(searchElement[, fromIndex])

注意點

  1. 如果開始查詢的索引值大於或等於陣列長度,意味著不會在陣列裡查詢,返回-1
  2. 如果引數中提供的索引值是一個負值,則將其作為陣列末尾的一個抵消,即-1表示從最後一個元素開始查詢,-2表示從倒數第二個元素開始查詢 ,以此類推
  3. 如果引數中提供的索引值是一個負值,並不改變其查詢順序,查詢順序仍然是從前向後查詢陣列
  4. 如果抵消後的索引值仍小於0,則整個陣列都將會被查詢。其預設值為0.
const array = [2, 5, 9]

console.log(array.indexOf(2))      // 0
console.log(array.indexOf(7))      // -1
console.log(array.indexOf(9, 2))   // 2
console.log(array.indexOf(2, -1))  // -1
console.log(array.indexOf(2, -3))  // 0

程式碼實現

點選檢視原始碼實現

有了上面的注意點和基本你使用,聰明的你肯定一眼就知道怎麼寫啦

Array.prototype.indexOf2 = function (targetEle, fromIndex) {
  const length = this.length

  fromIndex = +fromIndex || 0

  // 陣列為空或者從大於等於陣列長度的地方開始檢索,都直接是-1
  if (length === 0 || fromIndex >= length) {
    return -1
  }
  /*
    1. 從fromIndex開始搜尋元素
    2. fromIndex大於0時候直接取即可
    3. 小於0先用長度減去fromIndex的絕對值,如果還是小於0,就直接取0即可
  */
  let i = Math.max(fromIndex >= 0 ? fromIndex : length - Math.abs(fromIndex), 0)

  while (i < length) {
    // 在陣列內的元素並且和targetEle強等
    if (i in this && targetEle === this[ i ]) {
      return i
    }

    i++
  }

  return -1
}

測試一把


const array = [2, 5, 9]

console.log(array.indexOf2(2))      // 0
console.log(array.indexOf2(7))      // -1
console.log(array.indexOf2(9, 2))   // 2
console.log(array.indexOf2(2, -1))  // -1
console.log(array.indexOf2(2, -3))  // 0

11. lastIndexOf

基本使用

lastIndexOf 方法返回指定元素在陣列中的最後一個的索引,如果不存在則返回 -1。 mdn
arr.lastIndexOf(searchElement[, fromIndex])

注意點

  1. arr.length - 1位置開始逆向查詢。
  2. 如果fromIndex大於或等於陣列的長度,則整個陣列會被查詢。
  3. 如果fromIndex為負值,將其視為從陣列末尾向前的偏移。即使該值為負,陣列仍然會被從後向前查詢。
  4. 如果fromIndex值為負時,其絕對值大於陣列長度,則方法返回 -1,即陣列不會被查詢。
let array = [2, 5, 9, 2]

console.log(array.lastIndexOf(2)) // 3
console.log(array.lastIndexOf(7)) // -1
console.log(array.lastIndexOf(2, 3)) // 3
console.log(array.lastIndexOf(2, 2)) // 0
console.log(array.lastIndexOf(2, -2)) // 0
console.log(array.lastIndexOf(2, -1)) // 3

程式碼實現

點選檢視原始碼實現

Array.prototype.lastIndexOf2 = function (targetEle, fromIndex) {
  const length = this.length

  fromIndex = typeof fromIndex === 'undefined' ? length - 1 : fromIndex
  // 陣列為空,以及該值為負時且絕對值大於陣列長度,則方法返回 -1,即陣列不會被查詢。
  if (length === 0 || fromIndex < 0 && Math.abs(fromIndex) >= length) {
    return -1
  }

  let i

  if (fromIndex >= 0) {
    // 如果`fromIndex`大於或等於陣列的長度,則整個陣列會被查詢。
    // 也就是當大於陣列length - 1時,會取length - 1
    i = Math.min(fromIndex, length - 1)
  } else {
    i = length - Math.abs(fromIndex)
  }

  while (i >= 0) {
    // 等於targetEle時返回索引
    if (i in this && targetEle === this[ i ]) {
      return i
    }
    // 逆向遍歷
    i--
  }
  // 沒找到返回-1
  return -1
}

測試一把


let array = [2, 5, 9, 2]

console.log(array.lastIndexOf2(2)) // 3
console.log(array.lastIndexOf2(7)) // -1
console.log(array.lastIndexOf2(2, 3)) // 3
console.log(array.lastIndexOf2(2, 2)) // 0
console.log(array.lastIndexOf2(2, -2)) // 0
console.log(array.lastIndexOf2(2, -1)) // 3

12. includes

基本使用

includes 方法用來判斷一個陣列是否包含一個指定的值,如果包含則返回 true,否則返回false。mdn
arr.includes(valueToFind[, fromIndex])

注意點

  1. fromIndex 索引處開始查詢 valueToFind
  2. 如果為負值,則按升序從 array.length + fromIndex 的索引開始搜
  3. 陣列中存在NaN的話,[ ..., NaN ].includes(NaN)為true

console.log([1, 2, 3].includes(2))     // true
console.log([1, 2, 3].includes(4))     // false
console.log([1, 2, 3].includes(3, 3))  // false
console.log([1, 2, 3].includes(3, -1)) // true
console.log([1, 2, NaN].includes(NaN)) // true

程式碼實現

點選檢視原始碼實現

Array.prototype.includes2 = function (targetEle, fromIndex) {
  const length = this.length

  fromIndex = +fromIndex || 0

  // 陣列為空或者從大於等於陣列長度的地方開始檢索,都直接是-1
  if (length === 0 || fromIndex >= length) {
    return false
  }
  /*
    1. 從fromIndex開始搜尋元素
    2. fromIndex大於0時候直接取即可
    3. 小於0先用長度減去fromIndex的絕對值,如果還是小於0,就直接取0即可
  */
  let i = Math.max(fromIndex >= 0 ? fromIndex : length - Math.abs(fromIndex), 0)

  while (i < length) {
    const value = this[ i ]
    // 注意NaN情況
    if (targetEle === value || typeof targetEle === 'number' && typeof value === 'number' && isNaN(targetEle) && isNaN(value)) {
      return true
    }

    i++
  }

  return false
}

測試一把


console.log([1, 2, 3].includes2(2))     // true
console.log([1, 2, 3].includes2(4))     // false
console.log([1, 2, 3].includes2(3, 3))  // false
console.log([1, 2, 3].includes2(3, -1)) // true
console.log([1, 2, NaN].includes2(NaN)) // true

增刪改類

13. push

基本使用

push 方法將一個或多個元素新增到陣列的末尾,並返回該陣列的新長度。mdn
const animals = ['pigs', 'goats', 'sheep']
animals.push('cows')

console.log(animals, animals.length) 
// ["pigs", "goats", "sheep", "cows"], 4

animals.push('chickens', 'cats', 'dogs')

console.log(animals, animals.length) 

// ["pigs", "goats", "sheep", "cows", "chickens", "cats", "dogs"], 7

程式碼實現

點選檢視原始碼實現


Array.prototype.push2 = function (...pushEles) {
  const pushEleLength = pushEles.length
  const length = this.length

  let i = 0
  
  while (i < pushEleLength) {
    this[ length + i ] = pushEles[ i ]
    i++
  }

  return this.length
}

測試一把

const animals = ['pigs', 'goats', 'sheep']
animals.push2('cows')

console.log(animals, animals.length) 
// ["pigs", "goats", "sheep", "cows"], 4

animals.push2('chickens', 'cats', 'dogs')

console.log(animals, animals.length) 

// ["pigs", "goats", "sheep", "cows", "chickens", "cats", "dogs"], 7

14. pop

基本使用

pop方法從陣列中刪除最後一個元素,並返回該元素的值。此方法更改陣列的長度。mdn
let arr = [ 1, 2 ]
let arr2 = []

console.log(arr.pop(), arr) // 2 [1]
console.log(arr2.pop(), arr2) // undefined []

程式碼實現和使用一樣簡單,只要把陣列的最後一個元素返回,並且讓陣列長度減1即可

程式碼實現

點選檢視原始碼實現

Array.prototype.pop2 = function () {
  const length = this.length
  // 空陣列上pop,直接返回undefined
  if (length === 0) {
    return undefined
  }

  const delEle = this[ length - 1 ]

  this.length = length - 1

  return delEle
}

測試一把

let arr = [ 1, 2 ]
let arr2 = []

console.log(arr.pop2(), arr) // 2 [1]
console.log(arr2.pop2(), arr2) // undefined []

15. unshift

基本使用

unshift  方法將一個或多個元素新增到陣列的開頭,並返回該陣列的新長度(該方法修改原有陣列 )

注意點

  1. 如果傳入多個引數,它們會被以塊的形式插入到物件的開始位置,它們的順序和被作為引數傳入時的順序一致。
  2. 傳入多個引數呼叫一次 unshift ,和傳入一個引數呼叫多次 unshift (例如,迴圈呼叫),它們將得到不同的結果。例如:
let arr = [4,5,6]
// 一次性插入
arr.unshift(1,2,3)

console.log(arr) // [1, 2, 3, 4, 5, 6]

let arr2 = [4,5,6]
// 插入多次
arr2.unshift(1)
arr2.unshift(2)
arr2.unshift(3)

console.log(arr2); // [3, 2, 1, 4, 5, 6]

程式碼實現

點選檢視原始碼實現

Array.prototype.unshift2 = function (...unshiftEles) {
  // 藉助擴充套件符,將需要新增的元素以塊的形式插入到陣列前面
  let newArray = [ ...unshiftEles, ...this ]
  let length = newArray.length
  
  let i = 0

  if (unshiftEles.length === 0) {
    return length
  }
  // 重新複製給陣列
  while (i < length) {
    this[ i ] = newArray[ i ]
    i++
  }
  
  return this.length
}

測試一把


let arr = [4,5,6]
// 一次性插入
arr.unshift2(1,2,3)

console.log(arr) // [1, 2, 3, 4, 5, 6]

let arr2 = [4,5,6]
// 插入多次
arr2.unshift2(1)
arr2.unshift2(2)
arr2.unshift2(3)

console.log(arr2); // [3, 2, 1, 4, 5, 6]

16. shift

基本使用

shift 方法從陣列中刪除第一個元素,並返回該元素的值。 mdn

let arr = [ 1, 2 ]

console.log(arr.shift(), arr) // 1 [2]
console.log(arr.shift(), arr) // 2 []

程式碼實現

點選檢視原始碼實現

Array.prototype.shift2 = function () {
  const length = this.length
  const delValue = this[ 0 ]

  let i = 1

  while (i < length) {
    // 從第一個元素開始,後面的元素都往前移動一位
    this[ i - 1 ] = this[ i ]
    i++
  }
  // 設定好陣列的長度
  this.length = length - 1
  // 返回刪除的值
  return delValue
}

測試一把


let arr = [ 1, 2 ]

console.log(arr.shift2(), arr) // 1 [2]
console.log(arr.shift2(), arr) // 2 []

17. reverse

基本使用

reverse 方法將陣列中元素的位置顛倒,並返回該陣列。即陣列的第一個元素會變成最後一個,陣列的最後一個元素變成第一個。mdn
const arr = [1, 2, 3]

console.log(arr) // [1, 2, 3]

arr.reverse()

console.log(arr) // [3, 2, 1]

程式碼實現

點選檢視原始碼實現

Array.prototype.reverse2 = function () {
  // 設定雙指標,往中間靠攏
  let i = 0
  let j = this.length - 1

  while (i < j) {
    // 第一個和最後一個,第二個和倒數第二個進行位置調換
    [ this[ i ], this[ j ] ] = [ this[ j ], this[ i ] ]
    i++
    j--
  }

  return this
}

測試一把


const arr = [1, 2, 3]

console.log(arr) // [1, 2, 3]

arr.reverse2()

console.log(arr) // [3, 2, 1]

18. fill

基本使用

fill  方法用一個固定值填充一個陣列中從起始索引到終止索引內的全部元素。不包括終止索引。mdn

const array1 = [1, 2, 3, 4];

console.log(array1.fill(0, 2, 4)) // [1, 2, 0, 0]


console.log(array1.fill(5, 1)) // [1, 5, 5, 5]

console.log(array1.fill(6)) // [6, 6, 6, 6]

程式碼實現

點選檢視原始碼實現

Array.prototype.fill2 = function (value, start, end) {
  const length = this.length

  start = start >> 0
  // end沒填的話,預設是length,否則取填寫的 
  end = typeof end === 'undefined' ? length : end >> 0
  // start最小取0,最大取length
  start = start >= 0 ? Math.min(start, length) : Math.max(start + length, 0)
  // end最小取0,最大取length 
  end = end >= 0 ? Math.min(end, length) : Math.max(end + length, 0)
  // 填充指定範圍的索引為value
  while (start < end) {
    this[ start ] = value
    start++
  }
  // 返回被修改的陣列
  return this
}

測試一把


const array1 = [1, 2, 3, 4];

console.log(array1.fill2(0, 2, 4)) // [1, 2, 0, 0]


console.log(array1.fill2(5, 1)) // [1, 5, 5, 5]

console.log(array1.fill2(6)) // [6, 6, 6, 6]

連線、拼接

19. concat

基本使用

concat 方法用於合併兩個或多個陣列。此方法不會更改現有陣列,而是返回一個新陣列 mdn


let num1 = [[1]]
let num2 = [2, [3]]
let num3=[5,[6]]

let nums = num1.concat(num2) // [[1], 2, [3]]
let nums2 = num1.concat(4, num3) // [[1], 4, 5,[6]]

程式碼實現

點選檢視原始碼實現

Array.prototype.concat2 = function (...concatEles) {
  const length = concatEles.length
  // 陣列本身展開一層
  let newArray = [ ...this ]
  let i = 0

  while (i < length) {
    const value = concatEles[ i ]
    // 對陣列元素展開一層 
    Array.isArray(value) ? newArray.push(...value) : newArray.push(value)
    i++
  }

  return newArray
}

測試一把


let num1 = [[1]]
let num2 = [2, [3]]
let num3=[5,[6]]

let nums = num1.concat2(num2) // [[1], 2, [3]]
let nums2 = num1.concat2(4, num3) // [[1], 4, 5,[6]]

20. join

基本使用

join 方法將一個陣列的所有元素通過字元標識連線成一個字串並返回這個字串。如果陣列只有一個專案,那麼將返回該專案而不使用分隔符。
const elements = ['Fire', 'Air', 'Water']
const elements2 = ['Fire']

console.log(elements.join()) // Fire,Air,Water
console.log(elements.join('')) // FireAirWater
console.log(elements.join('-')) //  Fire-Air-Water
console.log(elements2.join('-')) // Fire

程式碼實現

點選檢視原始碼實現

Array.prototype.join2 = function (format = ',') {
  const length = this.length
  // 儲存最後一個元素,因為他不參與format連線 
  let lastEle = this[ length - 1 ]
  let string = ''

  if (length === 0) {
    return string
  }

  for (i = 0; i < length - 1; i++) {
    string += this[ i ] + format
  }

  return string + lastEle
}

測試一把

const elements = ['Fire', 'Air', 'Water']
const elements2 = ['Fire']

console.log(elements.join2()) // Fire,Air,Water
console.log(elements.join2('')) // FireAirWater
console.log(elements.join2('-')) //  Fire-Air-Water
console.log(elements2.join2('-')) // Fire

靜態方法

21. Array.isArray

基本使用

Array.isArray() 用於確定傳遞的值是否是一個 Array

Array.isArray([1, 2, 3]) // true

Array.isArray({foo: 123}) // false

Array.isArray("foobar") // false

Array.isArray(undefined) // false

程式碼實現

點選檢視原始碼實現

這個非常簡單,只需要一句話就可以

Array.isArray2 = function (ele) {
  return  Object.prototype.toString.call(ele) === '[object Array]';
}

測試一把


Array.isArray2([1, 2, 3]) // true

Array.isArray2({foo: 123}) // false

Array.isArray2("foobar") // false

Array.isArray2(undefined) // false

22. Array.of

基本使用

Array.of 方法建立一個具有可變數量引數的新陣列例項,而不考慮引數的數量或型別。

注意點

Array.of()  和 Array 建構函式之間的區別在於處理整數引數:

Array.of(7) 建立一個具有單個元素 7 的陣列,而 Array(7) 建立一個長度為7的空陣列(注意: 這是指一個有7個空位(empty)的陣列,而不是由7個undefined組成的陣列)

Array.of(7);       // [7]
Array.of(1, 2, 3); // [1, 2, 3]

Array(7);          // [ , , , , , , ]
Array(1, 2, 3);    // [1, 2, 3]

程式碼實現

點選檢視原始碼實現

實現思路就是把你穿進去的值,挨個賦值到當前陣列即可

Array.of2 = function (...eles) {
  const length = eles.length
  let i = 0
  let newArray = []

  while (i < length) {
    newArray[ i ] = eles[ i ]
    i++
  }

  return newArray
}

測試一把


Array.of2(7);       // [7]
Array.of2(1, 2, 3); // [1, 2, 3]

扁平類

23. flat

基本使用

flat()  方法會按照一個可指定的深度遞迴遍歷陣列,並將所有元素與遍歷到的子陣列中的元素合併為一個新陣列返回。 mdn
const arr1 = [0, 1, 2, [3, 4]];

console.log(arr1.flat()) // [0, 1, 2, 3, 4] 預設會平鋪展開一層


const arr2 = [0, 1, 2, [[[3, 4]]]]

console.log(arr2.flat(2)) // [0, 1, 2, [3, 4]] 指定展開兩層

程式碼實現

點選檢視原始碼實現


Array.prototype.flat2 = function (depth = 1) {
  const result = []
  const flat = (arr, depth) => {
    for (let item of arr) {
      // 當層數還未全部展開的時候,進行遞迴處理
      if (Array.isArray(item) && depth > 0) {
        flat(item, depth - 1)
      } else {
        // 去除空元素,新增非undefined元素
        item !== void 0 && result.push(item)
      }
    }
  }

  flat(this, depth)

  return result
}

測試一把


const arr1 = [0, 1, 2, [3, 4]];

console.log(arr1.flat2()) // [0, 1, 2, 3, 4]


const arr2 = [0, 1, 2, [[[3, 4]]]]

console.log(arr2.flat2(2)) // [0, 1, 2, [3, 4]] 

24. flatMap

基本使用

flatMap 方法首先使用對映函式對映每個元素,然後將結果壓縮成一個新陣列。它與 map 連著深度值為1的 flat 幾乎相同。 mdn

let arr = [1, 2, 3, 4]


arr.flatMap(x => [x * 2]) // [2, 4, 6, 8]

程式碼實現

點選檢視原始碼實現


Array.prototype.flatMap2 = function (callback, thisCtx) {
  if (typeof callback !== 'function') {
    throw `${callback} is not a function`
  }
  // map和flat具體實現可以看map.js和flat.js
  return this.map(function (it, i, array) {
    return callback.call(thisCtx, it, i, array)
  }).flat(1)
}

測試

let arr = [1, 2, 3, 4]


arr.flatMap2(x => [x * 2]) // [2, 4, 6, 8]

結尾

國慶將至,祝大家節日快樂,浪浪浪七天樂。

文章中可能包含實現有問題或者不夠充分的情況,歡迎大家在評論區指出,一定馬不停蹄地改正,拜謝。

篇幅原因,還有不少陣列方法沒有寫在文章中,如果對大家有一些用處,後續還會出一篇splicekeysvalues...等剩餘函式的原生實現。祝大家晚安啦 ?

相關文章