javascript函数式编程03-了解函数柯里化

304 阅读3分钟

这是我参与更文挑战的第3天,活动详情查看: 更文挑战

紧跟上一篇 ,这一篇主要介绍函数柯里化,及函数柯里化的实际应用
什么是函数柯里化?
  • 百度百科:在计算机科学中,柯里化(Currying)是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数且返回结果的新函数的技术。通俗点说就是将一个函数拆分成多个函数,是固定部分参数,返回一个接受剩余参数的函数,也称为部分计算函数,目的是为了缩小适用范围,创建一个针对性更强的函数。
  • 为什么会有函数柯里化?
    • Currying 的重要意义在于可以把函数完全变成「接受一个参数;返回一个值」的固定形式,这样对于讨论和优化会更加方便。
    • 将关注的重点聚焦到函数本身,而不因冗余的数据参数分散注意力。
    • 当函数可以作为函数的参数和返回值,成为函数式编程语言后,就会不可避免地产生函数柯里化。
函数柯里化的好处有哪些?
  • 参数复用
// 以封装正则校验为例
// 正常正则验证字符串 reg.test(txt)

// 函数封装后
function check(reg, txt) {
    return reg.test(txt)
}

check(/\d+/g, 'test')       //false
check(/[a-z]+/g, 'test')    //true

// Currying后
function curryingCheck(reg) {
    return function(txt) {
        return reg.test(txt)
    }
}

var hasNumber = curryingCheck(/\d+/g)
var hasLetter = curryingCheck(/[a-z]+/g)

hasNumber('test1')      // true
hasNumber('testtest')   // false
hasLetter('21212')      // false
  • 提前确认
var on = function(element, event, handler) {
    if (document.addEventListener) {
        if (element && event && handler) {
            element.addEventListener(event, handler, false);
        }
    } else {
        if (element && event && handler) {
            element.attachEvent('on' + event, handler);
        }
    }
}

var on = (function() {
    if (document.addEventListener) {
        return function(element, event, handler) {
            if (element && event && handler) {
                element.addEventListener(event, handler, false);
            }
        };
    } else {
        return function(element, event, handler) {
            if (element && event && handler) {
                element.attachEvent('on' + event, handler);
            }
        };
    }
})();

//换一种写法可能比较好理解一点,上面就是把isSupport这个参数给先确定下来了
var on = function(isSupport, element, event, handler) {
    isSupport = isSupport || document.addEventListener;
    if (isSupport) {
        return element.addEventListener(event, handler, false);
    } else {
        return element.attachEvent('on' + event, handler);
    }
}

我们在做项目的过程中,封装一些dom操作可以说再常见不过,上面第一种写法也是比较常见,
但是我们看看第二种写法,它相对一第一种写法就是自执行然后返回一个新的函数,
这样其实就是提前确定了会走哪一个方法,避免每次都进行判断。
  • 延迟运行
    • 最经典的是bind函数
Function.prototype.bind = function (context) {
    var _this = this
    var args = Array.prototype.slice.call(arguments, 1)
 
    return function() {
        return _this.apply(context, args)
    }
}
  • 提高适用性
//通用函数解决了兼容性的问题,但是同时在不同场景下,我们可能同一种规则需要反复使用
//这就可能会造成代码的重复性 比如

function square(i){ return i*i }//平方
function dubble(i){ return i*2 }//双倍
function map(handler,list){
	//handle 是操作的规则 list是操作的arrguments
	return list.map(handler)
}

map(square,[1,2,3]);//数组每一项平方
map(dubble,[1,2,3]);//数组每一项加倍
//这就是通用性 我可以用同一个函数做很多不同的操作
//但是如果我们需要大量做平方操作 每次我们都需要传入方法再传入数组就造成的代码浪费
//这时我们通过柯里化提高实用性

let mapSQ = currying(map,square);//直接定义出来的新的平凡操作函数
mapSQ([1,2,3]);//以后就不用传入操作方法了
函数柯里化的通用封装方法
// 支持多参数传递
function fnCurrying(fn, args) {
    var _this = this
    var len = fn.length;
    var args = args || [];
    return function() {
        var _args = Array.prototype.slice.call(arguments);
        Array.prototype.push.apply(args, _args);
        // 如果参数个数小于最初的fn.length,则递归调用,继续收集参数
        if (_args.length < len) {
            return fnCurrying.call(_this, fn, _args);
        }
        // 参数收集完毕,则执行fn
        return fn.apply(this, _args);
    }
}
函数柯里化的性能问题
  • 存取arguments对象通常要比存取命名参数要慢一点
  • 一些老版本的浏览器在arguments.length的实现上是相当慢的
  • 使用fn.apply( … ) 和 fn.call( … )通常比直接调用fn( … ) 稍微慢点
  • 创建大量嵌套作用域和闭包函数会带来花销,无论是在内存还是速度上