函数柯里化

119 阅读4分钟

前言✨

柯里化是什么

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

柯里化(Currying)是一种关于函数的高阶技术。它不仅被用于 JavaScript,还被用于其他编程语言。

柯里化不会调用函数,它只是对函数进行转换,就是把一个多参数的函数,转化为单参数函数如:函数 f(a, b, c) 转换为 f(a)(b)(c)

举个栗子🌰

// 普通函数
function add(x, y, z) {
    return x + y + z
}
add(1, 2, 3) // 6
// 柯里化函数
function addCurry(y) {
    return function (x) {
        return function (z) {
            return x + y + z
        }
    }
}
addCurry(1)(2)(3) // 6

代码分析:

1.函数作为返回值返回,闭包形成,外部环境可访问函数内部作用域。

2.子函数可访问父函数的作用域,作用域由内而外的作用域链查找规则,作用域嵌套形成。

3.在函数参数数量不满足时,返回一个函数(该函数可接收并处理剩余参数)。

4.当函数数量满足我们的触发机制(可自由制定),触发原始函数执行。

闭包:是由“父”函数返回的函数,可以访问父函数的内部状态。

柯里化:总是会导致闭包。因为柯里化函数返回的每个函数都会提供父函数的内部状态。

上面方案的问题:

  • 代码不够优雅,需要一层一层的嵌套函数。
  • 可扩展性差

柯里化工具函数封装:

function curry(fn, args) {
    var args = args || [];
    return function () {
        // arguments是类数组, 所以利用call调用数组方法slice, 浅拷贝成一个数组
        newArgs = args.concat(Array.prototype.slice.call(arguments));
        // 函数的参数个数可以直接通过函数数的.length属性来访问
        if (newArgs.length < fn.length) {
            // 传入的参数大于等于原始函数fn的参数个数,则直接执行该函数
            return curry.call(this, fn, newArgs);
        } else {
            // 否则继续对当前函数进行柯里化,返回一个接受所有参数(当前参数和剩余参数) 的函数
            return fn.apply(this, newArgs);
        }
    }
}

function add(x, y, z) {
    return x + y + z
}
const addCurry = curry(add)
console.log(addCurry(1)(2)(3)); // 6
console.log(addCurry(1, 2, 3)); // 6
console.log(addCurry(1, 2)(3)); // 6

柯里化的特点❓

案例参考:blog.csdn.net/qq_38650754…

一、参数复用

工作中会遇到的需求,通过正则校验电话号、邮箱、是否合法,如下:

// 普通函数
/**
 * @description 通过正则校验字符串
 * @param {RegExp} regExp 正则对象
 * @param {String} str 待校验字符串
 * @return {Boolean} 是否通过校验
 */
function checkByRegExp(regExp, str) {
    return regExp.test(str)
}

// 校验手机号
checkByRegExp(/^1\d{10}$/, '123445'); // false
checkByRegExp(/^1\d{10}$/, '12345678901'); // true
// 校验邮箱
checkByRegExp(/^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/, 'asd@163.com'); // true
checkByRegExp(/^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/, 'asd@qq.com'); // true

以上做法完全没问题,但是还有提升的空间:

1.校验同一类型的数据时,相同的正则我们写了很多次。

2.代码可读性较差,如果没有注释,我们并不能一下就看出来正则的作用。

柯里化处理

// 将函数柯里化
function checkByRegExp(regExp) {
    return function(str) {
        return regExp.test(str)
    }
}
// 校验手机
const checkPhone = checkByRegExp(/^1\d{10}$/)
// 校验邮箱
const checkEmail = checkByRegExp(/^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/)

// 校验手机号
checkPhone('123445'); // false
checkPhone('12345678901'); // true

// 校验邮箱
checkEmail('JayShen@163.com'); // true
checkEmail('JayShen@qq.com'); // true

二、提前返回

在JS DOM事件监听程序中,我们用addEventListener方法为元素添加事件处理程序,但是部分浏览器版本不支持此方法,我们会使用attachEvent方法来替代。

/**
 * @description: 
 * @param {object} element DOM元素对象
 * @param {string} type 事件类型
 * @param {Function} fn 事件处理函数
 * @param {boolean} isCapture 是否捕获
 * @return {void}
 */
function addEvent(element, type, fn, isCapture) {
    if (window.addEventListener) {
        element.addEventListener(type, fn, isCapture)
    } else if (window.attachEvent) {
        element.attachEvent("on" + type, fn)
    }
}

我们用addEvent来添加事件监听,但是每次调用此方法时,都会进行一次判断,事实上浏览器版本确定下来后,没有必要进行重复判断。

柯里化处理

function curryingAddEvent() {
    if (window.addEventListener) {
        return function(element, type, fn, isCapture) {
            element.addEventListener(type, fn, isCapture)
        }
    } else if (window.attachEvent) {
        return function(element, type, fn) {
            element.attachEvent("on" + type, fn)
        }
    }
}
const addEvent = curryingAddEvent()

// 也可以用立即执行函数将上述代码合并
const addEvent = (function curryingAddEvent() {
 	...
})()

现在我们得到的addEvent是经过判断后得到的函数,以后调用就不用重复判断了。 这就是提前返回或者说提前确认,函数柯里化后可以提前处理部分任务,返回一个函数处理其他任务。

三、延迟执行

返回的函数都不会立即执行,而是等待调用。