我的 JavaScript 世界觀

老姚發表於2019-04-01

我們都生活在主觀的世界裡,但真實世界卻是個複雜系統。

對於一個非線性系統來說,用任何線性思維去理解都會所偏頗。

用《失控》的觀點來說,對於非線性系統,你只有執行起來才知道它具體會是什麼。

話雖如此!

《好好學習》一書中,一個重要假設就是:複雜現象背後都是由幾個簡單的規律所主導的

個人認為 JS 也是屬於這種情形的。

JavaScript(ES3) 為啥是這樣?

也許只有 JavaScript 的作者才清楚吧,畢竟他只花了 10 天就發明了這個強大的存在。

本文不想去回顧歷史,也不是去還原作者的創作思路。而是以嘗試以自己的思路去理解 JS。

希望本篇文章,能給一些初學者點啟發,尤其對於以類為主導的語言出身者(比如 Java 使用者)。

除此之外,本文再無所求。

JS 的發明者 Brendan Eich 說這門語言主要有兩大特性:一等函式物件原型

一等函式,我認為這個才是 JS 最為核心的東西。

以函式是一等公民為出發點,就能解釋 JS 為啥有那些難點。

什麼是一等函式呢(First-class function)?

函式是一等公民是說,函式被設計成了“值”。

值能幹什麼它就能幹什麼。

值可以存進某個變數,或者某個結構,那麼函式表示式宣告就是理所當然的。

值除了可以被賦予某個變數,也可以單獨存在。比如 "x",所以函式就可以是匿名的。

值可以作為引數傳入函式,函式也可以,因此在 JS 中回撥的實現顯得那麼自然。

值可以作為返回值,函式也可以。這一點就非常重要了,因為這直接導致閉包這一機制必須存在。

值可以隨時用,也就是動態的,函式也可隨時用,因此匿名函式自執行,我覺得非常合理。

除了函式之外還有其他一等公民嗎?

函式是,陣列是,物件也是。他們都體現了“值”的特性。

因此在 JS 中,有兩句話,特別有名:

JavaScript 的一切都是物件,

JavaScript 的一切也都是值。

二者在 JS 中實現了對立統一。


知道這些後,也許你對很多東西就不再迷糊了,反而會越合計越合理。

下面將舉例來說明,為啥 JS 中有那麼多“奇怪”的東東。

1. 函式做為引數

比如:

setTimeout(function() {}, 1000);
複製程式碼

還記得當初學習JS時,感覺很迷糊。函式竟然能當引數。

setTimeout(later, 1000);
複製程式碼

你還記得,很多書都告訴你不要寫成 later() 嗎?

後來我認識到函式是一等公民後,學習 es5 時,也就不再迷糊:

[1, 2, 3].map(function(value) { return value * value; });
複製程式碼

函式能當引數傳遞,JQuery 對它的使用可以說達到了極致。具體應該不用舉例了吧。

2. 函式作為返回值

函式作為返回值這塊兒,直接導致了閉包的存在。

當然這不是充要條件,但可以作為一個理解閉包出發點。

我們知道,函式基本作用就是作為一個可複用程式碼的封裝,有輸入,有輸出。

比如:

function sum(a, b) {
  return a + b;
};
複製程式碼

但是當函式竟然還能返回函式時,有趣的事情就發生了:

function sumCreater(x) {
  return function(y) {
    return x + y;
  };
}
var sum10 = sumCreater(10);
sum10(12)// 22
複製程式碼

函式能返回函式是好事,但是被返回的函式最起碼應該能執行才行。

你要執行的話,內部引用的外部變數自然而然就該能被找到。

不然怎麼辦?難道你想讓瀏覽器直接報錯?這個機制就是閉包。

這裡具體不展開,因為我只是在搭建世界觀。



關於函式能當返回值,能當引數。這就涉及了高階函式概念。

因此也有了很多函數語言程式設計的基礎知識,比如柯里化,偏函式,函式組合等等。

3. 匿名函式呼叫表示式

(function() {})();
複製程式碼

函式是可以呼叫的,這沒啥可說的。那麼宣告個函式,直接執行一下,這應該一點也不奇怪。

4. 物件

一等函式是核心,之所以這麼說,也因為它對 JS 支援物件導向程式設計機制起到了關鍵作用。

一個物件,應該有屬性,有方法。因為函式是值,所以下面的程式碼,應該很親切:

var object = {
  say: function() {
    console.log('hello world!');
  };
};
複製程式碼

5. this 問題

因為物件的方法,要經常用到自己的屬性,this

是應該必須有的,不然用什麼指代當前這個物件本身呢?

var object = {
  name: 'laoyao',
  say: function() {
    console.log(this.name);
  };
};
複製程式碼

那麼這裡就涉及到一個問題,this 是在函式語義下的存在。

假如直接呼叫函式,this 指向什麼呢?

我又可以不可以動態修改一個函式 this 指向呢?

以上就是 JS 的 this 指向問題。

6. 物件複用機制

你有個東西,我覺得好,想為我所用。

辦法1,我去偷或者借,JS 中可以 call 和 apply。

辦法2,我 copy 一份,這就是 JS 中的混入(mixin)。

辦法3,我認你為爹。你都是我爹了,我用你的東西,還不行嗎?這是 Java 的世界觀。

辦法4,我當你是我的智囊。我解決不了問題,找你來想辦法。


辦法3和辦法4,就是兩種複用思想,一個是繼承,一個是委託。

而 JS 的 prototype,就是委託的機制。請參考《你不知道的JavaScript》上卷。

7. new 操作符

我總不能每使用一個相似的物件時,就宣告一個吧。

因此必須有種方式,通過類似“模板”的形式來做。這就是 new 的作用。

而 JS 中的 new 與 Java 中的 new,表層含義是一樣的,都是用於生成物件例項。

然而,JS 中的 new 在我看來只是封裝了物件間的委託關係罷了。

後記

上面總總就是我對 JS 的世界觀。

為啥要用“世界觀”這個詞語呢?因為我最近在看一本書,《世界觀》。

書中說,對世界的看法和信念,雖是一片一片的,卻是相互聯絡的,是能整合到一起去的,從而構成完整的拼圖。

比如你相信世界是以地球為中心的,那麼就可以解釋物體為啥會向下落。

同樣,假如你相信萬有引力,也可以解釋物體為何向下落。


如果採用某種方式,能讓自己覺得一些知識是自然而然,就本該是那樣的,也許會更好地對其吸收和利用。

當然,採用不同語言世界觀,就會形成不同的程式設計正規化。

就像有人喜歡使用物件導向程式設計來使用 JS,而有人喜歡函數語言程式設計那樣。


說到程式設計正規化,提下設計模式。

物件導向有物件導向的設計模式,函數語言程式設計也有自己的設計模式。

解決問題的動機是一樣的,但在 Java 中的複雜形式,而在 JS 中,卻簡單了很多。

這跟 JS 函式是一等物件是密不可分的。

提到了設計模式,最後說句 JS 的原型繼承方式,就是合成複用模式。

往往“有一個”比“是一個”的方式更好。

所以,你想有錢,不一定需要是富二代,你可以傍個大款。

所以,你想認識世界,不一定需要自己變成旅行者,你可以上知乎問“周遊世界是一種怎樣的體驗?”。
所以,你想幹成大事,不一定需要自己能力多強,你只要有幾個聰明人為你所用就行了。

所以,借刀殺人、狐假虎威以及借雞下蛋都是聰明人最愛使用的方法。

。。。


希望你也能找到一種方式,讓你產生 JS 就該是這樣的一種錯覺。

本文完。

相關文章