Radash 源码学习系列三之 Array 算是上篇吧,一是平时太忙,兼顾着只能周末慢慢来弄 … 二是 Array 方法还真挺多的 29 个方法 ~ 分成上下两篇了
Array
Radash 使用 TypeScript 编写,提供开箱即用的完整功能。 大多数 Radash 函数都是确定性 和 纯函数(Pure Function)。
range
创建用于迭代的范围
给定开始、结束、值和步长,返回一个生成器,该生成器将按步长生成从开始到结束的值。对于将 for (let i = 0) 替换为 for of 很有用。 Range 将返回一个生成器 generator
,for of 将一次调用一个生成器,因此创建大范围是安全的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| import { range } from 'radash'
range(3) range(0, 3) range(0, 3, 'y') range(0, 3, () => 'y') range(0, 3, i => i) range(0, 3, i => `y${i}`) range(0, 3, obj) range(0, 6, i => i, 2)
for (const i of range(0, 200, 10)) { console.log(i) }
for (const i of range(0, 5)) { console.log(i) }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| export function* range<T = number>( startOrLength: number, end?: number, valueOrMapper: T | ((i: number) => T) = i => i as T, step: number = 1 ): Generator<T> { const mapper = isFunction(valueOrMapper) ? valueOrMapper : () => valueOrMapper const start = end ? startOrLength : 0 const final = end ?? startOrLength for (let i = start; i <= final; i += step) { yield mapper(i) if (i + step > final) break } }
|
这个生成器函数可以用于生成指定范围内的整数序列,常用于遍历数组、集合或其他需要遍历的数据结构。
list
创建包含特定项目的列表
给定开始、结束、值和步长,返回一个列表,其中包含按步长从开始到结束的值。该接口与range
相同。
1 2 3 4 5 6 7 8 9 10
| import { list } from 'radash'
list(3) list(0, 3) list(0, 3, 'y') list(0, 3, () => 'y') list(0, 3, i => i) list(0, 3, i => `y${i}`) list(0, 3, obj) list(0, 6, i => i, 2)
|
看用法大家应该猜到了,没错,核心就是 Array.from()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| export const list = <T = number>( startOrLength: number, end?: number, valueOrMapper?: T | ((i: number) => T), step?: number ): T[] => { return Array.from(range(startOrLength, end, valueOrMapper, step)) }
|
objectify
将列表转换为字典对象
给定一个项目数组,使用给定函数映射的键和值创建一个字典。第一个参数是要映射的数组。第二个参数是确定每个项目的键的函数。第三个参数是可选的,它确定每个项目的值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| import { objectify } from 'radash'
const fish = [ { name: 'Marlin',weight: 105 }, { name: 'Bass',weight: 8 }, { name: 'Trout',weight: 13 } ]
objectify(fish, f => f.name)
objectify( fish, f => f.name, f => f.weight )
|
该函数接受三个参数:一个数组(array)、一个获取键的函数(getKey)和一个获取值的函数(getValue,可选)。函数的返回类型是一个记录(Record<Key, Value>
),其中键是Key类型,值是Value类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| export const objectify = <T, Key extends string | number | symbol, Value = T>( array: readonly T[], getKey: (item: T) => Key, getValue: (item: T) => Value = item => item as unknown as Value ): Record<Key, Value> => { return array.reduce((acc, item) => { acc[getKey(item)] = getValue(item) return acc }, {} as Record<Key, Value>) }
|
将一个包含对象或数组的数组转换为一个具有指定键值类型的记录。这对于将数据结构从数组转换为对象非常有用,特别是当数组中的元素具有相同的键和值时。
replaceOrAppend
替换数组中的项目或追加(如果不匹配)
给定一个项目数组、一个项目和一个恒等函数,返回一个新数组,其中该项目要么在现有项目的索引处替换——如果存在,否则将其附加在末尾。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| import { replaceOrAppend } from 'radash'
const fish = [ { name: 'Marlin', weight: 105 }, { name: 'Salmon', weight: 19 }, { name: 'Trout', weight: 13 } ] const salmon = { name: 'Salmon', weight: 22 } const sockeye = { name: 'Sockeye', weight: 8 }
replaceOrAppend(fish, salmon, f => f.name === 'Salmon')
replaceOrAppend(fish, sockeye, f => f.name === 'Sockeye')
|
该函数接受三个参数:一个 readonly 类型的数组list(表示原始数组),一个类型为T的新项newItem,以及一个函数match,用于判断是否需要替换或追加新项。函数的返回值是一个数组,类型为T。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| export const replaceOrAppend = <T>( list: readonly T[], newItem: T, match: (a: T, idx: number) => boolean ) => { if (!list && !newItem) return [] if (!newItem) return [...list] if (!list) return [newItem]
for (let idx = 0; idx < list.length; idx++) { const item = list[idx] if (match(item, idx)) { return [ ...list.slice(0, idx), newItem, ...list.slice(idx + 1, list.length) ] } } return [...list, newItem] }
|
这个函数可以在原始数组list中查找或替换特定条件下的元素,从而创建一个新的数组。这个函数可以用于在数组中插入新元素、修改现有元素或在特定条件下替换元素。
replace
替换数组中的一项
给定一组项目,替换与给定条件函数匹配的项目。仅替换第一个匹配项。始终返回原始数组的副本。
1 2 3 4 5 6 7 8 9 10
| import { replace } from 'radash'
const fish = [ { name: 'Marlin', weight: 105 }, { name: 'Bass', weight: 8 }, { name: 'Trout', weight: 13 } ] const salmon = { name: 'Salmon', weight: 22 }
replace(fish, salmon, f => f.name === 'Bass')
|
该函数接受三个参数:一个 readonly 类型的数组list(表示原始数组),一个类型为T的新项newItem,以及一个函数match,用于判断某个项是否满足替换条件。
实现原理是将原始数组中的每个项与新项进行比较,如果满足条件,则将新项插入到该项之前,并将该项及后续项向前移动一位。最后返回一个新的数组,其中包含了替换后的结果。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| export const replace = <T>( list: readonly T[], newItem: T, match: (item: T, idx: number) => boolean ): T[] => { if (!list) return [] if (newItem === undefined) return [...list]
for (let idx = 0; idx < list.length; idx++) { const item = list[idx] if (match(item, idx)) { return [ ...list.slice(0, idx), newItem, ...list.slice(idx + 1, list.length) ] } } return [...list] }
|
select
过滤并映射数组
一次性应用过滤器和映射操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| import { select } from 'radash'
const fish = [ { name: 'Marlin', weight: 105, source: 'ocean' }, { name: 'Bass', weight: 8, source: 'lake' }, { name: 'Trout', weight: 13, source: 'lake' } ]
select( fish, f => f.weight, f => f.source === 'lake' )
|
该函数接受三个参数:一个 readonly 类型的数组array、一个依赖传入数组和索引的函数mapper和一个依赖传入数组和索引的函数condition。
实现原理是使用reduce方法遍历数组,根据condition函数的条件来判断是否保留当前元素,如果保留,则使用mapper函数计算该元素的新值并将其添加到结果数组中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| export const select = <T, K>( array: readonly T[], mapper: (item: T, index: number) => K, condition: (item: T, index: number) => boolean ) => { if (!array) return []
return array.reduce((acc, item, index) => { if (!condition(item, index)) return acc acc.push(mapper(item, index)) return acc }, [] as K[]) }
|
该函数可以用于从数组中选择满足特定条件的元素,并返回一个新的数组,其中包含转换后的元素。例如,可以用于从数组中选择偶数并将它们转换为字符串:
shift
将数组项移动 n 步
给定一个项目列表,返回一个向右移动 n 个位置的数组。
1 2 3
| import { shift } from 'radash' const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9] shift(arr, 3)
|
该函数接受一个数组arr和一个数字n作为参数,并返回一个新的数组。实现原理是将数组arr中的元素向右移动n次。
1 2 3 4 5 6 7 8 9 10 11 12 13
| export function shift<T>(arr: Array<T>, n: number) { if (arr.length === 0) return arr const shiftNumber = n % arr.length if (shiftNumber === 0) return arr
return [...arr.slice(-shiftNumber, arr.length), ...arr.slice(0, -shiftNumber)] }
|
sift
从列表中删除所有虚假项目
给定一个项目列表,返回一个新列表,其中包含所有不为假的项目。
1 2 3 4 5
| import { sift } from 'radash'
const fish = ['salmon', null, false, NaN, 'sockeye', 'bass']
sift(fish)
|
实现原理是将一个readonly类型的可迭代列表(只读的可迭代对象)过滤掉所有的假值(Falsy),并将过滤后的结果转换为T类型的数组。
1 2 3 4 5 6 7 8 9 10 11 12 13
| export const sift = <T>( list: readonly (T | Falsy)[] ): T[] => { return ( list?.filter(x => !!x) as T[] ) ?? [] }
|
主要用途是用来处理列表,将其中所有的假值过滤掉,从而得到一个纯T类型的数组。这样做的目的是为了提高代码的可读性和可维护性,避免因为假值导致的问题。
sort
按数字属性对对象列表进行排序
给定一个对象数组,返回一个按 get 函数中指定的数值属性排序的新数组。第三个可选参数允许您按降序而不是默认的升序排序。
该函数仅支持数字排序。对于按字母顺序排序,请参阅按字母顺序功能。
1 2 3 4 5 6 7 8 9 10 11 12 13
| import { sort } from 'radash'
const fish = [ { name: 'Marlin', weight: 105 }, { name: 'Bass', weight: 8 }, { name: 'Trout', weight: 13 } ]
sort(fish, f => f.weight)
sort(fish, f => f.weight, true)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| export const sort = <T>( array: readonly T[], getter: (item: T) => number, desc = false ) => { if (!array) return [] const asc = (a: T, b: T) => getter(a) - getter(b) const dsc = (a: T, b: T) => getter(b) - getter(a)
return array.slice().sort(desc === true ? dsc : asc) }
|
这个sort函数可以用于对具有getter函数指定的属性的数组进行排序。例如,您可以使用这个函数对具有id属性值的数组进行升序排序,或者使用desc参数为true对数组进行降序排序。
sum
将数组的所有项相加
给定一个项目数组,以及一个将每个项目映射到数字的可选函数,将所有项目相加。
1 2 3 4 5 6 7 8 9
| import { sum } from 'radash'
const fish = [ { name: 'Marlin', weight: 100 }, { name: 'Bass', weight: 10 }, { name: 'Trout', weight: 15 } ]
sum(fish, f => f.weight)
|
实现原理:使用reduce方法来计算数组中元素的总和。reduce方法会遍历数组,并将每个元素与上一个累积值相加,然后将结果作为新的累积值传递给下一个reduce迭代
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
|
export function sum<T extends number>(array: readonly T[]): number
export function sum<T extends object>( array: readonly T[], fn: (item: T) => number ): number
export function sum<T extends object | number>( array: readonly any[], fn?: (item: T) => number ): number { return (array || []).reduce((acc, item) => acc + (fn ? fn(item) : item), 0) }
|
toggle
切换数组中的项目是否存在
如果列表中已存在符合条件的项目,则会将其删除。如果没有,将会添加。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| import { toggle } from 'radash'
const names = ['ra', 'zeus', 'loki']
toggle(names, 'ra') toggle(names, 'vishnu')
const ra = { name: 'Ra' } const zeus = { name: 'Zeus' } const loki = { name: 'Loki' } const vishnu = { name: 'Vishnu' }
const gods = [ra, zeus, loki]
toggle(gods, ra, g => g.name)
toggle(gods, vishnu, g => g.name)
|
这个函数的主要目的是从一个给定的list中删除或添加一个item,根据toKey函数的返回值是否与list中的元素相等。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| export const toggle = <T>( list: readonly T[], item: T, toKey?: null | ((item: T, idx: number) => number | string | symbol), options?: { // 它可以通过strategy选项来决定添加项目的位置,默认为'append' strategy?: 'prepend' | 'append' } ) => { if (!list && !item) return [] if (!list) return [item] if (!item) return [...list]
const matcher = toKey ? (x: T, idx: number) => toKey(x, idx) === toKey(item, idx) : (x: T) => x === item const existing = list.find(matcher) if (existing) return list.filter((x, idx) => !matcher(x, idx)) const strategy = options?.strategy ?? 'append' if (strategy === 'append') return [...list, item] return [item, ...list] }
|
unique
从数组中删除重复项
给定一个项目数组 - 以及一个可选的确定其身份的函数 - 返回一个没有任何重复项的新数组。
该函数不保留项目的原始顺序。
1 2 3 4 5 6 7 8 9 10 11 12 13
| import { unique } from 'radash'
const fish = [ { name: 'Marlin', weight: 105, source: 'ocean' }, { name: 'Salmon', weight: 22, source: 'river' }, { name: 'Salmon', weight: 22, source: 'river' } ]
unique( fish, f => f.name )
|
该函数接受一个 readonly T[]
类型的参数array和一个可选的toKey函数。T是一个泛型类型,可以是一个字符串、数字或符号;K是一个扩展类型,可以是一个字符串、数字或符号。函数的返回类型是T[]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| export const unique = <T, K extends string | number | symbol>( array: readonly T[], toKey?: (item: T) => K ): T[] => { const valueMap = array.reduce( (acc, item) => { const key = toKey ? toKey(item) : (item as any as string | number | symbol) if (acc[key]) return acc acc[key] = item return acc }, {} as Record<string | number | symbol, T> ) return Object.values(valueMap) }
|
zipToObject
将多个数组组合成集合
创建一个对象,将第一个数组中的键映射到第二个数组中对应的值。
1 2 3 4 5 6 7 8 9 10 11 12 13
| import { zipToObject } from 'radash'
const names = ['ra', 'zeus', 'loki'] const cultures = ['egypt', 'greek', 'norse']
zipToObject(names, cultures)
zipToObject(names, (k, i) => k + i)
zipToObject(names, null)
|
zipToObject函数使用reduce方法遍历keys数组,对于每个键,调用getValue函数获取相应的值。getValue函数根据values参数的类型进行不同的处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| export function zipToObject<K extends string | number | symbol, V>( keys: K[], values: V | ((key: K, idx: number) => V) | V[] ): Record<K, V> { if (!keys || !keys.length) { return {} as Record<K, V> }
const getValue = isFunction(values) ? values : isArray(values) ? (_k: K, i: number) => values[i] : (_k: K, _i: number) => values
return keys.reduce((acc, key, idx) => { acc[key] = getValue(key, idx) return acc }, {} as Record<K, V>) }
|
zip
将多个数组组合成集合
创建一个分组元素数组,其中第一个包含给定数组的第一个元素,第二个包含给定数组的第二个元素,依此类推。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| import { zip } from 'radash'
const names = ['ra', 'zeus', 'loki'] const cultures = ['egypt', 'greek', 'norse']
const zipped1 = zip(names, cultures)
const zipped2 = zip(['a', 'b'], [1, 2], [true, false])
|
zip函数可以用于将多个数组组合成一个新的数组,特别用于多维数组的处理。例如,如果有一个包含3个一维数组的数组,可以使用zip函数将其组合成一个二维数组。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
|
export function zip<T1, T2, T3, T4, T5>( array1: T1[], array2: T2[], array3: T3[], array4: T4[], array5: T5[] ): [T1, T2, T3, T4, T5][]
export function zip<T1, T2, T3, T4>( array1: T1[], array2: T2[], array3: T3[], array4: T4[] ): [T1, T2, T3, T4][] export function zip<T1, T2, T3>( array1: T1[], array2: T2[], array3: T3[] ): [T1, T2, T3][] export function zip<T1, T2>(array1: T1[], array2: T2[]): [T1, T2][] export function zip<T>(...arrays: T[][]): T[][] { if (!arrays || !arrays.length) return [] return new Array( Math.max(...arrays.map(({ length }) => length)) ) .fill([]) .map((_, idx) => arrays.map(array => array[idx])) }
|