浅析js中的纯函数、高阶函数、记忆函数、偏函数
2021-02-09 05:15
标签:result 检测 优点 log llb 严格 逻辑 当前时间 check 上周分享文档中遇到几个关键名称,纯函数、高阶函数、记忆函数、偏函数....,这里做一下解析与举例 纯函数是函数式编程中非常重要的一个概念,简单来说,就是一个函数的返回结果只依赖于它的参数,并且在执行过程中没有副作用,我们就把这个函数叫做纯函数 一个函数,如果符合以下两个特点,那么它就可以称之为纯函数 对于相同的输入,永远得到相同的输出(函数的返回结果只依赖于它的参数) 没有任何可观察到的副作用(函数执行过程中没有副作用) 上面代码中,greet函数不是一个纯函数,因为它的返回结果依赖外部变量greeting,因为greeting是有可能变化的,所以我们不能保证greet(‘World‘)的值永远是 现在,greet的返回结果只依赖于它的参数greeting和name,就是说,只要代码不变,greet(‘Hi‘, ‘Savo‘)的返回值永远是 这就是纯函数的第一个条件:函数的返回结果只依赖于它的参数 没有副作用的意思是,这个函数的运行,不会修改外部的状态,即不会污染外部作用域 可见,执行函数的时候会修改到 flag 的值(注意:如果函数没有任何返回值,那么它很可能就具有副作用) 由于定义的函数改变的值在函数作用域之外,导致这个函数成为“不纯”的函数 上面的代码,我们只计算了作用域内的局部变量,没有任何作用域外部的变量被改变,因此这个函数是“纯函数” 除了修改外部的变量,一个函数在执行过程中还有很多方式产生外部可观察的变化,比如说调用 DOM API 修改页面,或者你发送了 Ajax 请求,还有调用 纯函数很严格,也就是说你几乎除了计算数据以外什么都不能干,计算的时候还不能依赖除了函数参数以外的数据 再来看看JavaScript中常用的两个方法slice和splice 可以看到,slice和splice的作用是大致相同的,但是splice改变了原数组,而slice却没有,实际开发中,slice这种不改变原数组的方式更安全一些,改变原始数组,是一种副作用 纯函数可以根据输入来做缓存,实现缓存的是一种叫做 可复用性/可移植性 纯函数仅依赖于传入的参数,这意味着你可以随意将这个函数移植到别的代码中,只需要提供踏需要的参数即可。如果是非纯函数,有可能你需要一根香蕉,却需要将整个香蕉树搬过去 可测试性 纯函数非常容易进行单元测试,因为不需要考虑上下文环境,只需要考虑输入和输出 并行代码 纯函数是健壮的,改变执行次序不会对系统造成影响,因此纯函数的操作可以并行执行 英文叫Higher-order function。JavaScript的函数其实都指向某个变量。既然变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数 函数可以作为参数传递(接受一个或多个函数作为输入) 函数可以作为返回值输出(输出一个函数) 可以将高阶函数理解为函数之上的函数,它很常用,比如常见的回调函数 在ajax异步请求的过程中,回调函数使用的非常频繁 在不确定请求返回的时间时,将callback回调函数当成参数传入 待请求完成后执行callback函数 或者在众多闭包的场景中都使用到 比如 防抖函数(debounce)与节流函数(throttle) 防抖,指的是无论某个动作被连续触发多少次,直到这个连续动作停止一定到延时后,才会被当作一次来执行,即多合一 比如一个输入框接受用户不断输入,输入结束后才开始搜索 以页面滚动作为例子,可以定义一个防抖函数,接受一个自定义的 delay值,作为判断停止的时间标识 记忆化(memoization)是一种构建函数的处理过程,能够记住上次计算结果的函数。在实现中,可以这样进行处理:当函数计算得到结果时,就将该结果按照参数存储起来。采取这种方式时,如果另外一个调用也使用相同的参数,我们则可以直接返回上次存储的结果而不是再计算一遍 显而易见,像这样避免既重复又复杂的计算可以显著提高性能。对于动画中的计算、搜索不经常变化的数据或任何耗时的数学计算来说,记忆化这种方式是十分有用的 isPrime函数是一个自记忆素数检测函数,每当它被调用时 首先,检查它的answers属性来确认是否已经有自记忆的缓存,如果没有,创建一个 接下来,检查参数之前是否已经被缓存过,如果在缓存中找到该值,直接返回缓存的结果 如果参数是一个全新的值,进行正常的素数检测 最后,存储并返回计算值 由于函数调用时会寻找之前调用所得到的值,所以用户最终会乐于看到所获得的性能收益 它不需要执行任何特殊请求,也不需要做任何额外初始化,就能顺利进行工作 任何类型的缓存都必然会为性能牺牲内存 缓存逻辑不应该和业务逻辑混合,一个方法只需要把一件事情做好 对记忆函数很难做负载测试或估算算法复杂度,因为结果依赖于函数之前的输入 偏函数属于函数式编程的一部分,使用偏函数可以通过有效地“冻结”那些预先确定的参数,来缓存函数参数,然后在运行时,当获得需要的剩余参数后,可以将他们解冻,传递到最终的参数中,从而使用最终确定的所有参数去调用函数 简而言之,偏函数是 JS 函数柯里化运算的一种特定应用场景,就是把一个函数的某些参数先固化,也就是设置默认值,返回一个新的函数,在新函数中继续接收剩余参数,这样调用这个新函数会更简单 将一些函数组合封装到一个函数中,调用时可以按顺序实现全部功能 在组合函数 compose 中,依次执行 toUpperCase、split、reverse、join、add 实现全部功能 当然有更优雅的写法,通过数组自带的方法实现 浅析js中的纯函数、高阶函数、记忆函数、偏函数 标签:result 检测 优点 log llb 严格 逻辑 当前时间 check 原文地址:https://www.cnblogs.com/zhazhanitian/p/13063999.html前言
纯函数
简介
定义
解析--函数的返回结果只依赖于它的参数
let greeting = ‘Hello‘
function greet (name) {
return greeting + ‘ ‘ + name
}
console.log(greet(‘World‘)) // Hello World
Hello World
。虽然greet函数的代码没有变化,传入的参数也没有变化,但它的返回值是不可预料的接着做以下修改
function greet (greeting, name) {
return greeting + ‘ ‘ + name
}
console.log(greet(‘Hi‘, ‘Savo‘)) // Hi Savo
Hi Savo
解析--没有副作用
const user = {
name: ‘Nick Brown‘
}
let flag = false
function checkData (user) {
if (user.name.length > 4) {
flag = true
}
}
const user = {
name: ‘Nick Brown‘
}
function checkData (user) {
return user.name.length > 4
}
const flag = checkData(user)
window.reload
刷新浏览器,甚至是 console.log
往控制台打印数据也是副作用特殊例子
const arr1 = [0, 1, 2, 3, 4, 5, 6]
const arr2 = [0, 1, 2, 3, 4, 5 ,6]
const spliceArr = arr1.splice(0, 2)
const sliceArr = arr2.slice(0, 2)
console.log(‘arr1: ‘, arr1)
console.log(‘spliceArray: ‘, spliceArr)
console.log(‘arr2: ‘, arr2)
console.log(‘sliceArray: ‘, sliceArr)
// 执行结果
array1: 2,3,4,5,6
spliceArray: 0,1
array2: 0,1,2,3,4,5,6
sliceArray: 0,1
场景
memorize
的技术,例Vue 源码/**
* Create a cached version of a pure function.
* 只适用于缓存 接收一个字符串为参数的 fn
*/
export function cached (fn) {
const cache = Object.create(null)
return function cachedFn (str) {
const hit = cache[str]
return hit || (cache[str] = fn(str))
}
}
/**
* Capitalize a string.
*/
export const capitalize = cached((str) => {
return str.charAt(0).toUpperCase() + str.slice(1)
})
优点
高阶函数
简介
特点
解析
const getData = function(url, callback) {
ajax.get(url, function(data){
callback(data)
})
}
防抖函数
// 这个是用来获取当前时间戳的
function now() {
return +new Date()
}
/**
* 防抖函数,返回函数连续调用时,空闲时间必须大于或等于 wait,func 才会执行
*
* @param {function} func 回调函数
* @param {number} wait 表示时间窗口的间隔
* @param {boolean} immediate 设置为ture时,是否立即调用函数
* @return {function} 返回客户调用函数
*/
function debounce (func, wait = 50, immediate = true) {
let timer, context, args
// 延迟执行函数
const later = () => setTimeout(() => {
// 延迟函数执行完毕,清空缓存的定时器序号
timer = null
// 延迟执行的情况下,函数会在延迟函数中执行
// 使用到之前缓存的参数和上下文
if (!immediate) {
func.apply(context, args)
context = args = null
}
}, wait)
// 这里返回的函数是每次实际调用的函数
return function(...params) {
// 如果没有创建延迟执行函数(later),就创建一个
if (!timer) {
timer = later()
// 如果是立即执行,调用函数
// 否则缓存参数和调用上下文
if (immediate) {
func.apply(this, params)
} else {
context = this
args = params
}
// 如果已有延迟执行函数(later),调用的时候清除原来的并重新设定一个
// 这样做延迟函数会重新计时
} else {
clearTimeout(timer)
timer = later()
}
}
}
记忆函数
简介
实例分析
// 自记忆素数检测函数
function isPrime (value) {
// 创建缓存
if (!isPrime.answers) {
isPrime.answers = {};
}
// 检查缓存的值
if (isPrime.answers[value] !== undefined) {
return isPrime.answers[value];
}
// 0和1不是素数
var prime = value !== 0 && value !== 1;
// 检查是否为素数
for (var i = 2; i
优缺点
优点
缺陷
偏函数
简介
常见场景试例
function toUpperCase(str) {
return str.toUpperCase() // 将字符串变成大写
}
function add(str) {
return str + ‘!!!‘ // 将字符串拼接
}
function split(str) {
return str.split(‘‘) // 将字符串拆分为数组
}
function reverse(arr) {
return arr.reverse() // 将数组逆序
}
function join(arr) {
return arr.join(‘-‘) // 将数组按‘-‘拼接成字符串
}
function compose() {
const args = Array.prototype.slice.call(arguments) // 类数组转换为数组
const len = args.length - 1 // 最后一个参数的索引
return function(x) {
let result = args[len](x) // 执行最后一个函数的结果
while(len--) {
result = args[len](result) // 执行每个函数的结果
}
return result
}
}
const f = compose(add, join, reverse, split, toUpperCase)
console.log( f(‘cba‘) ) // A-B-C!!!
const compose2 = (...args) => x => args.reduceRight((result, cb) => cb(res), x)
const f = compose2(add, join, reverse, split, toUpperCase)
console.log( f(‘cba‘) ) // A-B-C!!!
文章标题:浅析js中的纯函数、高阶函数、记忆函数、偏函数
文章链接:http://soscw.com/index.php/essay/52943.html