JavaScript王国之函数教主

本篇是JavaScript王国的一次旅行,一个没有类的世界怎么玩转面向对象?的续集


来源:码农翻身 微信公众号

作者:刘欣

原文地址:javascript王国之函数教主

前言

本篇由BlindingDark撰写, 老刘修改,原文参见 http://www.jianshu.com/p/74120c8ec56a

拜函数教

Java 小王子在 JavaScript 王国待了也有一段时间,这里虽然不像 Java 帝国那样规范严苛,但也因此千奇百怪,五光十色。

要说小王子最喜欢待的地方,那还是人来人往的 JSON 酒馆,不仅有上好的酒菜,还有机会认识到各式各样的人。这不,一来二去他已经和上回认识的眼镜官员成了朋友,甚至私底下还称兄道弟的。

这一天小王子又和眼镜大哥一起约来吃酒。才寒暄了几句,筷子还没动,酒馆门口就发生了一点骚动。只见来了一位看起来像是教书先生的精瘦男子,旁边还有些随从。

“…… 大哥,这人什么来头,气度不凡啊。”小王子悄声向旁边的眼镜官员问到。

“哈哈,他是拜函数教的邱大教主,最近他们教派的信众激增啊,真是风水轮流转。”

“函数式?以前随父亲经商途中是听说过他们的事情,好像非常古老而且高深莫测啊,据说只有学者和虔诚的教徒才会加入他们,怎么最近也接收新人了?”

“小弟果然是见多识广,不错,在很久以前的确是这样,不过为何有大批新教徒这种事情也不在我的管辖范围内啊,所以也不太清楚。但是毕竟我是本国语言规范审查官,还是与他打过一些交道。不妨我们邀请他来一起喝酒,你亲自问他。”

小王子本来听到眼镜大哥也不了解情况正有些失望,突然得知可以直接面对面打探对方的底细,顿时兴奋了起来。

“那太好了!”

“这不是眼镜老弟么,别来无恙啊。”

“主教兄,甚好甚好,要不这顿我请?小二!”

饭菜上桌,互相客套了几句之后,话题就开始了。

“主教兄,这是我最近认识的朋友,年纪轻轻就周游四方,他有些事情要问你。”

“哦?”函数式主教把目光放了过来,“有何见教?”

“久闻贵教派向来神秘,为何最近有如此多的新教徒加入呢?”

“哈,这个嘛 … 现在的年轻人都不喜欢条条框框,本教向来以简洁强大著称,自然就受欢迎了。”

小王子心想,这个教主倒也是有话直说、自卖自夸,不过难道没了面向对象这种强大的武器,他还能变出怎么样的花儿来?

“那还敢请教教主大人,依你看要怎么实现 Animal, Cat, Dog 这些对象呢?”想了一会儿,小王子认为不如直接发问。

“这个嘛,本教派并无对象这种说法,不过如果你愿意,也可以构造一个对象出来,只需要……”

“我们拜函数教不用对象~”,还没等着主教说完,旁边的妹子突然发话了。

“哦,小兰,你来说吧。”这位被叫做小兰的少女像是主教的助手,看起来深得主教信任。

“嘻嘻,教主大人说的太复杂了,其实实现你说的那些根本不用什么对象。”

“哦?那该怎么做?”小王子顿时来了兴趣。

函数是一等公民

“你想啊,搞出来这些猫啊狗啊的,不就是想让他们都可以吃东西么?干嘛要封装到一个对象中,太压抑了”

哦!Java 小王子恍然大悟,在自己的Java帝国,法律非常严格, 类和对象才是一等公民, 函数是不可能独立存在的, 你即使是想输出一个简单的hello world , 也必须写一个类,在类中写一个方法 , 在这个方法中才能输出hello world 。

在这里函数已经翻身做主人,成为了一等公民,再也不用困在对象的牢笼中,声明后就可以直接使用。

“也就是说,猫、狗都可以直接拿来调用咯?”

“是的呢~ 不止是猫、狗,连人都能作为参数调用呢, 只要一个对象有name这个属性就行啊”小兰的脸上还是一样的笑容。

长期在一个强类型的语言中生活,小王子对这种有点“变态”的灵活性还真不习惯, 他说:“在Java帝国,方法的参数都有确定的类型,如果你的方法写错了,IDE就能自动帮你检测, 减少了很多错误,现在可就没这种福利了。”

“有得必有失嘛”小兰笑道 “你可以多写一点单元测试来保证正确性啊”

“既然函数是一等公民, 我相信它不止这点能耐吧?“

“那是自然, 我们这里的函数还可以作为参数传递给另外一个函数呢!

小王子突然想到父王给自己讲过,要在Java8中引入函数式编程, 可以把一个所谓的lambada 表达式传递给另外一个函数, 省去了创建类或匿名类的开销。

但是宫廷老师偷偷地告诉过自己:“那只是半吊子的函数式编程,每个lambda表达式其实还得和一个函数接口相匹配, JDK在背后做了类型的推断”

(微信公众号码农翻身注: 参见文章《Java帝国之函数式编程(上)》和《Java帝国之函数式编程(下)》)

“函数既然能作为参数,我猜肯定也能作为返回值吧?” 小王子觉得拜函数教的邱大教主在场, 这个天真活泼的小兰好像不敢给自己透露太多, 只好主动发问。

“你这个外乡人很聪明嘛, 估计有不少编程基础吧?” 小兰又展示了一段代码:

果然和自己的猜想一致! 作为皇族,小王子确实非常聪明,他很快就想到了另外一个问题: 如果内部的eat函数访问了外部函数的变量会发生什么状况?

按照我们 Java 帝国的理论, 函数在被调用的时候是以栈帧的方式被压入栈中的, 函数的局部变量也在栈帧当中, 当这个函数返回,对应的栈帧就会被清除,局部变量自然也不可用了。

现在这个eat函数还能再使用createEatFunction()函数的desc这个变量吗? 或者在调用它的时候会使用外边的全局desc 变量? 从而输出 “dog 正在吃东西”?

函数作为返回值看起来很美, 但细节着实让人费解啊?

闭包

看到小王子像上次一样又“卡”住发呆了, 眼镜官员笑着对主教和小兰说: 这小子肯定又在深思了。

小兰说:“这个外乡人确实不一般,思考很深入,让他的CPU再运转一会儿吧”

小王子没有找到答案,从思考中回来,看到大家都盯着自己看,有点不好意思, 把疑惑给大家说了。

“外乡人,Java 把你毒害得不浅, 我看你资质不错,不如忘掉Java 那丑陋的模型, 加入到我们拜函数教来吧。 将来你继承我的位置也未可知啊!” 邱大教主看了小兰一眼,意味深长地说。

小兰接口说:“还是先解释下你的疑惑吧, 你那段代码的输出应该是‘dog is eating’ , 而不是 ‘dog 正在吃东西’”

小王子说: “这我猜到了, 我就是不理解一个函数(createEatFunction)都执行完了, 为什么它的变量(desc)还能被 后面执行的函数(eat)来使用。”

“要不说你的java毒中的很深啊, 你换个角度想想,局部变量在函数执行完以后还可以被访问, 那它肯定不能存在于你说的那个什么java栈帧中。” 小兰说。

“那它放在那里?”

“在JavaScript 当中,有个作用域链(scope chain)的东西,它定义了一个函数激活执行的时候去哪儿找变量的值, 比如eat函数的作用域链是这样的:”

(微信公众号码农翻身注: 此图只是示意图,并不严谨,例如没有表达出Activation object, Variable Object等概念)

小王子看着这个图,马上就明白了,这个eat函数没有定义desc 这个变量, 所以就沿着链去查找, 在createEatFunction作用域去查找,如果还找不到,就到global 作用域中查找……

这个例子中,在createEatFunction的作用域中有定义,于是就直接使用了。

“可是 ”小王子不服气地说 “createEatFunction 已经执行完了, 难道它的desc 变量不删除吗?”

“那肯定不能删除喽” 小兰说 “这里和你熟悉的java 有个重要的不同, 当你执行creatEatFunction的时候, eat函数才会被创建出来,此时eat函数就会把外部函数的作用域链记录下来(其中包含desc=’ is eating’),以便执行时使用”

“奥,这个作用域是在函数创建时刻发生关联的,而不是运行时刻”

“没错, 这叫做静态作用域(static scope),或者叫词法作用域(lexical scope)。当eat函数被激活执行,就可以在createEatFuncction中找到desc的值,而不是在全局中找到desc的值。 ” 小兰接着说,“我再给你看个例子, 你就理解这个静态作用域了”

小王子说: “这段代码的输出应该是2 啊, 还有什么可说的,哦,不对不对,应该输出1, 静态作用域! 函数foo在创建的那一刻, 已经确定了它会和包含x=1的全局作用域关联 , 所以在运行的时候也只会从全局作用域查找,而不是从bar 函数的作用域中找x ”

“看来你已经Get到了, 这个静态作用域是实现闭包的一个必需条件, 你听说过闭包吧?”

小王子说: “我听父王说过,但是不知道怎么回事….”

小王子说漏了嘴,赶紧打住。

小兰像没有听到一样,继续讲闭包:“闭包在JavaScript当中就是一个函数和以静态方式存储的父作用域的一个集合体,通过这个集合体,一个函数就可以访问外部函数的变量了。 ”

“所以eat函数就可以访问外部函数的变量了! 闭包这个名称有点古怪,不过背后的概念还是比较清晰的” 小王子赶紧说。

尾声

“咳咳,小莱你够了。客人都被你吓到了。”这时,在旁边默默看着的邱大教主说话了。

经过这一番交谈,小王子震惊于函数式的独特之处,尤其是和Java帝国的不同。

不过毕竟是皇族,阅历丰富,他还是悟出了一点门道,“我大概明白了一点,由于 JavaScript 是动态类型,其实无论是函数还是对象,在这里都可以做为一个值来传递。函数式里面偏向对值直接进行处理,通过对这些值的传递和组合,就可以组装实现更高级的功能。”

“不错不错,我看你这位朋友的来头可不一般啊,新加入的信徒大多都经过漫长的适应期才能理解,他却立即悟出这些道理来。”邱大教主对小王子赞赏有加,或许以他的智慧已经识破了小王子的身份。

“不不不,还是因为小兰妹妹讲的好啊。”小王子脸红的说道。

“这还不是多亏了我们的原型才能自由灵活的实现各种编程范式么。”眼镜大哥也参合了起来。

“哈哈哈,你又在自夸了。”

“来,说了这么多 eat,不说了,吃菜吃菜~”

看来编程世界上还有这么多种形态,小王子下次要去哪里,又会见识到怎样的东西呢?或许也能给他的 Java 帝国带回一些新鲜的血液?

登场人物名:

小兰 == lambda

邱大教主 == 阿隆佐.丘齐(Alonzo Church) Lambda演算发明人