流年似水博客开通了,本站主要是写关于Web和大数据方面内容,正在更新中,欢迎大家光临!
  1. 文章:97 篇
  2. 总浏览:34,379 次
  3. 评论:22条
  4. 最后更新:2020-06-08
  5. 分类目录:39 个

柯里化函数实现详细解答

Javascript l, xy 176℃ 0评论

前言:

在开展柯理化函数实现的知识之前,我们需了解apply、call、bind的使用,以及valueOf、toString隐式转换知识。

什么是柯里化?

把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。

柯里化有三个特点:提前返回、延迟执行、参数复用

记住这个特点,接下来,就是见证奇迹的时刻。

一道面试题

    求:add(1)(2)(3) = 6?

    可能看到题目会觉得,这不很简单吗,嵌套3个函数就搞定了啊,考闭包呀!于是写下:

var add = function(a) {
        return function(b) {
            return function(c) {
                return a * b * c
            }
        }
    }

然后求出来的结果确实是 6,对的,那我们再加大一下难度 add(1)(2)(3)...(n) ,这咋办呀,总不能嵌套n个函数,这不合适,柯里化能解决这个问题。

两处用到柯里化特点的方法

  一、监听函数封装:

执行事件之前需要判断是否是ie浏览器,普通的写法是这样的

var addEvent = function(el, type, fn, capture) {
        if(window.addEventListener) {
            el.addEventListener(type, function(e) {
                fn.call(el, e);
            }, capture);
        }else {
            el.attachEvent('on' + type, function(e) {
                fn.call(el, e);
            })
        }
    }

这样写使用是没问题的,但是不好,为啥呢?你想,你每一次执行一个事件都要调用addEvent函数然后执行一遍这个if-else判断,

这样就浪费资源了,我们只需要执行一次判断,然后执行函数就完事了。于是我们优化了一下。

  //使用了柯里化的提前返回和延迟执行的特点
    var addEvent = (function() {
        if (window.addEventListen) {
            return function(el, type, fn, capture) {
                el.addEventListen(type, function(e) {
                    fn.call(el, e);
                }, capture)
            }
        } else {
            return function(el, type, fn) {
                el.addEvent(type, function(e) {
                    fn.call(el, e);
                })
            }
        }
    }())

执行到这行代码的时候会直接执行(function(){})(),判断当前浏览器,返回对应浏览器执行函数,这里你会发现,判断只执行了一次,再调用addEvent函数,也只会执行它的return后面的返回函数,使用了柯里化的提前返回和延迟执行的特点。

  二、bind函数实现:

柯里化另一个使用例子就是bind函数的实现,使用了柯里化的参数复用提前返回的特点。

    //实现一个bind, 柯里化的参数复用和提前返回
    Function.prototype.bind = function() {
        var fn = this;
        var args = Array.prototype.slice.call(arguments);
        var context = args.shift();

        return function() {
            return fn.apply(context, args.concat(Array.prototype.slice.call(arguments)));
        }
    }

    var name = "window"
    var bindObj = {
        name: 'bindObj',
        fn: function() {
            console.log(this.name);
        }
    }
    bindObj.fn.bind(this)(); //window

柯里化实现

简单的实现:

    1、通过arguments获取参数,存储参数

  2、使用apply执行处理函数

    // 初步封装,参数复用、提前返回
    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) } } var test = function(a, b, c) { return a * b * c } currying(test)(1, 2, 3)//6

但是感觉哪不对呀,题目是add(1)(2)(3),这长得也不像啊,还是不能实现无限参数的功能。然后我们需要升级一下。

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

   add(1)(2)(3) // f 6
   add(1, 2, 3)(4) // f 24
   add(1)(2)(3)(4)(5) // f 120
   add(2, 6)(1) // f 12

执行过程:

 1、add函数只会在第一个括号时调用一次( 执行add(1) )

2、第二个括号和之后的括号执行的都是返回的_adder函数

_args的存储:

每个括号的参数会通过arguments拿到,在_adder函数里面拼接每个括号的参数。

valueOf函数与toString方法深入理解

js的数据类型除了null和undefined,都含有valueOf和toString方法

 var obj = {
            i: 10,
            valueOf: function() {
                return this.i + 20;
            },
            toString: function() {
                return this.valueOf() + 10;
            }
        }
        alert(obj > 20) //true
        alert(+obj) //30
        alert(obj) //40

执行 alert(obj)时,我们并没有做什么计算之类的,为什么打印出来的值却不是10呢?只有一种情况,它自动走了valueOf或者toString方法,

接下来就让我们验证一下什么情况会走valueOf或者toString方法。

var obj2 = {
        i: 10,
        valueOf: function() {
            console.log("valueOf");
            return this.i;
        },
        toString: function() {
            console.log("toString");
            return this.i;
        }
    }

    alert(obj2); //10 toString
    alert(+obj2); //10 valueOf
    alert('' + obj2); //10 valueOf
    alert(String(obj2)); //[object Undefined] toString
    alert(Number(obj2)) //10 valueOf
    alert(obj2 == 10); // true valueOf
    alert(obj2 === 10); //false

我又写了一个obj2对象,得出一个结论:在有运算符号和转换为数字类型时会执valueOf,取值和转化为字符串时会执行toString,"==="情况下没有隐式转换。

总结:

柯里化,是函数式编程的一个重要概念。它既能减少代码冗余,同时柯里化的特点:提前返回、参数复用、延迟执行,我们在写功能或者封装优化代码的时候很容易都会遇到。闭包,以及bind、apply、call都是必须掌握的技能。柯里化函数实现不只是考察无限参数的求成积,更多的是为了检测应试者基础知识的掌握。

后记:

本着学习和总结的态度写的技术输出,如过本篇文章有任何错误和问题,请大家指出。

转载请注明:流年似水 » 柯里化函数实现详细解答

喜欢 (1)or分享 (0)

Warning: copy(https://cn.gravatar.com/avatar/?s=54&d=%2Fwp-content%2Fthemes%2Fyusi1.0%2Fimg%2Fdefault.png&r=g): failed to open stream: HTTP request failed! HTTP/1.1 400 Bad Request in /usr/share/nginx/html/timewentby/wp-content/themes/yusi1.0/functions.php on line 239

Warning: copy(/wp-content/themes/yusi1.0/img/default.png): failed to open stream: No such file or directory in /usr/share/nginx/html/timewentby/wp-content/themes/yusi1.0/functions.php on line 243
发表我的评论
取消评论

表情

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址