Skip to content

1.创建数组的几种方式

1.1 基于字面量和 Array 构造函数创建数组

js
// 字面量方式创建数组
const arr1 = [1, 2, 3, 4, 5]

// new Array()创建数组
const arr2 = new Array(1, 2, 3, 4, 5) // [1,2,3,4,5]
// 创建包含5个空元素的数组
const arr3 = new Array(5) // [空属性 × 5]
// 通过Array构造函数创建包含5个空元素的数组
const arr4 = Array(5) // [空属性 × 5]

1.2 基于 Array.of()创建数组

js
Array.of(1, 2, 3, 4, 5) // [1,2,3,4,5]

1.3 基于 Array.from()创建数组

js
// 创建包含5个空元素的数组
Array.from({ length: 5 }) // [空属性 × 5]

// 创建包含1到12元素的数组
Array.from({ length: 12 }, (v, i) => i + 1) // [1,2,3,4,5,6,7,8,9,10,11,12]

2.Array 原型函数

2.1 Array.prototype.forEach()

js
Array.prototype.myForEach = function (callback, thisArg) {
  if (!Array.isArray(this)) throw new Error('只有数组才有forEach()')
  if (callback && typeof callback !== 'function') {
    throw new Error('callback必须是个函数')
  }
  const len = this.length
  if (len === 0) return
  for (let i = 0; i < len; i++) {
    arguments.length > 1 ? callback.call(thisArg, this[i], i, this) : callback(this[i], i, this)
  }
}

2.2 Array.prototype.map()

js
// map
Array.prototype.myMap = function (callback, thisArg) {
  if (!Array.isArray(this)) throw new Error('只有数组才有map()')
  if (callback && typeof callback !== 'function') {
    throw new Error('callback必须是个函数')
  }
  const len = this.length,
    arr = []
  if (len === 0) return arr

  for (let i = 0; i < len; i++) {
    const result =
      arguments.length > 1 ? callback.call(thisArg, this[i], i, this) : callback(this[i], i, this)
    arr.push(result)
  }
  return arr
}

2.3 Array.prototype.reduce()

js
Array.prototype.myReduce = function (callback, initialValue) {
  if (!Array.isArray(this)) throw new Error('Reduce does not exist in array')
  if (callback && typeof callback !== 'function') {
    throw new Error(`${callback} is not a function`)
  }
  const len = this.length
  if (len === 0 && initialValue === 'undefined') {
    throw new Error('Reduce of empty array with no initial value')
  }
  initialValue = initialValue || this[0]
  for (let i = 1; i < len; i++) {
    initialValue = callback(initialValue, this[i], i - 1, this)
  }

  return initialValue
}

// 测试
const result = [1, 2, 3].myReduce((prev, next, currentIndex, array) => {
  console.log(prev, next, currentIndex, array)
  return prev + next
})
console.log(result) // 6

2.4 Array.prototype.filter()

js
Array.prototype.myFilter = function (callback, thisArg) {
  if (!Array.isArray(this)) throw new Error('只有数组才有filter()')
  const len = this.length,
    arr = []
  if (len === 0) return arr
  if (typeof callback !== 'function') throw new Error('callback必须是个函数')
  for (let i = 0; i < len; i++) {
    const result =
      arguments.length > 1 ? callback.call(thisArg, this[i], i, this) : callback(this[i], i, this)
    Boolean(result) && arr(this[i])
  }
}

2.5 Array.prototype.find()

js
Array.prototype.myFind = function (callback, thisArg) {
  if (!Array.isArray(this)) throw new Error('只有数组才有find()')
  if (typeof callback !== 'function') throw new Error('callback必须是个函数')
  const len = this.length
  for (let i = 0; i < len; i++) {
    const result =
      arguments.length > 1 ? callback.call(thisArg, this[i], i, this) : callback(this[i], i, this)
    if (result) {
      return result
    }
  }
  return
}

2.6 手写 Array.prototype.flatMap()

3.数组去重

3.1 利用 Set 元素唯一去重

将数组转为 Set,利用 Set 结构相同元素不能重复的特性去除数组中相同元素,然后将 Set 转为数组。

js
/*
 * 方式1:利用Set相同元素不能重复的特性去除数组中相同元素,然后通过Array.from()将Set转为数组,
 * 也可以通过扩展运算符将Set转为数组,例如[...new Set(arr)]
 */
const distinct = (arr) => Array.from(new Set(arr))

// 测试
const arr = [1, 2, 3, 1, 2]
console.log(distinct(arr)) // [1,2,3]

3.2 利用 indexOf()或 includes()

创建一个新数组利用 indexOf()或 indecludes()判断原数组的元素是否存在新数组中,若不存在则往新数组添加该元素。

js
/*
 * 方式2:创建一个新数组,利用indexOf()判断原数组元素是否存在新数组中,若不存在则添加。
 * includes()实现去重与indexOf()类似
 */
const distinct = (arr) => {
  if (!Array.isArray(arr)) {
    throw new Error('arr not an array')
  }
  const newArr = []
  for (let i = 0, len = arr.length; i < len; i++) {
    newArr.indexOf(arr[i]) === -1 && newArr.push(arr[i])
  }
  return newArr
}

// 测试
const arr = [1, 2, 3, 1, 2]
console.log(distinct(arr)) // [1,2,3]

3.3 通过 filter()去重

利用 filter()过滤相同元素。

js
const distinct = (arr) => {
  return arr.filter((item, index, array) => array.indexOf(item) === index)
}

// 测试
const arr = [1, 2, 3, 1, 2]
console.log(distinct(arr)) // [1,2,3]

3.4 利用对象键名唯一去重

对象的 key 在同一个对象中必须是唯一的,对于相同的 key 后者会覆盖前者。

js
const distinct = (arr) => {
  const obj = {}
  arr.forEach((item) => {
    if (!obj[item]) {
      obj[item] = item
    }
  })
  return Object.values(obj)
}

// 测试
const arr = [1, 2, 3, 1, 2]
console.log(distinct(arr)) // [1,2,3]

3.5 利用 Map 结构 Key 不重复特性去重

js
/*
 * 方式5:利用Map key名不可重复去重
 */
const distinct = (arr) => {
  const map = new Map()
  arr.forEach((item) => {
    if (!map.has(item)) {
      map.set(item, item)
    }
  })
  return [...map.keys()]
}

// 测试
const arr = [1, 2, 3, 1, 2]
console.log(distinct(arr)) // [1,2,3]

3.6 splice()+双重循环去重

js
const distinct = (arr) => {
  let len = arr.length
  for (let i = 0; i < len; i++) {
    for (let j = i + 1; j < len; j++) {
      if (arr[i] === arr[j]) {
        arr.splice(j, 1)
        // 由于数组删除了一个元素,长度和遍历的范围都要减一
        len--
        j--
      }
    }
  }
  return arr
}

// 测试
const arr = [1, 2, 3, 1, 2]
console.log(distinct(arr)) // [1,2,3]

4.数组扁平化

提供一个多维数组,编写一个函数将数组扁平化去并除其中重复部分数据,最终得到一个升序且不重复的数组。

js
// 多维数组,要求将其扁平化并去重升序排序
const arr = [[1, 2, 2], [3, 4, 5, 5], [6, 7, 8, 9, [11, 12, [12, 13, [14]]]], 10]

4.1 基于 flat 扁平化数组

flat(Infinity)将任意多维数组拍平为一维数组,然后数组转 Set 去重,然后通过 Array.from 将对象转为数组,最后通过 sort()升序排序。

js
const fn = (arr) => {
  if (!Array.isArray(arr)) return
  return Array.from(new Set(arr.flat(Infinity))).sort((a, b) => a - b)
}

// 测试
console.log(fn(arr)) // [1, 2, 3, 4, 5, 6,7, 8, 9, 10, 11, 12,13, 14]

4.2 基于递归扁平化数组

通过递归拍平数组,然后转 Set 去重,最后 sort()升序。

js
const fn = (arr) => {
  if (!Array.isArray(arr)) return
  Array.prototype.flat = function () {
    return [].concat(...this.map((item) => (Array.isArray(item) ? item.flat() : [item])))
  }
  Array.prototype.unique = function () {
    return [...new Set(this)]
  }
  const sort = (a, b) => a - b
  return arr.flat().unique().sort(sort)
}

// 测试
console.log(fn(arr)) // [1, 2, 3, 4, 5, 6,7, 8, 9, 10, 11, 12,13, 14]

5.数组转树形结构

ts
// 将arr根据pid与id的关系转树形结构
const arr = [
  { id: 1, label: '1', pid: -1 },
  { id: 2, label: '2', pid: -1 },
  { id: 3, label: '1-1', pid: 1 },
  { id: 4, label: '1-2', pid: 1 },
  { id: 5, label: '2-1', pid: 2 },
  { id: 6, label: '1-1-1', pid: 3 },
]

5.1 通过循环数组转树形结构

步骤 1:遍历数组,先判断 item 的 pid 是否是为 null 或者-1,如果满足条件则说明该元素是根节点,则直接添加到 tree。 步骤 2:如果 item 不是根节点,则根据 item 的 pid 获取到父节点(parent),父节点初始化 children 属性用于存储子节点,如果父节点的 children 不包含 item 则直接向父节点的 children 添加。

ts
const arrayToTree = (arr) => {
  if (!Array.isArray(arr)) return []
  const tree = []

  arr.forEach((item) => {
    // 如果pid为空或-1则直接向tree添加数据
    if (!item.pid || item.pid === -1) {
      tree.push(item)
    } else {
      // 获取父节点,这里可以通过map或object做数据映射,可以避免在循环中调用find(),从而提升执行效率,空间换时间
      const parent = arr.find((row) => row.id === item.pid)
      // 初始化parent.childrent
      !parent.children && (parent.children = [])
      // 判断parent.children是否已包含当前遍历元素,不包含则向parent.children添加
      !parent.children.includes(item) && parent.children.push(item)
    }
  })
  return tree
}

console.log(JSON.stringify(arrayToTree(arr)))
ts
// 结果
;[
  {
    id: 1,
    label: '1',
    pid: -1,
    children: [
      {
        id: 3,
        label: '1-1',
        pid: 1,
        children: [
          {
            id: 6,
            label: '1-1-1',
            pid: 3,
          },
        ],
      },
      {
        id: 4,
        label: '1-2',
        pid: 1,
      },
    ],
  },
  {
    id: 2,
    label: '2',
    pid: -1,
    children: [
      {
        id: 5,
        label: '2-1',
        pid: 2,
      },
    ],
  },
]

5.2 通过 reduce 转树形结构

ts
const arrayToTree = (arr) => {
  if (!Array.isArray(arr)) return []
  const tree = []
  // 通过map或者object做数据映射,可以更快的获取数据
  const map = new Map()
  // 倒序遍历
  for (let i = arr.length - 1; i--; ) {
    map.set(arr[i].id, arr[i])
  }
  arr.reduce((_, cur) => {
    if (!cur.pid || cur.pid === -1) {
      tree.push(cur)
    } else {
      const parent = map.get(cur.pid)
      !parent.children && (parent.children = [])
      !parent.children.includes(cur) && parent.children.push(cur)
    }
  }, [])
  return tree
}
console.log(JSON.stringify(arrayToTree(arr)))

6.树形结构转数组

ts
// 将树形转为数组
const tree = [
  {
    id: 1,
    label: '1',
    pid: -1,
    children: [
      {
        id: 3,
        label: '1-1',
        pid: 1,
        children: [
          {
            id: 6,
            label: '1-1-1',
            pid: 3,
          },
        ],
      },
      {
        id: 4,
        label: '1-2',
        pid: 1,
      },
    ],
  },
  {
    id: 2,
    label: '2',
    pid: -1,
    children: [
      {
        id: 5,
        label: '2-1',
        pid: 2,
      },
    ],
  },
]

6.1 通过递归遍历树形结构转数组

ts
const treeToArray = (tree) => {
  if (!Array.isArray(tree) || !tree.length) return []
  const arr = []
  tree.forEach((item) => {
    arr.push(item, ...treeToArray(item.children))
  })
  return arr
}
console.log(JSON.stringify(treeToArray(tree)))
ts
// 结果
;[
  {
    id: 1,
    label: '1',
    pid: -1,
    children: [
      {
        id: 3,
        label: '1-1',
        pid: 1,
        children: [
          {
            id: 6,
            label: '1-1-1',
            pid: 3,
          },
        ],
      },
      {
        id: 4,
        label: '1-2',
        pid: 1,
      },
    ],
  },
  {
    id: 3,
    label: '1-1',
    pid: 1,
    children: [
      {
        id: 6,
        label: '1-1-1',
        pid: 3,
      },
    ],
  },
  {
    id: 6,
    label: '1-1-1',
    pid: 3,
  },
  {
    id: 4,
    label: '1-2',
    pid: 1,
  },
  {
    id: 2,
    label: '2',
    pid: -1,
    children: [
      {
        id: 5,
        label: '2-1',
        pid: 2,
      },
    ],
  },
  {
    id: 5,
    label: '2-1',
    pid: 2,
  },
]

6.2 通过 reduce 树形结构转数组

js
const treeToArray = (tree) => {
  if (!Array.isArray(tree) || !tree.length) return []
  const arr = []
  tree.reduce((prev, cur) => {
    arr.push(cur, ...treeToArray(cur.children))
  }, [])
  return arr
}
console.log(JSON.stringify(treeToArray(tree)))

7.数组分组

js
/**
 * 根据指定属性对目标对象数组进行分组
 * @param target 目标对象数组
 * @param prop 分组属性,可以是一个字符串或返回指定分组属性的好函数s
 */
function groupBy(target, prop) {
  if (!Array.isArray(target)) {
    throw new Error('target must be an array')
  }
  if (typeof prop !== 'string' && typeof prop !== 'function') {
    throw new Error('prop can only be a string or function')
  }
  const result = {}
  for (let i = 0, len = target.length; i < len; i++) {
    if (typeof target[i] === 'object' && target[i] != null) {
      const key = typeof prop === 'string' ? prop : prop(target[i], i)
      if (target[i].hasOwnProperty(key)) {
        const value = target[i][key]
        result[value] = result[value] ? result[value].concat(target[i]) : [target[i]]
      }
    }
  }
  return result
}
const arr = [
  {
    name: 'Joker',
    age: 12,
  },
  {
    name: 'Lover',
    age: 12,
  },
  {
    name: 'Joker',
    age: 30,
  },
  {
    name: 'Q',
    age: 24,
  },
  {
    name: 'Joker',
    age: 40,
  },
]
const result1 = groupBy(arr, 'age')
const result2 = groupBy(arr, (obj, i) => {
  return i <= 1 ? 'age' : 'name'
})
console.log(result1, result2)

/**
  {
    "12": [{"name": "Joker","age": 12},{"name": "Lover","age": 12}],
    "24": [{"name": "Q","age": 24}],
    "30": [{"name": "Joker","age": 30}],
    "40": [{"name": "Joker","age": 40}]
  }
  {
    "12": [{"name": "Joker","age": 12},{"name": "Lover","age": 12}],
    "Joker": [{"name": "Joker","age": 30},{"name": "Joker","age": 40}],
    "Q": [{"name": "Q","age": 24}]
  }
 */

Released under the MIT License.