JS 奧義解析(4):閉包

李世雲發表於2016-07-28

奧義是平凡中所蘊含的不凡,能有效地用於指導實踐

揮棒必向更強者

我們都知道,七十二變是孫悟空的成名絕技之一,但並沒有人能列舉這七十二變都有哪些,也木有記載。孫悟空只是在需要用的時候,即自然幻化。而豬八戒則只有三十六變,沙僧則更少只有十八變。

我們可以猜想,其實這些變化的底層實現都是一樣,只不過運用之妙,存乎一心。這就像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基礎,或者根本不關心語言本身。