手记

柯里化技术分享(后续更新...)

一、柯里化介绍

柯里化其实是为实现多参函数提供了一个递归降解的实现思路— —把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术

简单理解就是:一个把具有较多参数 的函数转换成具有较少参数函数的过程 。如图:

此图来源于网上

二、简单案例说明

  1. 问题:求长方体的体积,写一函数。
// 一般
function volume(l,w,h) {
    return l * w * h;
}
const aCylinder = volume(100,20,90) // 180000

那么从上面柯里化的概念,我们是不是也可以得出这样的写法:

function volume(l) {
    return (w) => {
        return (h) => {
            return l * w * h
        }
    }
}
const aCylinder = volume(100)(20)(90) // 180000

通过上面的简单案例,大家对柯里化函数有什么理解?…待

那么,大家有想过一个问题么?如果有一百个参数呢?难道要写一百次?有没有一种方法可以简单的帮我们实现柯里化?
运行案例

function curry(fn) {
  var outerArgs = Array.prototype.slice.call(arguments, 1); // 用来接收参数,除了fu之外的参数存进outerArgs
  
  return function() {
    var innerArgs = Array.prototype.slice.call(arguments),
      finalArgs = outerArgs.concat(innerArgs); // 收集传入的参数,进行缓存

    return fn.apply(null, finalArgs);
  };
}

const say = (name, prefix, greeting) => `${greeting}, ${prefix} ${name}!`;

const curriedSay = curry(say);
curriedSay('Tom', 'Mr', 'Hello'); // "Hello, Mr Tom!"
curry(say,'Tom', 'Mr')('Hello');  // "Hello, Mr Tom!"

【项目场景中的应用】
讲到这,大家能举例出在在项目中有哪些柯里化的应用么?…待

axios 案例

例:监听事件 EG:

function nodeListen(node, eventName){
    return function(fn){
        node.addEventListener(eventName, function(){
            fn.apply(this, Array.prototype.slice.call(arguments));
        }, false);
    }
}

var bodyClickListen = nodeListen(document.body, 'click');
bodyClickListen(function(){
    console.log('first listen');
});

bodyClickListen(function(){
    console.log('second listen');
});
// 使用柯里化,优化监听DOM节点事件。addEventListener三个参数不用每次都写。

三、柯里化使用场景

  • 参数复用

固定不变的参数,实现参数复用,比如递归用法。

  • 延迟执行

通过bind方法

<div onClick={handleOnClick.bind(null, data)} />
// bind 方法和以上实现的 currying 方法,在功能上有极大的相似,在实现上也几乎差不多。
// 可能唯一的不同就是 bind 方法需要强制绑定 context,也就是 bind 的第一个参数会作为原函数运行时的 this 指向。而 currying 不需要此参数。所以使用 currying 或者 bind 只是一个取舍问题。

  
  Function.prototype.myBind = function(oThis) {
      if(typeof this !== 'function') {
          return;
      }
      var self = this,
          args = Array.prototype.slice.call(arguments, 1);
      return function() {
          return self.apply(oThis, args.concat(Array.prototype.slice.call(arguments)));
      }
  }
  function foo() {
      console.log(this.a);
  }
  var obj = { a: 2 };
  var bar = foo.myBind(obj);
  bar();      // 2

箭头函数

<div onClick={() => handleOnClick(data))} />
// 箭头函数能够实现延迟执行,同时也不像 bind 方法必需指定 context。可能唯一需要顾虑的就是在 react 中,会有人反对在 jsx 标签内写箭头函数,这样子容易导致直接在 jsx 标签内写业务逻辑。

通过currying

<div onClick={currying(handleOnClick, data)} />
  • 性能对比

通过jsPerf(js基准测试)测试四种方式的性能,结果为:

箭头函数 > bind > currying > trueCurrying 。

currying 函数相比 bind 函数,其原理相似,但是性能相差巨大,其原因是 bind 由浏览器实现,运行效率有加成。

从这个结果看 Currying 性能无疑是最差的,但是另一方面就算最差的 trueCurrying,而 trueCurrying 方法中实现的自动 Currying 化,是另外三个方法所不具备的。

四、到底需不需要柯里化

  • 为什么需要柯里化

1.为了多参函数复用性

2.为函数式编程而生

例如:纯函数、compose、container等等事物,也需要用到Currying。

  • 为什么不需要柯里化

1.柯里化的一些特性也有其他解决方案,比如提前绑定参数,如箭头函数,bind等。
2. 使用柯里化等函数式特性有额外的性能开销。

a. 存取arguments对象通常要比存取命名参数要慢一点.

b. 一些老版本的浏览器在arguments.length的实现上是相当慢的.

c. 创建大量嵌套作用域和闭包函数会带来花销,无论是在内存还是速度上.

五、问题探讨

  1. 理解以下函数
let curry = function(fn) {
    var judge = function(...args) {
        if (Boolean(args[args.length - 1])===false) {
            return fn(args);//three(null)就到这里了
        } else {
            return function(...arg) { //one和two和three都是这个函数
                return judge(...args, ...arg);
            }
        }
    }
    return judge;
}

testCurry = (args)=>{
        args.pop();//去掉最后一个参数null
        if(args.length<1){
            return;
        }
        let sum = args.reduce((s,n)=>{
            return s+n;
        },0);
        console.log('参数',args);
        console.log('sum',sum);
        return sum;
    };
    
       OnClick =()=>{
        console.log('执行了这里 OnClick');
        let one = this.curry(this.testCurry)(1);
        let two = one(2)(3)(4)(5);
        let three = two(6,6,6,6);
        three(null);
    };
 // 函数理解(借鉴去理解文档第二个例子)   
judge并没有立即执行,而是在(...arg)=>judge(...args,...arg);方法被调用时才被执行
这个curry后的函数只有在传入的最后一个参数可以被转换为false的时候才开始执行,它存参数的方法也很简单
1,判断如果最后一个参数是否可以是false
2,是的话就把所有保存的参数放到fn里执行,结束
3,否则,返回一个新的匿名函数,这个函数把所有传入参数保存在arg数组中,而这个匿名函数被执行后,就把以前收到的参数数组和当前的参数数组合并后,放到前面说的逻辑中,在judge函数里判断,重复第1步

六、推荐借鉴资料

0人推荐
随时随地看视频
慕课网APP