Skip to content
大纲

实现一些 Array 方法

  • arrayLike
  • forEach
  • filter
  • map
  • reduce
  • flat
  • flatMap

类数组

js
// 类数组转化为数组

let arrayLike = {
  0: 'tom',
  1: '65',
  2: '',
  3: ['jane', 'john', 'Mary'],
  length: 4
}

// 1. [].slice
console.log([].slice.call(arrayLike))

// 2. Array.from
console.log(Array.from(arrayLike))

// 3. Array.apply
console.log(Array.apply(null, arrayLike))

// 4. [].concat
// 这个有点问题
console.log([].concat.apply([], arrayLike))

// 5. [...arr]
console.log([...arrayLike])

如何把一个数组随机打乱

js
// 如何把一个数组随机打乱

// 方案一
// 使用原生实现,Math.rondom() - 0.5 有时大于 0,有时小于 0 会达成这样的效果
;[1, 2, 3, 4].sort((x, y) => Math.random() - 0.5)

// 方案二
// 借用 lodash 可更方便
_.shuffle([1, 2, 3, 4])
//-> [3, 2, 4, 1]

创建一个数组,并填充值

比如创建一个数组大小为 100,每个值都为 0 的数组

点我查看详细
js
// 方法一:
Array(100).fill(0)

// 方法二:
// 注: 如果直接使用 map,会出现稀疏数组
Array.from(Array(100), (x) => 0)

// 方法二变体:
Array.from({ length: 100 }, (x) => 0)

如果要求填充自然数呢?

点我查看详细
js
Array.from({ length: 5 }, (x, i) => i)

实现数组的方法

手写数组 forEach 方法

手写之前,分析一位同学的如下写法,一直拿不到接口数据,是为什么?

js
const data = []
const urls = ['api1', 'api2', 'api3']
urls.forEach(async (url) => {
  const res = await request(url)
  data.push(res)
})

// 一直是空数组
console.log(data)

手写 forEach 方法,从 forEach 原理再看上面问题的所在?

js
Array.prototype.myForEach = function (fn, thisValue = []) {
  for (let i = 0; i < this.length; i++) {
    fn(this[i], i, this)
  }
}

forEach 的问题

  • 无法中断
  • 无法进行异步排队

手写数组 filter 方法

js
Array.prototype.myFilter = function (fn, thisValue = []) {
  let res = []
  for (let i = 0; i < this.length; i++) {
    if (fn(this[i], i, this)) {
      res.push(this[i])
    }
  }
  return res
}

手写数组 map 方法

js
Array.prototype.myMap = function (fn, thisValue = []) {
  let res = []
  let arr = this

  for (let i in arr) {
    res.push(fn(arr[i]))
  }

  return res
}

手写数组 reduce 方法

js
function myReduce(arr, fn, initialValue) {
  var num = initValue === undefined ? (num = arr[0]) : initValue
  var i = initValue === undefined ? 1 : 0

  for (i; i < arr.length; i++) {
    num = fn(num, arr[i], i)
  }

  return num
}

如何把一个数组 Array 转化为迭代器 Iterable

js
const list = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

const it = list[Symbol.iterator]()

it.next()

实现扁平化 flatten

实现 flatten 模拟 Array.prototype.flat,默认展开一层,可传递参数用以展开多层

数组降维

js
// ES2019 之前,可通过 reduce + concat 实现
// Array.prototype.concat 既可以连接数组又可以连接单项,十分巧妙

// 简单
// 方案一
const flatten = (list) => list.reduce((a, b) => a.concat(b), [])

// 方案二
const flatten = (list) => [].concat(...list)

// 深层数组打平
function flatten(list, depth = 1) {
  if (depth === 0) return list
  return list.reduce(
    (a, b) => a.concat(Array.isArray(b) ? flatten(b, depth - 1) : b),
    []
  )
}

// 使用迭代器实现
const flatten = function (target, depth = 1) {
  const copy = [...target]
  for (let i = 0; i < depth; ++i) {
    const iter = copy[Symbol.iterator]()
    let item = null
    for (item = iter.next(); !item.done; ) {
      // 注意:迭代器并不与可迭代对象某个时刻的快照绑定,而仅仅是用游标来记录遍历可迭代对象的历程,
      // 如果可迭代对象在迭代期间被修改了,那么迭代器也会反映相应的变化
      if (Array.isArray(item.value)) {
        const temp = [...item.value]
        let size = temp.length
        for (let j = 0; j < size; ++j) {
          item = iter.next()
        }
        copy.splice(copy.indexOf(item.value), 1, ...temp)
      } else {
        item = iter.next()
      }
    }
  }
  return copy
}

// 基于递归实现,不用 Array.concat
Array.prototype.myFlat = function (this: any[], depth: number = 1) {
  const myFlat = (
    arr: any[],
    flatLength = 1,
    resultArray = [] as any[],
    forEachCount = 0
  ) => {
    arr.forEach((d: any) => {
      if (
        Array.isArray(d) &&
        (flatLength === -1 || forEachCount < flatLength)
      ) {
        myFlat(d, flatLength, resultArray, forEachCount + 1);
      } else {
        resultArray.push(d);
      }
    });

    return resultArray;
  };

  return myFlat(this, depth);
};

ES6 flat

js
let arr = [1, 2, [3, 4, [5, [6]]]]
console.log(arr.flat(Infinity))
// flat 参数为指定要提取嵌套数组的结构深度,默认值为 1

使用 reduce 实现

常见的递归版本

js
// 没控制深度,相当于 Infinity
const myFlat = function flat(arr) {
  return arr.reduce((prev, cur) => {
    return prev.concat(Array.isArray(cur) ? flat(cur) : cur)
  }, [])
}

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

console.log(myFlat(arr, Infinity))

console.log([1, 2].concat(3, [4, 5]))

concat(valueN) 方法用于合并两个或多个数组。此方法不会更改现有数组,而是返回一个新数组。

concat 的参数 valueN 为数组和/或值,将被合并到一个新的数组中。如果省略了所有 valueN 参数,则 concat 会返回调用此方法的现存数组的一个浅拷贝。

扩展思考

能用迭代的思路去实现吗?

点我查看详细
js
function flatter(arr) {
  if (!arr.length) return arr
  while (arr.some((item) => Array.isArray(item))) {
    arr = [].concat(...arr)
  }
  return arr
}