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));
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342