精华 javascript闭包和柯里化的深度解释
发布于 3天前 作者 tulayang 560 次浏览 来自 分享


javascript拥有简洁的表达,使你可以专心于算法攻略。就好像黑白机上的闯关游戏,你拾取了宝剑,只需要不停地点A就可以了。你唯一要思考的就是如何不停地跳躲Boss的大招。

javascript成为浏览器的唯一语言,并且成为世界标准许多年,是有非常重要的理由的。《JavaScript: The Good Parts》做出了非常清晰地解释。

JavaScript:The World’s Most Misunderstood Programming Language

然而,想要掌握javascript的正确编写方式并不容易。尤其是当你从教科书开始的时候,大部分给你的信息都是面向对象的东西:newprototypeclassextend,…

这些都不是正确编写javascript的方式,当然你可以这么做,但是你会恨上javascript,这样编写的感觉会让你觉得你在写java却不能拥有java一样的计算速度。

想要喜欢上javascript并且享受programming的快感,你需要放下一切对面向对象的理解,走进函数和求值的世界。你的所有代码只有function,这是简单的并且灵活的。


什么是函数?


如同数学界,函数function即代表了求值。然而,从另一个角度来讲,函数function也是值:

g = f(x)

我们认为g是一个值,来自f函数的计算,输入是x。然而,我们可以把值g作为一个函数,输入y,再次进行求值:

v = g(y)

把函数function赋予值的定位,可以使计算充满了灵活:

v = f(x)(y) = k(f, x, y)


什么是柯里化Currying?


f(x)(y)就是柯里化:使用函数f,输入x,计算,获得一个新的函数,再次输入y,计算,获取结果。f(x)(y)(z)(a)(b)(c),你完全可以写这样的函数。每次进行一次计算时,都返回一个新的函数。当然,你也可以写成这样的方式g(x, y, z, a, b, c)

function f (x) {
  return function (y) {
    return function (z) {
      return function (a) {
        return function (b) {
          return function (c) {
              console.log(x  + y + z + a + b + c);
          };
        };
      };
    };
  };
}

计算6个数字相加为什么要费这么大的周折?原因在于可以获得拥有内部记忆的函数: g = f(1)可以获得一个新的函数g。可以使用g编写多种不同的求值组合,而使第一个输入始终是1:

g(2)(3)(4)(5)(6);
g(3)(3)(4)(5)(6);
g(9)(9)(1)(2)(3);


什么是闭包Closure?


上边的函数就是闭包,确切的说,利用柯里化机制的函数function就是闭包函数。通过柯里化,获取一个拥有记忆功能的函数,简化后续的多种计算操作,这就是闭包。

function move(start) {
  var pos = start;
  return function () {
    console.log('Move to ' + (pos += 2) + '.');
  }
}

var move_next = move(6); 
move_next();  // Move to 8. 
move_next();  // Move to 10.


进阶:记忆


下面我们来看一下经典的缓存函数。开始有两个输入参数,一个是数组sets,一个是求值函数f

function memorize(sets, f) {
    var cache = {}; 
    return function (x) { 
        console.log('cache: %j', cache);
        return x in cache
               ? cache[x]
               : cache[x] = f(sets[x]);
    }
}

首先,我们在memorize的内部空间放置了一个记忆单元:cache,是一个Object类型,这样我们就可以用来存储任何我们想要的数据。Object类型可以看做是简化并且统一版的C语言中的struct:不需要考虑链接,不需要考虑类型,解释器会为你完成。

接下来,我们运用柯里化返回一个函数。这个函数有一个输入参数,指定了sets数组中的第几项进行计算。我们首先使用console.log打印当时内部空间的记忆单元cache的值,然后判断输入参数是不是在cache的键中。如果已经存在,直接返回记忆的内容,如果没有存在,使用函数f对输入参数sets[x]求值,然后把结果记忆到内部空间的记忆单元:cache中。

通过记忆,每次使用求值函数f计算后,都将结果保存在cache中,这样可以极大的降低重复计算:

var g = memorize([1000, 2000, 3000], function (x) { return x * x; });
g(0);  // cache: {},计算1000*1000
g(0);  // cache: {"0":1000000},来自记忆
g(0);  // cache: {"0":1000000},来自记忆
g(0);  // cache: {"0":1000000},来自记忆
g(1);  // cache: {"0":1000000},计算2000*2000
g(1);  // cache: {"0":1000000, "1":4000000},来自记忆
g(1);  // cache: {"0":1000000, "1":4000000},来自记忆


进阶:让函数循环起来


我们已经看到了柯里化(闭包)的好处和妙处,同时这些也都是函数function概念帮助我们完成了一系列繁琐的工作。下面我们将把函数function运用到循环中,进一步了解函数的好处和妙处。

function map(sets, f) {
  var i = 0, len = sets.length, result = [], val;  
  while (i < len) {
    val = f(sets[i]);
    result.push(val);
    ++i;
  }
  return result;
}

函数map使用两个输入参数:一个数组sets,一个求值函数f

首先,我们计算数组sets的长度,设定一个位置符i。然后对sets进行循环的操作,把其中的sets[i]进行求值,然后压入result中,最后将result返回。

通过这样的设定,我们使函数f在循环中运转。我们还可以进一步,再放入一个条件函数,只有条件成功的时候才进行求值:

function map(sets, condf, f) {
  var i = 0, len = sets.length, result = [], val, set;  
  while (i < len) {
    set = sets[i];
    if (condf(set)) {
      val = f(set);
      result.push(val);
    }
    ++i;
  }
  return result;
}

上边我们刚刚讨论了柯里化,所以把这个函数改一改,变成柯里化:

function map(sets, f) {
  return function (condf) {
    var i = 0, len = sets.length, result = [], val, set;  
    while (i < len) {
      set = sets[i];
      if (condf(set)) {
        val = f(set);
        result.push(val);
      }
      ++i;
    }
    return result;
  }
}

现在,函数map在循环中进一步增加了灵活性,我们可以这样方便的使用:

var mymap = map([1,2,3,4,5,6], function (set) { return set + 1; });
mymap(function (set) { return set % 2 === 0 });  // 偶数
mymap(function (set) { return set > 9 });  // 大于9

// 如果你熟悉Ecmascript 6,那么代码会非常有趣
var mymap = map([1,2,3,4,5,6], set => set + 1);
mymap(set => set % 2 === 0);  // 偶数
mymap(set => set > 9 );  // 大于9

→ 如何玩转闭包和柯里化

13 回复

学到"=>"用法,第一次见,很惊奇,浏览器居然认,很简洁。谢。

捕获.PNG

function move(start) {
    var pos = start;
    return function () {
        console.log('Move to ' + (pos = pos + 2) + '.');
    }
}

var move_next = move(6);
move_next();  // Move to 8. 
move_next();  // Move to 10.

@yuyang041060120

确实存在这个错误,已经修改过来

😘 自豪地采用 CNodeJS ionic

有意思,学习了 自豪地采用 CNodeJS ionic

感谢楼主分享

@LongHorn-C 奇怪,为何我的chrome竟然报符号异常的错误,你用的是什么浏览器啊

@LongHorn-C 记得coffee里面有类似的写法

@LongHorn-C 好吧,原来只有在es6上才支持这种语法,不过不知道为何chrome的console里竟然也报错。。。

温习一下知识~

霍霍,赞 自豪地采用 CNodeJS ionic

回到顶部