javascript闭包和柯里化的深度解释

<br />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,这是简单的并且灵活的。
<br />


什么是函数?

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

g = f(x)

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

v = g(y)

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

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

<br />


什么是柯里化Currying?

<br />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);

<br />


什么是闭包Closure?

<br />上边的函数就是闭包,确切的说,利用柯里化机制的函数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.

<br />


进阶:记忆

<br />下面我们来看一下经典的缓存函数。开始有两个输入参数,一个是数组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},来自记忆

<br />


进阶:让函数循环起来

<br />我们已经看到了柯里化(闭包)的好处和妙处,同时这些也都是函数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

→ 如何玩转闭包和柯里化

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念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

推荐阅读更多精彩内容