实现一些 Array 方法
- arrayLike
Array 内置函数( 🟡 会改变原数组)
大部分参数都是 xxx(callbackFn, ?thisArg)
- 原型方法
at(index)
concat(value0, value1, /*...*/ valueN)
copyWithin(target, start, ?end)
🟡entries()
every
fill(value, ?start, ?end)
🟡filter
find
findIndex
findLast
findLastIndex
flat(?depth)
flatMap
forEach
group
groupToMap
includes(searchElement, ?fromIndex)
indexOf(searchElement, ?fromIndex)
join(?separator)
keys()
lastIndexOf(searchElement, ?fromIndex)
map
pop()
🟡push(...items)
🟡reduce(callbackFn, initialValue)
reduceRight(callbackFn, initialValue)
reverse()
🟡shift()
🟡slice(?start, ?end)
some
sort(?compareFn)
🟡splice(start, ?deleteCount, ...items)
🟡toLocaleString()
toString()
unshift(...items)
🟡values()
with(index, value)
ES2023toReversed()
ES2023toSorted()
ES2023toSpliced()
ES2023
- 静态方法
from(arrayLike, mapFn, ?thisArg)
fromAsync(arrayLike, mapFn, ?thisArg)
isArray(value)
of(...items)
类数组
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
// 方法一
let i = 0;
new Array(n).fill(i++)
// 方法二
Array.from({ length: n }, (v, i) => i)
// 方法三
[...Array(n).keys()]
// 方法四
// lodash 底层通过 new Array 和 while 循环实现
_.times(n)
// 方案五
function* generateNaturalNumber(n) {
let i = 0;
while(i <= n) {
yield i;
i++;
}
}
// 使用
[...generateNaturalNumber(100)]
// 方案六, 自定义迭代器
function createNaturalNumber(n) {
var i = 0;
return {
next: function next() {
return {
done: i >= n,
value: i++,
}
}
};
}
const myNaturalNumber = {
[Symbol.iterator]: function() {
return createNaturalNumber(100)
}
};
// 使用
[...myNaturalNumber]
// 或
for(var i of myNaturalNumber) {
console.log(i)
}
// 或
Array.from(myNaturalNumber)
实现数组的方法
手写数组 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 arr = this
const len = arr.length
let res = new Array(len)
for (let i = 0; i < len; i++) {
if (typeof arr[i] !== 'undefined') {
res[i] = fn(arr[i], i, arr)
} else {
// 对于空槽,赋值就变成 undefined 了
// 可通过 new Array(len) 生成带空槽的数组来处理此问题,
// 但这样如果值本身是 undefined 就又有问题了
res[i] = arr[i]
}
}
return res
}
// testing
let arr = [1, 2, , 3]
console.log(arr, arr.length)
console.log(arr.myMap((v) => v * 2))
console.log(arr.map((v) => v * 2))
- 稀疏数组在使用 map() 方法后仍然是稀疏的。空槽的索引在返回的数组中仍然为空,并且回调函数不会对它们进行调用。
- 空槽与未定义的值不同,最重要的区别是空槽不可枚举。
手写数组 reduce 方法
js
function myReduce(arr, fn, initValue) {
var num = initValue === undefined ? (num = arr[0]) : initValue
var i = initValue === undefined ? 1 : 0
for (i; i < arr.length; i++) {
if (typeof arr[i] !== 'undefined') {
num = fn(num, arr[i], i)
}
}
return num
}
// testing
let arr = [1, 2, , [3, 4, [5, [6]]]]
console.log(myReduce(arr, (v, b) => [v].concat(b), []))
console.log(
arr.reduce((v, b) => {
console.log(b)
return [v].concat(b)
}, [])
)
reduce()
会过滤掉空数组
如何把一个数组 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
- flat() 默认深度为 1
- flat() 方法会移除数组中的空项 empty(稀疏数组)
使用 reduce
实现 flat
常见的递归版本
js
// 考虑了深度控制
const myFlat = function flat(arr, depth = 1) {
return arr.reduce((prev, cur) => {
let temp = cur
if (Array.isArray(cur)) {
temp = depth > 1 ? flat(cur, depth - 1) : cur
}
return prev.concat(Array.isArray(cur) ? temp : cur)
}, [])
}
let arr = [1, 2, , [3, 4, [5, [6]]]]
console.log(arr)
console.log(myFlat(arr, 2))
console.log(arr.flat(2))
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
}
实现 flatMap
flatMap()
方法对数组中的每个元素应用给定的回调函数,然后将结果展开一级,返回一个新数组。它等价于在调用 map()
方法后再调用深度为 1 的 flat()
方法(arr.map(...args).flat()
),但比分别调用这两个方法稍微更高效一些。
注意:先 map 后 flat(1)
js
// 先 map 后 flat()
function myFlatMap(fn) {
let target = this
return target.map((i) => fn(i)).flat()
}
Array.prototype._flatMap = function (callback) {
let newArr = [...this]
let result = []
for (let i = 0; i < newArr.length; i++) {
result.push(callback(newArr[i], i, newArr))
}
return result.flat()
}
参考