柯里化
- 函数柯里化 把一个函数的范围缩小,让函数变得更具体
// 判断变量类型
//四种方式 constructor instanceof typeof Object.prototype.toString.call
function checkType(content,type){
// [object String] [object Number] [object Boolean]
return Object.prototype.toString.call(content) === `[object ${type}]`
}
let bool = checkType('hello','String')
let bool1 = checkType('qqq','String')
// 柯里化
function checkType(type){
// 私有化,
return function(content){
return Object.prototype.toString.call(content) === `[object ${type}]`
}
}
let isString = checkType('String')
let isBoolean = checkType('Boolean')
let flag1 = isString('hello')
let flag2 = isBoolean(true)
2.通用柯里化
const add = (a,b,c,d) => {
return a+b+c+d
}
function createCurry(func, args) {
var arity = func.length;
var args = args || [];
return function() {
var _args = [].slice.call(arguments);
[].push.apply(_args, args);
// 如果参数个数小于最初的func.length,则递归调用,继续收集参数
if (_args.length < arity) {
return createCurry.call(this, func, _args);
}
// 参数收集完毕,则执行func
return func.apply(this, _args);
}
}
const curring = (fn, arr=[]) { //第二个参数默认是数组
let len = fn.length; // 长度指代的是函数的参数的个数
return (...args) => {
arr = [...arr,...args]
if(arr.length < len){
return curring(fn,arr);
}
return fn(...arr)
}
}
add(1,2)(3)(3)(4)
3.应用场景
- 参数复用
function checkType(type,content){
// [object String] [object Number] [object Boolean]
return Object.prototype.toString.call(content) === `[object ${type}]`
}
const curring = (fn, arr=[]) { //第二个参数默认是数组
let len = fn.length; // 长度指代的是函数的参数的个数
return (...args) => {
arr = [...arr,...args]
if(arr.length < len){
return curring(fn,arr);
}
return fn(...arr)
}
}
let util = {};
['Number','String','Boolean'].forEach((item)=>{
util['is'+item] = curring(checkType)(item)
})
let r = util.isString('hello');
let b = util.isBoolean(true);
- 提前确认
// 第一种:常见封装dom的方法
var on = function(element, event, handler) {
if(document.addEventListener) {
if(element && event && handler) {
element.addEventLister(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);
}
}
- 延迟执行
传入多个参数的sum(1)(2)(3),就是延迟执行的最好例子,传入参数个数没有满足原函数入参个数,都不会立即返回结果。
类似的场景,还有绑定事件回调,更使用bind()方法绑定上下文,传入参数类似,
Function.prototype.bind = function(context) { //context为我们想修正的this对象
var self = this; //保存原函数
var args = Array.prototype.slice.call(arguments,1);
return function(){// 返回一个新函数,实际执行时会先执行这个新函数
return self.apply(context,args);// 执行原函数,指定context为原函数体内的this
}
}
addEventListener('click', hander.bind(this, arg1,arg2...))
addEventListener('click', curry(hander))
延迟执行的特性,可以避免在执行函数外面,包裹一层匿名函数,curry函数作为回调函数就有很大优势。
- 函数式编程中,作为compose, functor, monad 等实现的基础
有人说柯里化是应函数式编程而生,它在里面出现的概率就非常大了,在JS 函数式编程指南中,开篇就介绍了柯里化的重要性。
3.缺点
函数柯里化可以用来构建复杂的算法 和 功能, 但是滥用也会带来额外的开销。
从上面实现部分的代码中,可以看到,使用柯里化函数,离不开闭包, arguments, 递归。
闭包,函数中的变量都保存在内存中,内存消耗大,有可能导致内存泄漏。
递归,效率非常差,
arguments, 变量存取慢,访问性很差,
反柯里化
当我们调用对象的某个方法时,不用关心该对象原本是否被设计拥有这个方法,只要这个方法适用于它,我们就可以对这个对象使用它
- 使用uncurring将泛化this的过程提取出来
// 分析调用Array.prototype.push.uncurring()这句代码发生了什么事情:
Function.prototype.uncurring = function() {
var self = this; //self 此时是Array.prototype.push
return function() {
var obj = Array.prototype.shift.call(arguments);
//obj 是{
// "length": 1,
// "0": 1
// }
//arguments的第一个对象被截取(也就是调用push方法的对象),剩下[2]
return self.apply(obj, arguments); //相当于Array.prototype.push.apply(obj,2)
}
}
// 测试一下
var push = Array.prototype.push.uncurring();
var obj = {
'length': 1,
'0': 1
};
push(obj, 2)
console.log(obj); //{0: 1,1:2,length:2}
//另一种方式
Function.prototype.uncurring=function(){
var self = this;
return function(){
return Function.prototype.call.apply(self,argumrnt)
}
}
// 把Array.prototype.push方法转换成一个普通的push函数
var push = Array.prototype.push.uncurring();
// 测试一下
// arguments本是没有push方法的,通常,我们需要用Array.prototype.push.call来实现push方法,但现在,直接调用push函数
(function(){
push(arguments,4);
console.log(arguments); // [1,2,3,4]
})(1,2,3);