函数柯里化=>你真的知道吗?含Vue源码实现标签判断以及面试题

1,160 阅读5分钟
  • 柯里化(英语:Currying),又译为卡瑞化或加里化,是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。

  • 通俗的解释:

    • 柯里化就是一个函数原本有多个参数,只传入一个参数,生成一个新函数,由新函数接收剩下的参数来运行得到结果.
    • 与柯里化类似的二个有意思的概念 ====>偏函数高阶函数大家知道他的定义吗?下文会简单的给出答案.
  • 在JavaScript高级程序设计 里这样描述的:

    • 柯里化函数通常由以下步骤动态创建:调用另一个函数并为它传入要柯里化的函数和必要的参数.
    • 通用方式:
    function curry(fn){
        var args = Array.prototype.slice.call(arguments,1);
        return function(){
            var innerArgs = Array.prototype.slice.call(arguments);
            var finalArgs = args.contact(innerArgs);
            return fn.apply(null,finalArgs);
        }
    }
    function add (num1,num2){
        return num1 + num2;
    }
    var curriedAdd = curry(add,5);
    alert(curriedAdd(3);// 8
    
    • 在书中还有一个关于柯里化与绑定函数的结合示例:
      function bind(fn,context){
          var var args = Array.prototype.slice.call(arguments,2);
          return function(){
              var innerArgs = Array.prototype.slice.call(arguments);
              var finalArgs = args.contact(innerArgs);
              return fn.apply(context,finalArgs);
          }
      }
      //通过柯里化和绑定函数可以实现强大的动态函数创建功能.
      

    以上是JavaScript高级程序设计里对于柯里化的解释与简单示例.

  • 现在我提出个问题,如何判断一个标签是否为原生的html标签,给大家二分钟思考下.

    • 简单实现:最最简单了
        var str = 'html,body,base,head,link,meta,style,title,' +
            'address,article,aside,footer,header,h1,h2,h3,h4,h5,h6,hgroup,nav,section,' +
            'div,dd,dl,dt,figcaption,figure,picture,hr,img,li,main,ol,p,pre,ul,' +
            'a,b,abbr,bdi,bdo,br,cite,code,data,dfn,em,i,kbd,mark,q,rp,rt,rtc,ruby,' +
            's,samp,small,span,strong,sub,sup,time,u,var,wbr,area,audio,map,track,video,' +
            'embed,object,param,source,canvas,script,noscript,del,ins,' +
            'caption,col,colgroup,table,thead,tbody,td,th,tr,' +
            'button,datalist,fieldset,form,input,label,legend,meter,optgroup,option,' +
            'output,progress,select,textarea,' +
            'details,dialog,menu,menuitem,summary,' +
            'content,element,shadow,template,blockquote,iframe,tfoot';
        function isHTMLTag ( tag, str ) {
            var list = str.split(',');
            var flag =false;
            for (var i = 0; i < list.length; i++) {
                if(list[i] == tag){
                    flag = true;
                    break;
                }
            }
            return flag;
        }
        var isHTMLTag = isHTMLTag('div',str);
      
    • 这个方法是不是超级简单就实现了,但是这个方法单独使用ok一点问题没有,但是如果我们调用100次呢,我们循环就是(100*str的个数)次.
    • 大家可能会说不用for循环,用indexOf就没有循环,其实indexOf的内部实现也是有循环的.
  • 接下来我们来看看我们熟悉的Vue框架的源码中是如何实现这个功能的.

    • 源码其实就是通过函数柯里化,闭包等技巧来优化我们的操作的.
        /**
       * Make a map and return a function for checking if a key
       * is in that map.
        */
        function makeMap ( str, expectsLowerCase ) {
            var map = Object.create(null);
            var list = str.split(',');
            for (var i = 0; i < list.length; i++) {
                map[list[i]] = true;
            }
            return expectsLowerCase
                ? function (val) { return map[val.toLowerCase()]; }
                : function (val) { return map[val]; }
        }
        var isHTMLTag = makeMap(
            'html,body,base,head,link,meta,style,title,' +
            'address,article,aside,footer,header,h1,h2,h3,h4,h5,h6,hgroup,nav,section,' +
            'div,dd,dl,dt,figcaption,figure,picture,hr,img,li,main,ol,p,pre,ul,' +
            'a,b,abbr,bdi,bdo,br,cite,code,data,dfn,em,i,kbd,mark,q,rp,rt,rtc,ruby,' +
            's,samp,small,span,strong,sub,sup,time,u,var,wbr,area,audio,map,track,video,' +
            'embed,object,param,source,canvas,script,noscript,del,ins,' +
            'caption,col,colgroup,table,thead,tbody,td,th,tr,' +
            'button,datalist,fieldset,form,input,label,legend,meter,optgroup,option,' +
            'output,progress,select,textarea,' +
            'details,dialog,menu,menuitem,summary,' +
            'content,element,shadow,template,blockquote,iframe,tfoot'
        );
        var isTextInputType = makeMap('text,number,password,search,email,tel,url');//这个是判断是否是可输入框的
        var isHTMLTag = isHTMLTag('div');
        //没想到吧,Vue的源码里判断是否是原生的Html标签竟然是将所有的原生标签枚举出来的,惊喜不惊喜,意外不意外
    
    • 但是源码呢只是初始化的时候调用(str的个数)次,然后每次使用的时候因为函数柯里化因为闭包只需要根据map的值去检索,性能得到了很大的提升,虽然代码量变多了,也损失了部分性能与增加了额外开销,但是整体性能得到了很大的提升.
    • 这个makeMap方法我们可以拿出来用到自己的项目中去,也能算是一个亮点哟.
  • 好了,基础的大概就这些了,现在我们来看几道关于柯里化的面试题.

    1. 实现add(1)(2)(3)(4)...无限累加.
    2. 实现add(1)(2)(3)(4)...并且支持add(1,2)(3,4)(5)..或者add(1,2,3,4,5).
      • 先来解决无限累加问

      • 解决这个问题首先要知道:toString方法用于将当前对象以字符串的形式返回,toString()函数的返回值为String类型,返回当前对象的字符串形式,并且JavaScript的许多内置对象都重写了该函数,以实现更适合自身的功能需要.

            function add(a){
                function s(b){
                    a = a + b;
                    return s;
                }
                s.toString = function(){
                    return a;
                }
                return s;
            }
            var a = add(1)(2)(3)(4).toString();//10
        
      • 现在我们来解决第二个问题,下面这个该怎么处理呢

            var a = add(1)(2)(3)(4);   //10
            var b = add(1, 2, 3, 4);   //10
            var c = add(1, 2)(3, 4);   //10
            var d = add(1, 2, 3)(4);   //10
            
            function add() {
                var args = Array.prototype.slice.call(arguments);//伪数组转数组
                function hardAdd() {
                    var innerArgs = Array.prototype.slice.call(arguments,0);//伪数组转数组
                    args = args.concat(innerArgs);//合并参数
                    return hardAdd;
                }
                hardAdd.toString = function () {//重写toString方法
                    return args.reduce((previous, current) => {//用reduce做累加
                        return previous + current
                    });
                }
                return hardAdd;
            }
        
    3. 其实还有一个题是已知参数数量,累加达到参数数量后返回结果,大家可以试试啦
          fn(a,b,c,d) => fn(a)(b)(c)(d);//这样的
          //提示:fn.length这个返回的是啥呢?希望留言
      
  • 哦,对了,接下来跟大家说下 偏函数 还有 高阶函数 的定义吧(柯里化也放一起吧,便于大家比较). (ps:我认为柯里化就是每次只能传一个参数才算柯里化,但也不完全是)

    • 柯里化:一个函数原本有多个参数,只传入一个参数,生成一个新函数,由新函数接收剩下的参数来运行得到结果.
    • 偏函数:一个函数原本有多个参数,只传入一部分参数,生成一个新函数,由新函数接收剩下的参数来运行得到结果.
    • 高阶函数:一个函数参数是一个函数,该函数对参数进行加工,得到一个函数,这个加工用的函数就是高阶函数,或者函数作为返回值也是的.