js 高阶函数之柯里化

博客地址:https://ainyi.com/74

定义

在计算机科学中,柯里化(Currying)是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数且返回结果的新函数的技术

就是只传递给函数某一部分参数来调用,返回一个新函数去处理剩下的参数(==闭包==)

常用的封装成 add 函数

// reduce 方法
const add = (...args) => args.reduce((a, b) => a + b)// 传入多个参数,执行 add 函数
add(1, 2) // 3// 假设有一个 currying 函数
let sum = currying(params)
sum(1)(3) // 4

实际应用

延迟计算

部分求和例子,说明了延迟计算的特点

const add = (...args) => args.reduce((a, b) => a + b)// 简化写法
function currying(func) {const args = []return function result(...rest) {if (rest.length === 0) {return func(...args)} else {args.push(...rest)return result}}
}const sum = currying(add)sum(1, 2)(3)    // 未真正求值,收集参数的和
sum(4)      // 未真正求值,收集参数的和
sum()       // 输出 10

上面的代码理解:先定义 add 函数,然后 currying 函数就是用==闭包==把传入参数保存起来,当传入参数的数量足够执行函数时,就开始执行函数

上面的 currying 函数是一种简化写法,判断传入的参数长度是否为 0,若为 0 执行函数,否则收集参数到 args 数组

另一种常见的应用是 bind 函数,我们看下 bind 的使用

let obj = {name: 'Krry'
}
const fun = function () {console.log(this.name)
}.bind(obj)fun() // Krry

这里 bind 用来改变函数执行时候的上下文==this==,但是函数本身并不执行,所以本质上是延迟计算,这一点和 call / apply 直接执行有所不同

动态创建函数

有一种典型的应用情景是这样的,每次调用函数都需要进行一次判断,但其实第一次判断计算之后,后续调用并不需要再次判断,这种情况下就非常适合使用柯里化方案来处理

即第一次判断之后,动态创建一个新函数用于处理后续传入的参数,并返回这个新函数。当然也可以使用惰性函数来处理,本例最后一个方案会介绍

我们看下面的这个例子,在 DOM 中添加事件时需要兼容现代浏览器和 IE 浏览器(IE < 9),方法就是对浏览器环境进行判断,看浏览器是否支持,简化写法如下

// 简化写法
function addEvent (type, el, fn, capture = false) {if (window.addEventListener) {el.addEventListener(type, fn, capture);}else if(window.attachEvent) {el.attachEvent('on' + type, fn);}
}

但是这种写法有一个问题,就是每次添加事件都会调用做一次判断,比较麻烦

可以利用闭包和立即调用函数表达式(IIFE)来实现只判断一次,后续都无需判断

const addEvent = (function(){if (window.addEventListener) {return function (type, el, fn, capture) { // 关键el.addEventListener(type, fn, capture)}}else if(window.attachEvent) {return function (type, el, fn) { // 关键el.attachEvent('on' + type, fn)}}
})()

上面这种实现方案就是一种典型的柯里化应用,在第一次的 if...else if... 判断之后完成第一次计算,然后动态创建返回新的函数用于处理后续传入的参数

这样做的好处就是之后调用之后就不需要再次调用计算了

当然可以使用惰性函数来实现这一功能,原理很简单,就是重写函数

function addEvent (type, el, fn, capture = false) {// 重写函数if (window.addEventListener) {addEvent = function (type, el, fn, capture) {el.addEventListener(type, fn, capture);}}else if(window.attachEvent) {addEvent = function (type, el, fn) {el.attachEvent('on' + type, fn);}}// 执行函数,有循环爆栈风险addEvent(type, el, fn, capture); 
}

第一次调用 addEvent 函数后,会进行一次环境判断,在这之后 addEvent 函数被重写,所以下次调用时就不会再次判断环境

参数复用

我们知道调用 toString() 可以获取每个对象的类型,但是不同对象的 toString() 有不同的实现

所以需要通过 Object.prototype.toString() 来获取 Object 上的实现

同时以 call() / apply() 的形式来调用,并传递要检查的对象作为第一个参数

例如下面这个例子

function isArray(obj) { return Object.prototype.toString.call(obj) === '[object Array]';
}function isNumber(obj) {return Object.prototype.toString.call(obj) === '[object Number]';
}function isString(obj) {return Object.prototype.toString.call(obj) === '[object String]';
}// Test
isArray([1, 2, 3])  // true
isNumber(123)       // true
isString('123')     // true

但是上面方案有一个问题,那就是每种类型都需要定义一个方法,这里我们可以使用 bind 来扩展,优点是可以直接使用改造后的 toStr

const toStr = Function.prototype.call.bind(Object.prototype.toString);// 改造前直接调用
[1, 2, 3].toString()    // "1,2,3"
'123'.toString()    // "123"
123.toString()      // SyntaxError: Invalid or unexpected token
Object(123).toString()  // "123"// 改造后调用 toStr
toStr([1, 2, 3])    // "[object Array]"
toStr('123')        // "[object String]"
toStr(123)      // "[object Number]"
toStr(Object(123))  // "[object Number]"

上面例子首先使用 Function.prototype.call 函数指定一个 this 值,然后 .bind 返回一个新的函数,始终将 Object.prototype.toString 设置为传入参数,其实等价于 Object.prototype.toString.call()

实现 Currying 函数

可以理解所谓的柯里化函数,就是封装==一系列的处理步骤==,通过闭包将参数集中起来计算,最后再把需要处理的参数传进去

实现原理就是用闭包把传入参数保存起来,当传入参数的数量足够执行函数时,就开始执行函数

上面延迟计算部分已经实现了一个简化版的 Currying 函数

下面实现一个更加健壮的 Currying 函数

function currying(fn, length) {// 第一次调用获取函数 fn 参数的长度,后续调用获取 fn 剩余参数的长度length = length || fn.lengthreturn function (...args) { // 返回一个新函数,接收参数为 ...args// 新函数接收的参数长度是否大于等于 fn 剩余参数需要接收的长度return args.length >= length? fn.apply(this, args) // 满足要求,执行 fn 函数,传入新函数的参数: currying(fn.bind(this, ...args), length - args.length)// 不满足要求,递归 currying 函数// 新的 fn 为 bind 返回的新函数,新的 length 为 fn 剩余参数的长度}
}// Test
const fn = currying(function(a, b, c) {console.log([a, b, c]);
})fn("a", "b", "c")   // ["a", "b", "c"]
fn("a", "b")("c")   // ["a", "b", "c"]
fn("a")("b")("c")   // ["a", "b", "c"]
fn("a")("b", "c")   // ["a", "b", "c"]

上面使用的是 ES5 和 ES6 的混合语法

那如果不想使用 call/apply/bind 这些方法呢,自然是可以的,看下面的 ES6 极简写法,更加简洁也更加易懂

const currying = fn =>judge = (...args) =>args.length >= fn.length? fn(...args): (...arg) => judge(...args, ...arg)// Test
const fn = currying(function(a, b, c) {console.log([a, b, c]);
})fn("a", "b", "c") // ["a", "b", "c"]
fn("a", "b")("c") // ["a", "b", "c"]
fn("a")("b")("c") // ["a", "b", "c"]
fn("a")("b", "c") // ["a", "b", "c"]

如果还很难理解,看下面例子

function currying(fn, length) {length = length || fn.length;     return function (...args) {           return args.length >= length    ? fn.apply(this, args)          : currying(fn.bind(this, ...args), length - args.length) }
}const add = currying(function(a, b, c) {console.log([a, b, c].reduce((a, b) => a + b))
})add(1, 2, 3) // 6
add(1, 2)(3) // 6
add(1)(2)(3) // 6
add(1)(2, 3) // 6

扩展:函数参数 length

函数 currying 的实现中,使用了 fn.length 来表示函数参数的个数,那 fn.length 表示函数的所有参数个数吗?并不是

函数的 length 属性获取的是形参的个数,但是形参的数量不包括剩余参数个数,而且仅包括第一个具有默认值之前的参数个数,看下面的例子

((a, b, c) => {}).length; // 3((a, b, c = 3) => {}).length; // 2 ((a, b = 2, c) => {}).length; // 1 ((a = 1, b, c) => {}).length; // 0 ((...args) => {}).length; // 0const fn = (...args) => {console.log(args.length);
} 
fn(1, 2, 3) // 3

所以在柯里化的场景中,不建议使用 ES6 的函数参数默认值

const fn = currying((a = 1, b, c) => {console.log([a, b, c])
})fn() // [1, undefined, undefined]fn()(2)(3) // Uncaught TypeError: fn(...) is not a function

我们期望函数 fn 输出 1, 2, 3,但是实际上调用柯里化函数时 ((a = 1, b, c) => {}).length === 0

所以调用 fn() 时就已经执行并输出了 1, undefined, undefined,而不是理想中的返回闭包函数

所以后续调用 fn()(2)(3) 将会报错

小结&链接

定义:柯里化是一种将使用多个参数的函数转换成一系列使用一个参数的函数,并且返回接受余下的参数而且返回结果的新函数的技术

实际应用

  1. 延迟计算:部分求和、bind 函数
  2. 动态创建函数:添加监听 addEvent、惰性函数
  3. 参数复用:Function.prototype.call.bind(Object.prototype.toString)

实现 Currying 函数:用闭包把传入参数保存起来,当传入参数的数量足够执行函数时,就开始执行函数

函数参数 length:获取的是形参的个数,但是形参的数量不包括剩余参数个数,而且仅包括==第一个参数有默认值之前的参数个数==

参考文章:JavaScript专题之函数柯里化

博客地址:https://ainyi.com/74

转载于:https://www.cnblogs.com/ainyi/p/10918175.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/424715.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Breadth-first Search 】专题3

529 Minesweeper 输入&#xff1a;一个二维矩阵&#xff0c;一些修改规则。  如果点到一个隐藏的地雷M&#xff0c;把它改为X&#xff0c;游戏结束  如果点到一个E&#xff0c;且其周围8邻接的范围没有地雷&#xff0c;那么应该把8邻接的范围的格子全部翻开为E  如果翻开的…

第十五期:一个用户至少“值”100美元,美国最“贵”数据法案CCPA明年初实行!

还在急于应对欧洲GDPR&#xff08;General Data Protection Regulation&#xff0c;通用数据保护条例&#xff09;&#xff1f;那你就OUT了&#xff01; 作者&#xff1a;文摘菌 大数据文摘出品 作者&#xff1a;刘俊寰 还在急于应对欧洲GDPR(General Data Protection Regul…

【数据结构与算法】【算法思想】【推荐系统】向量空间

背景知识 欧几里得的距离公式 推荐系统方法 应用 如何实现一个简单的音乐推荐系统&#xff1f; 1. 基于相似用户做推荐 找到跟你口味偏好相似的用户&#xff0c;把他们爱听的歌曲推荐给你&#xff1b; 2. 基于相似歌曲做推荐 找出跟你喜爱的歌曲特征相似的歌曲&#x…

第十六期:AWS 瘫痪:DNS 被 DDoS 攻击了 15 个小时

AWS警告客户&#xff0c;分布式攻击严重阻碍网络连接&#xff0c;殃及众多网站和应用软件&#xff0c;云巨头AWS遭到攻击后&#xff0c;今天其部分系统实际上断。网。 作者&#xff1a;佚名来源|2019-10-23 15:17 AWS警告客户&#xff0c;分布式攻击严重阻碍网络连接&#xff…

【Breadth-first Search 】752. Open the Lock

输入&#xff1a;deadends 是指针终止状态列表&#xff0c;target 是希望到达的指针状态&#xff0c;初始化指针状态是0000。 输出&#xff1a;如果指针能够到达target状态&#xff0c;则变化的最少步骤是多少。如果不能到达target状态&#xff0c;返回-1。 分析&#xff1a;指…

Spring Security在标准登录表单中添加一个额外的字段

概述 在本文中&#xff0c;我们将通过向标准登录表单添加额外字段来实现Spring Security的自定义身份验证方案。 我们将重点关注两种不同的方法&#xff0c;以展示框架的多功能性以及我们可以使用它的灵活方式。 我们的第一种方法是一个简单的解决方案&#xff0c;专注于重用现…

【数据结构与算法】【算法思想】【MySQL数据库索引】B+树

B树特点 考虑因素 支持按照区间来查找数据 磁盘 IO 操作 N叉树 树的高度就等于每次查询数据时磁盘 IO 操作的次数 在选择 m 大小的时候&#xff0c;要尽量让每个节点的大小等于一个页的大小。读取一个节点&#xff0c;只需要一次磁盘 IO 操作。&#xff08;分裂成两个节点&am…

第十七期:2019人工智能统计数字和一些重要事实

人工智能(AI)每天在以惊人的速度发展。这项技术在2018年已经取得了巨大的成功&#xff0c;简化医疗保健业的工作流程&#xff0c;降低制造业的间接费用&#xff0c;并减少教育业的行政工作量。现在是2019年&#xff0c;每天似乎都有一家新的AI初创公司冒出来&#xff0c;致力于…

Filter和Listener

javaweb三大组件1. Filter&#xff1a;过滤器 2. Listener&#xff1a;监听器3. servlet Filter&#xff1a;过滤器 1. 概念&#xff1a;* 生活中的过滤器&#xff1a;净水器,空气净化器&#xff0c;土匪、* web中的过滤器&#xff1a;当访问服务器的资源时&#xff0c;过滤器可…

【Breadth-first Search 】934. Shortest Bridge

输入&#xff1a;一个二维数组&#xff0c;每个元素的值为0/1。 规则&#xff1a;所有连在一起的1是一个岛屿&#xff0c;数组中包含2个岛屿。连在一起是指上下左右4个方向。可以将0变为1&#xff0c;将2个岛屿链接在一起。 输出&#xff1a;最小改变多少个0就可以将2个岛屿链接…

[Leetcode][第78题][JAVA][子集][位运算][回溯]

【问题描述】[中等] 【解答思路】 1. 位运算 复杂度 class Solution {List<Integer> t new ArrayList<Integer>();List<List<Integer>> ans new ArrayList<List<Integer>>();public List<List<Integer>> subsets(int[] n…

第十八期:闲鱼上哪些商品抢手?Python分析后告诉你

经常看到有朋友在闲鱼卖些小东西又或是自己擅长的一些技能&#xff0c;都能为他们带来不错的 睡后收入。 作者&#xff1a;星安果 1.目标场景 经常看到有朋友在闲鱼卖些小东西又或是自己擅长的一些技能&#xff0c;都能为他们带来不错的睡后收入。 闲鱼上大量的商品&#xf…

【Breadth-first Search 】785. Is Graph Bipartite?

输入&#xff1a;一个无向图graph。graph[i] 的值是一些节点&#xff0c;表示从i到这些节点有边。图中节点用0到graph.length-1表示。这个图没有指向自己的边&#xff0c;也就是说节点i不会有指向节点i的边。输入中不会有重复的边。 输出&#xff1a;如果这个图是bipartite&…

[Leetcode][第1143题][JAVA][最长公共子序列][LCS][动态规划]

【问题描述】[中等] 【解答思路】 时间复杂度&#xff1a;O(N^2) 空间复杂度&#xff1a;O(N^2) class Solution {public int longestCommonSubsequence(String text1, String text2) {int m text1.length(), n text2.length();int[][] dp new int[m 1][n 1];for (int i …

第十九期:程序员节,女朋友偷偷送了我这个...

10 月 24 日&#xff0c;本是个寻常的日子&#xff0c;但是在新时代的中国&#xff0c;却赋予了它新的意义。 作者&#xff1a;技术栈 10 月 24 日&#xff0c;本是个寻常的日子&#xff0c;但是在新时代的中国&#xff0c;却赋予了它新的意义。 正是广大的程序员们&#xff…

【Breadth-first Search 】103. Binary Tree Zigzag Level Order Traversal

输入&#xff1a;一颗二叉树 输出&#xff1a;这棵树的之字形层次遍历。 规则&#xff1a;之字形层次遍历是指第一层从左到右遍历&#xff0c;第二层从右到左遍历。 分析&#xff1a;这与102的区别就是有从左到右&#xff0c;从右到左遍历的交替过程。我们习惯从左到右遍历。最…

将小数点后的0去掉

decimal d 0.0500M; var C d.ToString("0.##");//0.05 转载于:https://www.cnblogs.com/macT/p/10928118.html

第二十期:黄金三步法 | 汇报时,如何让老板快速抓住重点?

对事物的归类分组是我们人类的天性&#xff0c;我们的大脑会自动将发现的所有事物以某种持续组织起来。但如何组织才能帮助我们解决工作和生活中出现的各种复杂问题?今天&#xff0c;我们请阿里高级技术专家张建飞分享他的黄金三步法。 作者&#xff1a;从码农到工匠 对事物的…

【数据结构与算法】【算法思想】【算法总结】索引结构

“基础不是100分考60分&#xff0c;而是建摩天大楼的地基。” 为什么需要索引&#xff1f; &#xff08;1&#xff09;在实际的软件开发工作的本质都可以抽象为“对数据的存储和计算”。对应到数据结构和算法中&#xff0c;那“存储”需要的就是数据结构&#xff0c;“计算”需…

【Breadth-first Search 】279. Perfect Squares

输入&#xff1a;一个非负整数n。 输出&#xff1a;这个非负整数可以写成几个完全平方数。返回这个数量。 规则&#xff1a;完全平方数可以表示为某个整数的平方。例如&#xff1a;1&#xff0c;4&#xff0c;9… 分析&#xff1a;1349 也就是说13可以写成2个完全平方数的和。我…