lodash已死?radash库方法介绍及源码解析 —— 函数柯里化 + Number篇
写在前面
tips:点赞 + 收藏 = 学会!
- 主页有更多其他篇章的方法,欢迎访问查看。
- 本篇我们继续介绍
radash
中函数柯里化和Number
相关的方法使用和源码解析。
函数柯里化
chain:创建一个函数链并依次执行
- 使用说明
- 功能描述:用于创建一个函数链,该链依次执行一系列函数,每个函数的输出都是下一个函数的输入。这种模式常见于函数式编程,特别是在数据转换和流水线处理中。
- 参数:函数数组(或者说任意数量的函数)。
- 返回值:返回一个新的函数。
- 使用代码示例
import { chain } from 'radash' const add = (y: number) => (x: number) => x + y const mult = (y: number) => (x: number) => x * y const addFive = add(5) const double = mult(2) const chained = chain(addFive, double) chained(0) // => 10 chained(7) // => 24
- 源码解析
// 定义一个名为 `chain` 的函数。 export function chain( // `funcs` 是一个由函数组成的数组,这些函数将会被依次执行。 // 每个函数都可以接受任意参数,并返回任何类型的值。 ...funcs: ((...args: any[]) => any)[] ) { // `chain` 函数返回一个新的函数,这个新函数接受任意参数。 return (...args: any[]) => { // 使用 `funcs` 数组中的第一个函数和传入的参数 `args` 来初始化累加器 `acc`。 // 然后,使用 `reduce` 方法依次执行剩余的函数。 return funcs.slice(1).reduce( // 在每个迭代中,将前一个函数的返回值作为下一个函数的输入。 (acc, fn) => fn(acc), // 第一次迭代的初始值是 `funcs` 数组中第一个函数的执行结果。 funcs[0](...args) ) } }
- 方法流程说明:
chain
函数接受任意数量的函数作为参数,并返回一个新的函数。- 当新函数被调用时,它首先使用传入的参数调用
funcs
数组中的第一个函数。 - 然后,它使用
reduce
方法依次执行funcs
数组中剩余的函数,每个函数的返回值都被传递给下一个函数。 - 最终,返回最后一个函数的执行结果。
- 方法流程说明:
compose:依次执行传入的函数,每个函数的输出作为下一个函数的输入
- 使用说明
- 功能描述:接受一系列函数作为参数,并返回一个新的函数。这个新函数将从右到左依次执行传入的函数,每个函数的输出作为下一个函数的输入。这是函数式编程中的组合(composition)模式。
- 参数:一系列函数(函数数组)。
- 返回值:一个新的函数。
- 使用代码示例
import { compose } from 'radash' const useZero = (fn: any) => () => fn(0) const objectize = (fn: any) => (num: any) => fn({ num }) const increment = (fn: any) => ({ num }: any) => fn({ num: num + 1 }) const returnArg = (arg: any) => (args: any) => args[arg] const composed = compose( useZero, objectize, increment, increment, returnArg('num') ) composed() // => 2
- 源码解析
// 定义一个名为 `compose` 的函数。 export function compose( // 使用展开运算符接收一个函数数组,每个函数可以接受任意参数并返回任何类型的值。 ...funcs: ((...args: any[]) => any)[] ) { // `compose` 函数返回一个新的函数。 return (...args: any[]) => { // 首先反转 `funcs` 数组,因为我们需要从右到左执行函数。 // 然后使用 `reduce` 方法来组合这些函数。 return funcs.reverse().reduce((acc, fn) => { // 在每次迭代中,将上一个函数的返回值(acc)作为当前函数(fn)的参数。 return fn(acc); }, args); // 初始值 `args` 是传递给组合函数的参数。 }; }
- 方法流程说明:
- 接受任意数量的函数作为参数。
- 返回一个新的函数,这个新函数接受任意参数。
- 当新函数被调用时,它首先反转
funcs
数组,以确保函数能够从右到左执行。 - 使用
reduce
方法应用每个函数,从最右边的函数开始,每个函数的返回值都作为下一个函数的输入。 - 最终,返回最左边函数的执行结果。
- 方法流程说明:
debounce:函数防抖
- 使用说明
- 功能描述:创建一个防抖函数,该函数会在指定的延迟时间后执行。防抖(debouncing)是一种控制函数执行频率的技术,它确保函数只在最后一次被调用后的一段时间后执行,通常用于处理像窗口调整大小或键盘输入这样的连续事件。
- 参数:配置对象(包含delay——延时)、需要防抖的函数。
- 返回值:返回防抖后的函数。
- 使用代码示例
import { debounce } from 'radash' const makeSearchRequest = (event) => { api.movies.search(event.target.value) } input.addEventListener('change', debounce({ delay: 100 }, makeSearchRequest))
- 源码解析
// 定义一个泛型函数 `debounce`。 export const debounce = <TArgs extends any[]>( // 第一个参数是一个对象,包含 `delay` 属性,它是函数延迟执行的毫秒数。 { delay }: { delay: number }, // 第二个参数 `func` 是需要被防抖的函数。 func: (...args: TArgs) => any ) => { // 初始化一个变量 `timer` 用于保存 setTimeout 的返回值。 let timer: NodeJS.Timeout | undefined = undefined // 初始化一个标志变量 `active` 来控制函数是否应该执行。 let active = true // 定义一个防抖后的函数 `debounced`。 const debounced: DebounceFunction<TArgs> = (...args: TArgs) => { // 如果 `active` 是 `true`,则执行防抖逻辑。 if (active) { // 清除之前的定时器(如果有的话)。 clearTimeout(timer) // 设置一个新的定时器,延迟 `delay` 毫秒后执行 `func`。 timer = setTimeout(() => { // 检查 `active` 是否仍然是 `true`,如果是,则执行 `func`。 active && func(...args) // 执行后,将 `timer` 设置为 `undefined`。 timer = undefined }, delay) } else { // 如果 `active` 是 `false`,则立即执行 `func`,不使用防抖逻辑。 func(...args) } } // 为 `debounced` 函数添加一个方法 `isPending`,用于检查是否有等待执行的 `func`。 debounced.isPending = () => { return timer !== undefined } // 为 `debounced` 函数添加一个方法 `cancel`,用于取消执行 `func`。 debounced.cancel = () => { active = false } // 为 `debounced` 函数添加一个方法 `flush`,用于立即执行 `func`。 debounced.flush = (...args: TArgs) => func(...args) // 返回防抖后的函数 `debounced`。 return debounced }
- 方法流程说明:
- 接受一个包含延迟时间
delay
的对象和一个需要被防抖的函数func
作为参数。 - 返回一个新的函数
debounced
,这个函数在被连续调用时会取消之前的调用并重新计时。 - 如果在延迟时间
delay
内没有再次被调用,func
将被执行。 debounced
函数提供了三个额外的方法:isPending
检查是否有等待执行的函数,cancel
取消等待执行的函数,flush
立即执行函数。- 使用
debounce
函数可以帮助你控制函数的执行频率,尤其是在处理频繁触发的事件时。
- 接受一个包含延迟时间
- 方法流程说明:
memo:创建一个记忆化(memoized)版本的给定函数
- 使用说明
0. 功能描述:记忆化是一种优化技术,它存储函数执行的结果,并在后续调用中重用这个结果,以避免重复执行相同计算。这种技术特别适用于计算成本高昂或调用频繁的函数。
0. 参数:需要被记忆化的函数、可选配置对象。
0. 返回值:返回一个记忆版本的函数。 - 使用代码示例
import { memo } from 'radash' const timestamp = memo(() => Date.now()) const now = timestamp() const later = timestamp() now === later // => true
- 源码解析
// 定义一个泛型函数 `memo`。 export const memo = <TArgs extends any[], TResult>( // 第一个参数 `func` 是需要被记忆化的函数。 func: (...args: TArgs) => TResult, // 第二个参数 `options` 是一个可选的配置对象。 options: { // `key` 是一个可选的函数,用于根据函数的参数生成一个唯一的缓存键。 key?: (...args: TArgs) => string // `ttl` 是一个可选的数字,表示缓存的生存时间(以毫秒为单位)。 ttl?: number } = {} // 如果没有提供 `options`,则使用一个空对象作为默认值。 ) => { // `memo` 函数返回一个记忆化版本的 `func`。 return memoize({}, func, options.key ?? null, options.ttl ?? null) as ( // 返回的函数类型与原始 `func` 相同。 ...args: TArgs ) => TResult }
- 方法流程说明:
memo
函数接受一个函数func
和一个可选的配置对象options
。options
对象包含两个可选的属性:key
和ttl
。key
是一个函数,用于生成缓存键;ttl
是缓存的生存时间。memo
函数调用memoize
函数(在代码片段中未定义)来创建一个记忆化版本的func
。memoize
函数接受一个缓存对象、原始函数func
、键生成函数options.key
和生存时间options.ttl
。- 如果没有提供
options.key
,则使用null
作为默认值;如果没有提供options.ttl
,也使用null
作为默认值。 memo
函数返回一个记忆化版本的func
,它的类型与原始func
相同。
- 方法流程说明:
partial :创建一个偏应用的部分函数(允许你预先填充一些参数,只需传入剩余参数)
- 使用说明
- 功能描述:创建一个新函数,这个新函数是原始函数
fn
的偏应用版本。偏应用(Partial Application)是一种函数式编程技术,它允许你预先填充一些参数,并返回一个新函数,这个新函数只需要剩余的参数就可以执行。 - 参数:原始函数,原始参数数组。
- 返回值:返回一个接受剩余的参数数组
rest
的新函数。
- 功能描述:创建一个新函数,这个新函数是原始函数
- 使用代码示例
import { partial } from 'radash' const add = (a: number, b: number) => a + b const addFive = partial(add, 5) addFive(2) // => 7
- 源码解析
// 定义一个泛型函数 `partial`。 export const partial = <T extends any[], TA extends Partial<T>, R>( // `fn` 是原始函数,它接受一个参数数组 `T` 并返回一个结果 `R`。 fn: (...args: T) => R, // 使用展开运算符接收一个或多个预先填充的参数数组 `args`,其类型为 `TA`。 // `TA` 是原始参数数组 `T` 的部分类型。 ...args: TA ) => { // `partial` 函数返回一个新函数,这个新函数接受剩余的参数数组 `rest`。 return (...rest: RemoveItemsInFront<T, TA>) => // 新函数调用原始函数 `fn`,首先展开预先填充的参数 `args`,然后展开剩余的参数 `rest`。 // 使用类型断言 `as T` 确保参数数组的类型正确。 fn(...([...args, ...rest] as T)) }
- 这段代码中使用了几个未定义的函数和类型,如
tryit
、list
、fork
和sort
,以及类型WorkItemResult<K>
。我们可以假设这些函数和类型具有以下功能:partial
函数接受一个原始函数fn
和一系列预先填充的参数args
。- 返回一个新函数,这个新函数接受剩余的参数
rest
。 - 当新函数被调用时,它将预先填充的参数
args
和剩余的参数rest
合并成一个完整的参数数组,并调用原始函数fn
。 - 原始函数
fn
被执行,并返回结果。 - 类型
RemoveItemsInFront<T, TA>
是一个类型操作,它从类型T
中移除与TA
对应的项。它在这段代码中没有定义,我们可以假设它的作用是确保rest
参数只包含原始函数fn
还需要的参数。
- 这段代码中使用了几个未定义的函数和类型,如
partob:创建一个偏应用的部分函数(跟partial类似,不过接收参数不一样)
- 使用说明
- 功能描述:创建一个新的函数,该函数是原始函数
fn
的偏应用版本。这个新函数将接受一个对象参数restobj
,它包含了原始函数fn
所需参数的剩余部分,然后将restobj
与预先填充的参数对象argobj
合并后调用fn
。 - 参数:原始函数、预先填充的参数对象。
- 返回值:返回一个接收剩余参数对象
restobj
的新函数。
- 功能描述:创建一个新的函数,该函数是原始函数
- 使用代码示例
import { partob } from 'radash' const add = (props: { a: number; b: number }) => props.a + props.b const addFive = partob(add, { a: 5 }) addFive({ b: 2 }) // => 7
- 源码解析
// 定义一个泛型函数 `partob`。 export const partob = <T, K, PartialArgs extends Partial<T>>( // `fn` 是原始函数,它接受一个类型为 `T` 的参数对象,并返回一个类型为 `K` 的结果。 fn: (args: T) => K, // `argobj` 是一个预先填充的参数对象,其类型为 `PartialArgs`,它是原始参数对象 `T` 的部分类型。 argobj: PartialArgs ) => { // `partob` 函数返回一个新的函数,该函数接受一个类型为 `Omit<T, keyof PartialArgs>` 的参数对象 `restobj`。 // `Omit<T, keyof PartialArgs>` 表示从 `T` 中省略掉 `PartialArgs` 中已有的键,只保留剩余的键。 return (restobj: Omit<T, keyof PartialArgs>): K => // 新函数调用原始函数 `fn`,传入合并后的参数对象。 fn({ // 使用展开运算符将 `argobj` 和 `restobj` 合并为一个新对象。 // 这里的类型断言确保合并后的对象符合原始参数对象 `T` 的类型。 ...(argobj as Partial<T>), ...(restobj as Partial<T>) } as T) }
- 方法流程说明:
partob
函数接受一个原始函数fn
和预先填充的参数对象argobj
。- 返回一个新的函数,该函数接受剩余参数对象
restobj
。 - 当新函数被调用时,它将预先填充的参数对象
argobj
和剩余参数对象restobj
合并成一个完整的参数对象,并调用原始函数fn
。 - 原始函数
fn
被执行,并返回结果。
- 方法流程说明:
proxied:创建动态代理对象
- 使用说明
- 功能描述:创建的代理对象可以拦截对其属性的访问并返回由一个处理函数
handler
产生的值。 - 参数:处理函数(该函数接受一个属性名)。
- 返回值:返回一个新的
Proxy
对象。
- 功能描述:创建的代理对象可以拦截对其属性的访问并返回由一个处理函数
- 使用代码示例
import { proxied } from 'radash' type Property = 'name' | 'size' | 'getLocation' const person = proxied((prop: Property) => { switch (prop) { case 'name': return 'Joe' case 'size': return 20 case 'getLocation' return () => 'here' } }) person.name // => Joe person.size // => 20 person.getLocation() // => here
- 源码解析
// 定义一个泛型函数 `proxied`。 export const proxied = <T, K>( // `handler` 是一个函数,接受一个属性名 `propertyName` 并返回一个类型为 `K` 的值。 handler: (propertyName: T) => K ): Record<string, K> => { // 返回一个新的 Proxy 对象。 return new Proxy( // 第一个参数是要代理的目标对象,这里是一个空对象。 {}, // 第二个参数是一个处理器对象,它定义了多种拦截操作的方法。 { // `get` 方法用于拦截对属性的读取操作。 get: (target, propertyName: any) => // 当尝试读取属性时,调用 `handler` 函数并传入属性名。 // 返回 `handler` 函数的结果作为属性的值。 handler(propertyName) } ) }
- 方法流程说明:
proxied
函数接受一个handler
函数作为参数。- 使用
new Proxy()
创建一个新的Proxy
对象,它代理一个空对象。 - 定义
Proxy
对象的get
方法,用于拦截对代理对象属性的读取操作。 - 当尝试读取任何属性时,
get
方法调用handler
函数,传入被读取的属性名称。 handler
函数返回的值作为属性的值返回给调用者。- 返回创建的
Proxy
对象,它的类型为Record<string, K>
,表示一个对象,其属性名为字符串,属性值的类型为K
。
- 方法流程说明:
throttle :函数节流
- 使用说明
- 功能描述:节流(Throttling)是一种控制函数调用频率的技术,它确保函数在指定的时间间隔内最多只执行一次。这通常用于限制频繁触发的事件(如窗口调整大小、滚动等)的处理函数。
- 参数:对象({interval})—— 触发间隔、需要节流的函数。
- 返回值:返回节流后的函数。
- 使用代码示例
import { throttle } from 'radash' const onMouseMove = () => { rerender() } addEventListener('mousemove', throttle({ interval: 200 }, onMouseMove))
- 源码解析
// 定义一个泛型函数 `throttle`。 export const throttle = <TArgs extends any[]>( // 第一个参数是一个对象,包含 `interval` 属性,它是函数执行之间的毫秒间隔。 { interval }: { interval: number }, // 第二个参数 `func` 是需要被节流的函数。 func: (...args: TArgs) => any ) => { // 初始化一个标志变量 `ready`,表示函数是否准备好执行。 let ready = true // 初始化一个变量 `timer` 用于保存 setTimeout 的返回值。 let timer: NodeJS.Timeout | undefined = undefined // 定义一个节流后的函数 `throttled`。 const throttled: ThrottledFunction<TArgs> = (...args: TArgs) => { // 如果函数尚未准备好执行,直接返回。 if (!ready) return // 调用 `func` 并传入参数。 func(...args) // 设置 `ready` 为 `false`,防止函数在间隔时间内再次执行。 ready = false // 设置一个定时器,在 `interval` 毫秒后将 `ready` 重新设为 `true`,允许函数再次执行。 timer = setTimeout(() => { ready = true // 定时器执行完毕后,清除 `timer`。 timer = undefined }, interval) } // 为 `throttled` 函数添加一个方法 `isThrottled`,用于检查函数是否处于节流状态。 throttled.isThrottled = () => { return timer !== undefined } // 返回节流后的函数 `throttled`。 return throttled }
- 方法流程说明:
throttle
函数接受一个配置对象(包含interval
属性)和一个需要被节流的函数func
作为参数。- 返回一个新的函数
throttled
,该函数在被连续调用时会限制函数的执行频率。 - 当
throttled
函数被调用时,如果ready
是true
(即函数准备好执行),func
将被执行。 - 执行
func
后,ready
设置为false
,并通过setTimeout
设置一个定时器,定时器在interval
毫秒后执行,将ready
设置回true
。 - 如果
throttled
函数在定时器完成之前再次被调用,由于ready
是false
,func
不会被执行。 throttled
函数提供了一个额外的方法isThrottled
,用于检查函数是否正在等待下一次执行的间隔。
- 方法流程说明:
Number相关
inRange :检查给定数字是否在两个数字之间
- 使用说明
- 功能描述:检查给定数字是否在两个数字之间。判断包含起始值。不包含结束值。范围的开始和结束可以是升序或降序。如果未指定结束值,则将其设置为起始值。并且把起始值设置为0。
- 参数:需要检查的值,判断的起始值,[判断的结束值]。
- 返回值:在范围内返回
true
,否则返回false
。
- 使用代码示例
import { inRange } from 'radash' inRange(10, 0, 20) // true inRange(9.99, 0, 10) // true inRange(Math.PI, 0, 3.15) // true inRange(10, 10, 20) // true inRange(10, 0, 10) // false inRange(1, 2) // true inRange(1, 0) // false
- 源码解析
// 定义一个名为 `inRange` 的函数。 function inRange(number, start, end) { // 首先检查传入的参数类型是否正确:`number` 和 `start` 必须是数字类型, // `end` 要么是未定义,要么是数字类型。 const isTypeSafe = typeof number === "number" && typeof start === "number" && (typeof end === "undefined" || typeof end === "number"); // 如果参数类型不正确,直接返回 `false`。 if (!isTypeSafe) { return false; } // 如果 `end` 参数未提供(即 `undefined`),则将 `end` 设置为 `start` 的值, // 而将 `start` 设置为 `0`。这样就创建了一个从 `0` 到 `start` 的范围。 if (typeof end === "undefined") { end = start; start = 0; } // 检查 `number` 是否在 `start` 和 `end` 指定的范围内。 // 使用 `Math.min` 和 `Math.max` 来确保 `start` 和 `end` 的顺序正确, // 即使它们被反向提供(例如 `end` 小于 `start`)。 return number >= Math.min(start, end) && number < Math.max(start, end); }
- 方法流程说明:
- 首先验证所有参数的类型。如果
number
或start
不是数字,或者end
不是数字且不是undefined
,函数返回false
。 - 如果
end
参数未定义,函数将其解释为只提供了一个参数的情况,即一个从0
到start
的范围。 - 函数计算
number
是否大于等于范围的最小值(Math.min(start, end)
)并且小于范围的最大值(Math.max(start, end)
)。 - 如果
number
在这个范围内,函数返回true
;否则返回false
。
- 首先验证所有参数的类型。如果
- 方法流程说明:
toFloat :可能的情况下,将一个值转为浮点值
- 使用说明
- 功能描述:将一个值转换为浮点数。如果转换失败或者提供的值是
null
或undefined
,函数将返回默认值。 - 参数:需要转换的值、默认值。
- 返回值:能转换则返回转换后的浮点数,否则返回传入的默认值。
- 功能描述:将一个值转换为浮点数。如果转换失败或者提供的值是
- 使用代码示例
import { toFloat } from 'radash' toFloat(0) // => 0.0 toFloat(null) // => 0.0 toFloat(null, 3.33) // => 3.33
- 源码解析
// 定义一个名为 `toFloat` 的函数。 const toFloat = (value, defaultValue) => { // 如果没有提供 `defaultValue`,则使用 `0` 作为默认值。 // `void 0` 是 `undefined` 的一种安全写法。 const def = defaultValue === void 0 ? 0 : defaultValue; // 如果 `value` 是 `null` 或 `undefined`,返回默认值 `def`。 if (value === null || value === void 0) { return def; } // 尝试将 `value` 转换为浮点数。 const result = parseFloat(value); // 如果转换结果是 `NaN`(Not-a-Number),返回默认值 `def`。 // 否则,返回转换后的浮点数 `result`。 return isNaN(result) ? def : result; };
- 方法流程说明:
- 首先检查
defaultValue
是否提供,如果没有提供,则将def
设置为0
。 - 检查
value
是否是null
或undefined
,如果是,返回def
。 - 使用
parseFloat
函数尝试将value
转换为浮点数。 - 使用
isNaN
函数检查转换结果是否为NaN
。 - 如果结果是
NaN
,返回def
;如果转换成功,返回浮点数结果result
。
- 首先检查
- 方法流程说明:
toInt:可能的情况下,将一个值转为整数
- 使用说明
- 功能描述:将一个值转换为整数。如果转换失败或者提供的值是
null
或undefined
,函数将返回默认值。 - 参数:需要转换的值、默认值。
- 返回值:能转换则返回转换后的整数,否则返回传入的默认值。
- 功能描述:将一个值转换为整数。如果转换失败或者提供的值是
- 使用代码示例
import { toInt } from 'radash' toInt(0) // => 0 toInt(null) // => 0 toInt(null, 3) // => 3
- 源码解析
// 定义一个名为 `toFloat` 的函数。 const toFloat = (value, defaultValue) => { // 如果没有提供 `defaultValue`,则使用 `0` 作为默认值。 // `void 0` 是 `undefined` 的一种安全写法。 const def = defaultValue === void 0 ? 0 : defaultValue; // 如果 `value` 是 `null` 或 `undefined`,返回默认值 `def`。 if (value === null || value === void 0) { return def; } // 尝试将 `value` 转换为浮点数。 const result = parseFloat(value); // 如果转换结果是 `NaN`(Not-a-Number),返回默认值 `def`。 // 否则,返回转换后的浮点数 `result`。 return isNaN(result) ? def : result; };
- 方法流程说明:
- 首先检查
defaultValue
是否提供,如果没有提供,则将def
设置为0
。 - 检查
value
是否是null
或undefined
,如果是,返回def
。 - 使用
parseFloat
函数尝试将value
转换为浮点数。 - 使用
isNaN
函数检查转换结果是否为NaN
。 - 如果结果是
NaN
,返回def
;如果转换成功,返回浮点数结果result
。
- 首先检查
- 方法流程说明:
写在后面
- 老实说能看到这里的人呢,都是美丽帅气有眼光又多金的人才,感谢阅读到最后,Peace and Love!
- 后续我们会继续分享
Radash
库中其他方法的使用和源码解析。 - 大家有任何问题或见解,欢迎评论区留言交流和批评指正!!!
- 你的每一个点赞和收藏都是作者写作的动力!!!(求个点赞《《小声逼逼》》)
- 点击访问:radash官网