热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

「前端面试题系列6」明白函数的柯里化

媒介这是前端口试题系列的第6篇,你能够错过了前面的篇章,能够在这里找到:ES6中箭头函数的用法this的道理以及用法伪类与伪元素的区分及实战怎样完成一个圣杯规划?本日头条口试题和思

《「前端面试题系列6」明白函数的柯里化》

媒介

这是前端口试题系列的第 6 篇,你能够错过了前面的篇章,能够在这里找到:

  • ES6 中箭头函数的用法
  • this 的道理以及用法
  • 伪类与伪元素的区分及实战
  • 怎样完成一个圣杯规划?
  • 本日头条 口试题和思绪剖析

近来,朋侪T 在预备口试,他为一道编程题所困,向我乞助。原题以下:

// 写一个 sum 要领,当运用下面的语法挪用时,能一般事情
console.log(sum(2, 3)); // Outputs 5
console.log(sum(2)(3)); // Outputs 5

这道题要考核的,就是对函数柯里化的明白。让我们先来剖析一下问题的请求:

  • 假如通报两个参数,我们只需将它们相加并返回。
  • 不然,我们假定它是以sum(2)(3)的情势被挪用的,所以我们返回一个匿名函数,它将通报给sum()(在本例中为2)的参数和通报给匿名函数的参数(在本例中为3)。

所以,sum 函数能够如许写:

function sum (x) {
if (arguments.length == 2) {
return arguments[0] + arguments[1];
} return function(y) {
return x + y;
}
}

arguments 的用法挺天真的,在这里它则用于支解两种差别的状况。当参数只要一个的时刻,举行柯里化的处置惩罚。

那末,究竟什么是函数的柯里化呢?接下来,我们将从观点动身,探讨函数柯里化的完成与用处。

什么是柯里化

柯里化,是函数式编程的一个主要观点。它既能削减代码冗余,也能增添可读性。别的,附带着还能用来装逼。

先给出柯里化的定义:在数学和盘算机科学中,柯里化是一种将运用多个参数的一个函数转换成一系列运用一个参数的函数的手艺。

柯里化的定义,明白起来有点费力。为了更好地明白,先看下面这个例子:

function sum (a, b, c) {
console.log(a + b + c);
}
sum(1, 2, 3); // 6

毫无疑问,sum 是个简朴的累加函数,接收3个参数,输出累加的效果。

假定有如许的需求,sum的前2个参数坚持稳定,末了一个参数能够随便。那末就会想到,在函数内,是不是能够把前2个参数的相加历程,给抽离出来,由于参数都是雷同的,没必要每次都做运算。

假如先不论函数内的细致完成,挪用的写法能够是如许: sum(1, 2)(3); 或如许 sum(1, 2)(10); 。就是,先把前2个参数的运算效果拿到后,再与第3个参数相加。

这实在就是函数柯里化的简朴运用。

柯里化的完成

sum(1, 2)(3); 如许的写法,并不罕见。拆开来看,sum(1, 2) 返回的应当照样个函数,由于背面另有 (3) 须要实行。

那末反过来,从末了一个参数,从右往左看,它的左边必定是一个函数。以此类推,假如前面有n个(),那就是有n个函数返回了效果,只是返回的效果,照样一个函数。是不是是有点递归的意义?

网上有一些差别的柯里化的完成体式格局,以下是个人以为最轻易明白的写法:

function curry (fn, currArgs) {
return function() {
let args = [].slice.call(arguments);
// 初次挪用时,若未供应末了一个参数currArgs,则不必举行args的拼接
if (currArgs !== undefined) {
args = args.concat(currArgs);
}
// 递归挪用
if (args.length return curry(fn, args);
}
// 递归出口
return fn.apply(null, args);
}
}

剖析一下 curry 函数的写法:

起首,它有 2 个参数,fn 指的就是本文一开始的源处置惩罚函数 sum。currArgs 是挪用 curry 时传入的参数列表,比方 (1, 2)(3) 如许的。

再看到 curry 函数内部,它会全部返回一个匿名函数。

再接下来的 let args = [].slice.call(arguments);,意义是将 arguments 数组化。arguments 是一个类数组的构造,它并非一个真的数组,所以没法运用数组的要领。我们用了 call 的要领,就可以愉快地对 args 运用数组的原生要领了。在这篇 「干货」细说 call、apply 以及 bind 的区分和用法 中,有关于 call 更细致的用法引见。

currArgs !== undefined 的推断,是为了处理递归挪用时的参数拼接。

末了,推断 args 的个数,是不是与 fn (也就是 sum )的参数个数相称,相称了就可以够把参数都传给 fn,举行输出;不然,继承递归挪用,直到二者相称。

测试一下:

function sum(a, b, c) {
console.log(a + b + c);
}
const fn = curry(sum);
fn(1, 2, 3); // 6
fn(1, 2)(3); // 6
fn(1)(2, 3); // 6
fn(1)(2)(3); // 6

都能输出 6 了,搞定!

柯里化的用处

明白了柯里化的完成以后,让我们来看一下它的现实运用。柯里化的目标是,削减代码冗余,以及增添代码的可读性。来看下面这个例子:

const persOns= [
{ name: 'kevin', age: 4 },
{ name: 'bob', age: 5 }
];
// 这里的 curry 函数,之前已完成
const getProp = curry(function (obj, index) {
const args = [].slice.call(arguments);
return obj[args[args.length - 1]];
});
const ages = persons.map(getProp('age')); // [4, 5]
const names = persons.map(getProp('name')); // ['kevin', 'bob']

在现实的营业中,我们常会碰到相似的列表数据。用 getProp 就可以够很方便地,掏出列表中某个 key 对应的值。

须要注重的是,const names = persons.map(getProp('name')); 实行这条语句时 getProp 的参数只要一个 name,而定义 getProp 要领时,传入 curry 的参数有2个,objindex(这里必需写 2 个及以上的参数)。

为何要这么写?症结就在于 arguments 的隐式传参。

const getProp = curry(function (obj, index) {
console.log(arguments);
// 会输出4个类数组,取个中一个来看
// {
// 0: {name: "kevin", age: 4},
// 1: 0,
// 2: [
// {name: "kevin", age: 4},
// {name: "bob", age: 5}
// ],
// 3: "age"
// }
});

map 是 Array 的原生要领,它的用法以下:

var new_array = arr.map(function callback(currentValue[, index[, array]]) {
// Return element for new_array
}[, thisArg]);

所以,我们传入的 name,就排在了 arguments 的末了。为了拿到 name 对应的值,须要对类数组 arguments 做点转换,让它能够运用 Array 的原生要领。所以,终究 getProp 要领定义成了如许:

const getProp = curry(function (obj, index) {
const args = [].slice.call(arguments);
return obj[args[args.length - 1]];
});

固然,另有别的一种写法,curry 的完成更好明白,然则挪用的代码却变多了,人人能够依据现实状况举行弃取。

const getProp = curry(function (key, obj) {
return obj[key];
});
const ages = persons.map(item => {
return getProp(item)('age');
});
const names = persons.map(item => {
return getProp(item)('name');
});

末了,来看一个 Memoization 的例子。它用于优化比较耗时的盘算,经由过程将盘算效果缓存到内存中,如许关于一样的输入值,下次只须要中内存中读取效果。

function memoizeFunction(func) {
const cache = {};
return function() {
let key = arguments[0];
if (cache[key]) {
return cache[key];
} else {
const val = func.apply(null, arguments);
cache[key] = val;
return val;
}
};
}
const fibOnacci= memoizeFunction(function(n) {
return (n === 0 || n === 1) ? n : fibonacci(n - 1) + fibonacci(n - 2);
});
console.log(fibonacci(100)); // 输出354224848179262000000
console.log(fibonacci(100)); // 输出354224848179262000000

代码中,第2次盘算 fibonacci(100) 则只须要在内存中直接读取效果。

总结

函数的柯里化,是 Javascript 中函数式编程的一个主要观点。它返回的,是一个函数的函数。其完成体式格局,须要依靠参数以及递归,经由过程拆分参数的体式格局,来挪用一个多参数的函数要领,以到达削减代码冗余,增添可读性的目标。

虽然一开始明白起来有点云里雾里的,但一旦明白了个中的寄义和细致的运用场景,用起来就会随心所欲了。

PS:迎接关注我的民众号 “超哥前端小栈”,交换更多的主意与手艺。

《「前端面试题系列6」明白函数的柯里化》


推荐阅读
  • 本文详细介绍了Java中vector的使用方法和相关知识,包括vector类的功能、构造方法和使用注意事项。通过使用vector类,可以方便地实现动态数组的功能,并且可以随意插入不同类型的对象,进行查找、插入和删除操作。这篇文章对于需要频繁进行查找、插入和删除操作的情况下,使用vector类是一个很好的选择。 ... [详细]
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • Java序列化对象传给PHP的方法及原理解析
    本文介绍了Java序列化对象传给PHP的方法及原理,包括Java对象传递的方式、序列化的方式、PHP中的序列化用法介绍、Java是否能反序列化PHP的数据、Java序列化的原理以及解决Java序列化中的问题。同时还解释了序列化的概念和作用,以及代码执行序列化所需要的权限。最后指出,序列化会将对象实例的所有字段都进行序列化,使得数据能够被表示为实例的序列化数据,但只有能够解释该格式的代码才能够确定数据的内容。 ... [详细]
  • 开发笔记:加密&json&StringIO模块&BytesIO模块
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了加密&json&StringIO模块&BytesIO模块相关的知识,希望对你有一定的参考价值。一、加密加密 ... [详细]
  • 本文介绍了P1651题目的描述和要求,以及计算能搭建的塔的最大高度的方法。通过动态规划和状压技术,将问题转化为求解差值的问题,并定义了相应的状态。最终得出了计算最大高度的解法。 ... [详细]
  • 判断数组是否全为0_连续子数组的最大和的解题思路及代码方法一_动态规划
    本文介绍了判断数组是否全为0以及求解连续子数组的最大和的解题思路及代码方法一,即动态规划。通过动态规划的方法,可以找出连续子数组的最大和,具体思路是尽量选择正数的部分,遇到负数则不选择进去,遇到正数则保留并继续考察。本文给出了状态定义和状态转移方程,并提供了具体的代码实现。 ... [详细]
  • 计算机存储系统的层次结构及其优势
    本文介绍了计算机存储系统的层次结构,包括高速缓存、主存储器和辅助存储器三个层次。通过分层存储数据可以提高程序的执行效率。计算机存储系统的层次结构将各种不同存储容量、存取速度和价格的存储器有机组合成整体,形成可寻址存储空间比主存储器空间大得多的存储整体。由于辅助存储器容量大、价格低,使得整体存储系统的平均价格降低。同时,高速缓存的存取速度可以和CPU的工作速度相匹配,进一步提高程序执行效率。 ... [详细]
  • 开发笔记:计网局域网:NAT 是如何工作的?
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了计网-局域网:NAT是如何工作的?相关的知识,希望对你有一定的参考价值。 ... [详细]
  • Java学习笔记之面向对象编程(OOP)
    本文介绍了Java学习笔记中的面向对象编程(OOP)内容,包括OOP的三大特性(封装、继承、多态)和五大原则(单一职责原则、开放封闭原则、里式替换原则、依赖倒置原则)。通过学习OOP,可以提高代码复用性、拓展性和安全性。 ... [详细]
  • 本文讨论了clone的fork与pthread_create创建线程的不同之处。进程是一个指令执行流及其执行环境,其执行环境是一个系统资源的集合。在调用系统调用fork创建一个进程时,子进程只是完全复制父进程的资源,这样得到的子进程独立于父进程,具有良好的并发性。但是二者之间的通讯需要通过专门的通讯机制,另外通过fork创建子进程系统开销很大。因此,在某些情况下,使用clone或pthread_create创建线程可能更加高效。 ... [详细]
  • 本文讨论了在手机移动端如何使用HTML5和JavaScript实现视频上传并压缩视频质量,或者降低手机摄像头拍摄质量的问题。作者指出HTML5和JavaScript无法直接压缩视频,只能通过将视频传送到服务器端由后端进行压缩。对于控制相机拍摄质量,只有使用JAVA编写Android客户端才能实现压缩。此外,作者还解释了在交作业时使用zip格式压缩包导致CSS文件和图片音乐丢失的原因,并提供了解决方法。最后,作者还介绍了一个用于处理图片的类,可以实现图片剪裁处理和生成缩略图的功能。 ... [详细]
  • Java中包装类的设计原因以及操作方法
    本文主要介绍了Java中设计包装类的原因以及操作方法。在Java中,除了对象类型,还有八大基本类型,为了将基本类型转换成对象,Java引入了包装类。文章通过介绍包装类的定义和实现,解答了为什么需要包装类的问题,并提供了简单易用的操作方法。通过本文的学习,读者可以更好地理解和应用Java中的包装类。 ... [详细]
  • 本文介绍了深入浅出Linux设备驱动编程的重要性,以及两种加载和删除Linux内核模块的方法。通过一个内核模块的例子,展示了模块的编译和加载过程,并讨论了模块对内核大小的控制。深入理解Linux设备驱动编程对于开发者来说非常重要。 ... [详细]
  • 预备知识可参考我整理的博客Windows编程之线程:https:www.cnblogs.comZhuSenlinp16662075.htmlWindows编程之线程同步:https ... [详细]
  • 本文记录了在vue cli 3.x中移除console的一些采坑经验,通过使用uglifyjs-webpack-plugin插件,在vue.config.js中进行相关配置,包括设置minimizer、UglifyJsPlugin和compress等参数,最终成功移除了console。同时,还包括了一些可能出现的报错情况和解决方法。 ... [详细]
author-avatar
wjw0000
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有