2021-02-07 JS中的柯理化,高阶函数,偏函数。以及闭包的英文表达closure

对于js函数的柯里化深入分析

转载:https://blog.csdn.net/weixin_42614080/article/details/90814427
以下是转载内容,但是在转载之前,我想大家,F12打开控制台,运行下面的代码。其中有截图的地方,是我新增的。帮助大家理解。

一、柯里化的理解

柯里化

1)柯里化(currying)也称为部分求值,柯里化是一种将使用多个参数的一个函数转换成一系列使用一个参数的函数的技术
2)所谓"柯里化",就是把一个多参数的函数,转化为单参数函数,并且返回接受余下的参数而且返回结果
3)一个currying的函数首先会接受一些参数,接受了这些参数之后,该函数并不会立即求值,而是继续返回另外一个函数,刚才传入的参数在函数形成的闭包中被保存起来,待到函数被真正需要求值的时候,之前传入的所有参数都会被一次性用于求值

柯里化的分析

1)需求分析:实现两数的相加
实例代码:

   function add1(x,y){
        return x+y;
    }
    // 3
    console.log(add1(1,2));

2)函数的柯里化,把一个多参数的函数转换为一个单参数的函数,所有的函数只接收一个参数
实例代码:

   // 把x、y两个参数变成先用一个函数接收x参数,然后返回一个函数去处理y参数
   // 只传递给函数一部分参数来调用它,让它返回一个函数去处理剩下的参数
   function add2(y){
       return function(x){
           return x+y;
       };
   }
   // 3
  console.log(add2(2)(1));
柯里化函数的封装

1)实例代码:

    // 只能扩展一个参数,不支持多参数调用
     var currying = function(fn){
        // args  获取第一个方法内的全部参数
        var args = Array.prototype.slice.call(arguments,1);
        return function(){  
            // 将后面方法里面的全部参数和args进行合并
            var newArgs = args.concat(Array.prototype.slice.call(arguments));
            // 把合并后的参数通过apply作为fn的参数并且执行
            return fn.apply(this,newArgs);
        };
    };

2)上面的函数封装只能实现一个参数的扩展,无法实现多个参数的扩展,需要修改为支持多参数传递
实例代码:


   // 支持多参数传递
    function progressCurring(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 progressCurring.call(_this,fn,_args);
            }

            // 参数收集完毕,那么就执行fn
            return fn.apply(this,_args);
        }
    }
柯里化函数的性能

1)存取arguments对象通常要比存取命名参数要慢一点
2)一些老版本的浏览器在arguments.length的实现上是相当慢的
3)使用fn.apply( … ) 和 fn.call( … )通常比直接调用fn( … ) 稍微慢点
4)创建大量嵌套作用域和闭包函数会带来花销,无论是在内存还是速度上

二、柯里化的应用和面试

柯里化的应用

1)参数复用,很多地方都要校验是否有数字,其实就是需要将第一个参数reg进行复用,这样别的地方就能够直接调用hasNumber,hasLetter等函数,让参数能够复用,调用起来也更方便
实例代码:

// 正常正则验证字符串 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

这里我新增里一些操作,帮助大家来理解。大家复制代码到控制台,运行一下。并展开结果,代码和截图如下

function curryingCheck(reg) {
    return function(txt) {
        return reg.test(txt)
    }
}

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

console.dir(hasNumber)
console.dir(hasLetter)

image.png

继续展开。好像都一样,但当我们点开标注的地方。就发现里不一样。原来,闭包理保存了不同的变量。
image.png

image.png

关于这个[[Scopes]],我在下一篇里详细展开,其实,也是我百度的。
链接地址:

2)提前确认,提前确定了会走哪一个方法,避免每次都进行判断
实例代码:

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);
    }
}

3)延迟运行,js中经常使用的bind,实现的机制就是Currying
实例代码:

Function.prototype.bind = function (context) {
    var _this = this
    var args = Array.prototype.slice.call(arguments, 1)
 
    return function() {
        return _this.apply(context, args)
    }
}
柯里化函数的经典面试题一

1) 需求分析:
实现一个add方法,使计算结果能够满足如下预期
add(1)(2)(3) = 6;
add(1, 2, 3)(4) = 10;
add(1)(2)(3)(4)(5) = 15
2)实例代码:

      function add(){
       // 第一次执行时,定义一个数组专门用来存储所有的参数
       var _args = Array.prototype.slice.call(arguments);

       // 在内部声明一个函数,利用闭包的特性保存_args并且收集所有的参数值 
       var _adder = function(){
           _args.push(...arguments);
           return _adder;
       }; 

       // 利用toString的隐式转换的特性
       // 当最后执行的时候进行隐式转换,并进行最终的值返回
       _adder.toString = function(){
           return _args.reduce(function(a,b){
                return a + b;
           });
       };
        return _adder;
    }

    // 6
    console.log( add(1)(2)(3));
    // 10
    console.log( add(1,2,3)(4));
    // 15
    console.log( add(1)(2)(3)(4)(5));
柯里化函数的经典面试题二

1)需求分析:
实现 sum(1)(2)(3) 返回结果是1,2,3之和
2)实例代码:

   // 实现 sum(1)(2)(3) 返回结果是1,2,3之和
    function sum(a){
        return function(b){
            return function(c){
                return a + b + c;
            };
        };
    };

    // 6
    console.log(sum(1)(2)(3));

3)实现一个curry函数,将普通函数进行柯里化
实例代码:

    function curry(fn,args=[]){
        return function(){
            let rest = [...args,...arguments];
            if(rest.length < fn.length){
                return curry.call(this,fn,rest);
            }else{
                return fn.call(this,rest);
            };
        };
    }

    // test
    function sum2(a,b,c){
        return a + b + c;
    }
    let sumFn = curry(sum2);
    //  6
    console.log(sumFn(1)(2)(3));
    //  6
    console.log(sumFn(1)(2,3));

你可能感兴趣的:(2021-02-07 JS中的柯理化,高阶函数,偏函数。以及闭包的英文表达closure)