从零开始掌握函数柯里化:面试必备技能

引言

作为一个不断追求技术进步的开发者,呆同学最近深入学习了函数柯里化这个概念,并通过实现一个简单的add函数来巩固我的理解。函数柯里化不仅是一个有趣的编程技巧,更是面试中的一个重要考点,函数柯里化可以提高代码的可读性和复用性,同时也是很多面试官青睐的考点之一。掌握函数柯里化,不仅能让你的代码更优雅,还能在面试中展示你的深厚功底和思维能力。我将与大家分享我的学习过程、心得以及如何在实际开发和面试中运用函数柯里化。希望这篇文章能对你有所帮助,让我们一起从零开始掌握这一强大的编程技巧吧!

什么是函数柯里化?

函数柯里化(Currying)是一种将接收多个参数的函数转换成接收单一参数(返回接收余下参数的新函数)的技术。换句话说,柯里化将一个多参数的函数转换为一系列的单参数函数。

我举个简单的例子:

js 复制代码
// 非柯里化函数
function add(a, b) {
    return a + b;
}
console.log(add(2, 3)); // 输出: 5

// 柯里化函数
function curryAdd(a) {
    return function(b) {
        return a + b;
    }
}
const add2 = curryAdd(2);
console.log(add2(3)); // 输出: 5

在柯里化函数中运用到了闭包的特性,使得在两数相加的过程中,实现只传一个参数的函数。

为什么学习函数柯里化?

1. 代码复用性

柯里化可以创建可重用的函数。例如,通过柯里化后的add函数,我们可以创建一个新的函数addTwo,它固定了一个参数。这样的函数可以在多个地方重复使用。例如:

js 复制代码
const add2 = curryAdd(2);
console.log(add2(3)); // 输出: 5
console.log(add2(4)); // 输出: 6

2. 简洁性

柯里化可以让代码更加简洁和清晰,特别是在处理多个函数组合时。例如,在处理数组时,可以将柯里化函数与mapfilter等方法结合使用。

js 复制代码
const multiply = a => b => a * b;
const multiplyByTwo = multiply(2);
const result = [1, 2, 3].map(multiplyByTwo);
console.log(result); // 输出: [2, 4, 6]

3. 面试中的应用场景

函数柯里化是许多技术面试中的热门考点。它不仅考察我们对函数和闭包的理解,还考察我们的代码优化能力和抽象思维能力。面试官可能会通过柯里化相关的问题来了解我们对JavaScript高级特性的掌握情况。

例如,面试官可能会要求你将一个普通函数改写为柯里化函数,或者让你解释柯里化的优势和应用场景。熟练掌握柯里化能够让你在面试中脱颖而出,展示你对JavaScript的深刻理解和灵活运用能力。

函数柯里化的基本实现

使用ES5实现函数柯里化

在ES5中,我们可以使用arguments对象来实现柯里化。arguments对象是一个类数组对象,它包含传递给函数的所有参数。使用这个对象,我们可以实现一个简单的柯里化函数。举个例子:

js 复制代码
function curryES5(fn) {
    var args = Array.prototype.slice.call(arguments, 1);
    return function() {
        var newArgs = args.concat(Array.prototype.slice.call(arguments));
        return fn.apply(null, newArgs);
    };
}
// 示例函数
function add(a, b, c) {
    return a + b + c;
}
// 柯里化后的函数
var curriedAdd = curryES5(add, 1);
console.log(curriedAdd(2, 3)); // 输出: 6

在这个例子中,curryES5函数接收一个函数fn和一些参数args,然后返回一个新的函数。新的函数会收集传入的参数并与之前的参数合并,然后调用原始函数fn

使用ES6实现函数柯里化

在ES6中,我们可以使用rest运算符来实现更简洁的柯里化函数。rest运算符允许我们将不定数量的参数表示为一个数组。

js 复制代码
function curryES6(fn, ...args) {
    return function(...newArgs) {
        return fn(...args, ...newArgs);
    };
}
// 示例函数
const add = (a, b, c) => a + b + c;

// 柯里化后的函数
const curriedAdd = curryES6(add, 1);
console.log(curriedAdd(2, 3)); // 输出: 6

在这个例子中,curryES6函数接收一个函数fn和一些参数args,然后返回一个新的函数。新的函数会收集新的参数newArgs,并与之前的参数合并,然后调用原始函数fn。这里的...args是剩余参数,相当于arguments

全柯里化的实现

为了实现更全面的柯里化,我们可以实现一个递归的柯里化函数,这个函数可以逐个收集参数,直到所有参数都被收集完毕。

使用递归实现全柯里化:

js 复制代码
function curry(fn, ...args) {
    return function(...newArgs) {
        const allArgs = [...args, ...newArgs];
        if (allArgs.length >= fn.length) {
            return fn(...allArgs);
        } else {
            return curry(fn, ...allArgs);
        }
    };
}
// 示例函数
const add = (a, b, c) => a + b + c;

// 柯里化后的函数
const curriedAdd = curry(add);
console.log(curriedAdd(1)(2)(3)); // 输出: 6
console.log(curriedAdd(1, 2)(3)); // 输出: 6
console.log(curriedAdd(1)(2, 3)); // 输出: 6

在这个例子中,curry函数递归地收集参数,直到所有参数都被收集完毕。当收集的参数数量达到原始函数fn的参数数量时,调用原始函数fn,否则继续收集参数。

箭头函数与arguments对象

箭头函数的应用,为我们在编写函数的过程中带来了很多便利,但是它在面对柯里化时又会出现什么样的问题呢?

箭头函数

箭头函数的特点:

  1. 简洁的语法:箭头函数语法更简洁,相对于传统的函数表达式而言。

  2. 词法作用域中的this:箭头函数不绑定this,它捕获的是上下文中的this值。

js 复制代码
function Person() {
    this.age = 0;
    setInterval(() => {
        this.age++; // 这里的this指向Person实例
    }, 1000);
}
var p = new Person();
  1. 没有prototype属性:箭头函数没有prototype属性,不能用作构造函数。
js 复制代码
const Foo = () => {};
console.log(Foo.prototype); // undefined
  1. 词法作用域中的arguments:箭头函数没有自己的arguments对象,它会捕获外层非箭头函数的arguments对象。
js 复制代码
function foo() {
    const arrowFunc = () => {
        console.log(arguments);
    };
    arrowFunc(1, 2, 3);
}
foo(4, 5, 6); // 输出: [4, 5, 6]

在这个例子中,箭头函数arrowFunc中的arguments实际上是外层函数fooarguments

箭头函数设计的一个主要目的是让thisarguments表现得更一致、更直观。它们的值由外层作用域决定,而不是由函数调用方式决定。但是,如果箭头函数想要柯里化,没有arguments对象,就无法获取其余的参数,那该如何实现呢?

使用rest运算符

由于箭头函数没有自己的arguments对象,当我们需要处理不定数量的参数时,可以使用rest运算符(...)来代替arguments对象。rest运算符将所有传入的参数收集到一个数组中。

js 复制代码
const curry = (fn, ...args) =>
    // console.log(args.length, fn.length); // 1 4
    args.length >= fn.length
        ? fn(...args)
        : (...args2) => curry(fn, ...args, ...args2);
const add = (x, y, z, m) => {
    return x + y + z + m
}
console.log(curry(add, 1)(2)(3)(4));

在这个例子中,...args将所有传入的参数收集到一个数组args中,比较已收集参数 (args.length) 是否达到目标函数 fn 的参数数量 (fn.length)。如果参数足够,立即调用 fn 并传入所有参数 fn(...args)。如果参数不足,返回一个新的函数,继续收集剩余参数 (...args2),并递归调用 curry 函数。

通过使用rest运算符,可以让我们的代码更加简洁和符合ES6的规范,解决箭头函数中无法直接使用arguments对象的问题

总结

柯里化是一种强大的技术,可以使代码更加模块化、可读和可维护。通过柯里化,我们可以轻松实现参数的部分应用,柯里化不仅是一个理论知识,它在实际开发中有着广泛的应用场景,可以大大提升代码的灵活性和复用性。

在面试的现场编程中,可以通过实现柯里化函数,展示自己对参数部分应用和代码可读性的理解。 解释每一步的思路,并编写简洁、有效的代码,可以充分展示自己对柯里化的理解和应用能力,给面试官留下深刻印象。

如果你觉得有学到,希望点赞收藏+关注

相关推荐
lemon_sjdk11 分钟前
JavaScript 常用关键字全解析
开发语言·javascript·ecmascript
晚安日记wanna41 分钟前
分布式事务 6 连问从 Seata AT 到本地消息表落地
面试·架构
高申航42 分钟前
Vue 3 + DYMO Connect Framework 实战:从标签模板到打印服务的完整实现
前端·javascript·vue.js
雪芽蓝域zzs1 小时前
分页组件 + el-config-provider 中文国际化 + 靠右布局
前端·javascript·vue.js
志尊宝1 小时前
Vue3 零基础每日笔记(016):v-for 列表渲染——key 的作用与为什么不能用 index
javascript·vue.js·笔记
toooooop82 小时前
thinkphp查询数据表最后的自增id
前端·javascript·数据库
晚安日记wanna2 小时前
简历写精通浏览器原理一问 URL 到渲染直接哑火...
前端·面试
garmin Chen2 小时前
MySQL精简面试题
数据库·后端·mysql·面试
晚安日记wanna2 小时前
四年前端被问 Code Review90人只看得懂空格和命名
前端·面试
honkun62 小时前
vue 甘特图组件 vxe-gantt 配置拖拽调整任务起止日期时实时显示 Tooltip 提示
javascript·vue.js·甘特图·vxe-gantt