JS 奥义解析(4):闭包

529 查看

奥义是平凡中所蕴含的不凡,能有效地用于指导实践

挥棒必向更强者

我们都知道,七十二变是孙悟空的成名绝技之一,但并没有人能枚举这七十二变都有哪些,也木有记载。孙悟空只是在需要用的时候,即自然幻化。而猪八戒则只有三十六变,沙僧则更少只有十八变。

我们可以猜想,其实这些变化的底层实现都是一样,只不过运用之妙,存乎一心。这就像JavaScript和php,底层都是c实现的,但这二者却千差万别,php可以读写文件,可以多线程并发,可以多态继承;而JavaScript则被局限于浏览器之内守着自己的单线程。

可以想象应该存在这样的推论:JavaScript和php都是c实现的,他们应该具有相同的能力,至少应该具有相同的潜能。

而事实也正是这样的,当JavaScript被从浏览器中解放出来之后,它也拥有了php所拥有的一切能力,不去讲谁更胜一筹,至少他们站在了同样的高度。

民间也有龙生九子子子不同的神话传说,但实际上站在相同的起点上,他们都拥有无限的潜能,只要运用得当(努力的学习和历练),最终都可以站在同样的高度,尽管本领不同,性格迥异。正所谓一门三进士,大概也是这个道理。

再往底层,计算机不过0和1而已,但构建在其上的程序良莠不齐,能力更是大相径庭。虽然他们不都能发挥出最大的能力,但潜能是一样的,如果有人愿意改造,再破的小程序都可以跟unix一样站在同样的高度。当然,已经有了unix,对于程序来说,再来一个unix并没有什么用;但对写程序的人来说,却存在着巨大的意义,如果你也能写出unix的核心,尽管比别人晚了几十年并且还借鉴和学习了它的写法,虽然你可能还是不能站在和unix一样的高度,但你也已经足以超越自己,在不断的超越自己之后,你就有可能超越侪辈,脱颖而出。并且也有人做到了啊,只不过这人把他写的unix命名为linux。这句话看起来轻描淡写,但实际我们都知道,你必须十分努力,才能看起来毫不费力。

所以悟空、八戒和沙僧的变化之术很可能拥有相同的底层架构,只不过悟空不断的超越自己,最终脱颖而出,成为了齐天大圣。八戒也在不断的超越自己,但是他超越的侪辈更少一些,沙僧就更少,但这也已经足以让他超凡脱俗,和凡人区别开来。用卤讯的话来讲就是脱离了低级趣味。一样的了不起。

但世间自由山比此山更高,天外有天,人外有人,不排除就有人会一百零八变,会二百五十六变,既然底层架构是一样的,只要悟空自此精修,五百一十二变又未尝不可。这不是痴人说梦,等他学会五百一十二变得时候再回过头来看七十二变还不是so easy, 这就等同于他会七十二变的时候看着只会一十八变的沙僧,一个想的是so easy, 一个想的是我根本无法做到七十二变。其实只要沙僧相信自己也有七十二变的潜能,并不断修炼,总有一天他也就自然而然顺理成章的会了七十二变。并且也有人做到啊,六耳猕猴就达到了和孙悟空一样的高度,可惜人不可争一时之胜。也像杨过,断臂精修之后,武功还是那些武功,但天下已经鲜有敌手。

再说这底层架构,变化所需的都是固有属性(property),但是每个人添加的附属属性(attribute)是不一样的, 这就像一个框,有的人做出来是白色的,有的人做出来是薰衣草色的,也有人是闪着荧光的绿色,这是一个框还是三个框,是一种变化还是三种变化,我有七十二变,框是一变,但每一个框都不尽相同,在低标准者的眼里,这就是千变万化,但齐天大圣从来都不是低标准者,所以在他眼里不过七十二变。往事历经千年,也许今天的悟空已经学会了七百二十变,但早已不在现身人间,还是一样的争强好胜,但每挥棒必向更强者。

Functions that return functions

这是我见过的最简洁优雅的对于闭包的定义,并且形式上或者从外表来看,确实就是这样的,当然,即使是这样,这个定义要成立也应该还有两个前提:

  1. 你必须了解作用域(scope), JavaScript的作用域可以简单的说是由函数分隔或者界定的,所有我们称之为函数导向(function oriented)。
  2. 你必须了解垃圾回收机制(garbage collection)的引用计数(reference count)。
  3. 函数界定的函数导向的作用域决定了子级及子级以下作用域可以访问父级及父级以上作用域,反之则不行;垃圾回收机制的引用计数决定了一个变量如果不在被引用就会被垃圾回收。

说到这里其实问题就很明显了,可以猜出这里边可能涉及一个变量(不用说也可能是多个,毕竟大家都是举一反三的人),这个变量不会被回收,但是不会被回收还不足以突出他的与众不同之处,因为所有的全局变量都不会被回收,那么在结合它的名字这么一推导,闭包这个词根据语义化可能是用来装东西的,包嘛,毕竟用来装变量也是很合理的,这就印证了它用来装东西的猜想,这时候再结合functions that return function一想机会也就明白了,在父级作用域里定义一个变量,在子级作用域引用它,并且让它不会被回收,前面说过,全局作用域的变量是不会被回收的,所以顺利成章的把子级作用域也就是这个函数(函数导向的嘛,可以粗略理解为作用域就是一个函数)return 出去给全局作用域。你可能会诟病return出去之后不一定是给的全局作用域,但是对于子级作用域来说它父级的父级作用域那就可以理解成全局作用域,毕竟青蛙看到的也不过井口大的天,你得设身处地的去换位思考,所以变通一下嘛,别在不该聪明的时候聪明(另外,聪明是贬义词)。

所以聪明应该在大多数人只发现缺点的时候你用聪明去发现优点,大多数人在诟病的时候你用聪明去寻找一个合适的解释,去循着这个东西被创造、被命名的轨迹寻找那些闪闪发光的地方,吐槽很廉价,特别是没有经过认真思考的吐槽。甚至不应该帮助不经过认真思考不经过努力的人,因为那不过是浪费时间。

当你真正理解了返回函数的函数这个概念之后,真正的运用就在于你了,至于你是将其用作一个闭包,一个工厂,还是另做它用,就完全存乎你一心,这时候你可以说你掌握了闭包,掌握了工厂,理解了函数不过是一个变量,你可以说出一百种花样来,也可以说只有一招,只是运用这一招的你已经不再拘泥于这一招。

当然读到这里你应该脑子里已经闪过了另一个概念,如果没有,那就应该再学习了。与scope相对应的运行时上下文(runtime context), 这个是对象导向(object oriented)的,这里所指的对象是狭义的对象可以理解为{}。说的简单一点就是运行的时候,this等于哪个对象,你在用this点这点那的时候具体执行哪个对象里的属性或者方法,这也是为什么要加运行二字的原因。

每个人都拥有一样的潜能

JavaScript里Math对象和空对象{}其实具有同样的潜能,一样的强大,只不过空对象你得自己费尽去扩展,等同于人来说就是努力付出。但为什么又说每个人都具有同样的潜能呢,因为Math也可以扩展,人生就向一个状态机,不管你处于什么样的状态,只要还在努力付出,那就没有行或者不行,没有优秀或者差劲,只要还在不懈的追求,人生就具有不可比性,因为你永远不知道孰优孰劣。

附言

程序是理性的,但写程序的人是感性的,而奥义本来也是平凡中所蕴含的不凡,所以这一系列的文章将更加偏向理论、哲学,是用普遍联系的思想来组织的,不仅仅局限于JavaScript,也不仅仅只适用于编程,所以我们假设看这一系列文章的人都拥有良好的JavaScript基础,或者根本不关心语言本身。