引言
JavaScript 是每個程式設計師無法迴避的程式語言。它依託瀏覽器的支援,牢牢佔據著前端程式設計的市場,又憑藉nodejs,在服務端程式設計也佔有一席之地。很多程式設計師對它是愛恨交加,愛它的靈活方便,恨它過於靈活的型別轉換,複雜的包管理等等。今天,我想以一個 非JavaScript程式設計師的角度,來聊聊我所瞭解的 JavaScript,作為這些年來對它反覆淺嘗輒止的一個總結。
網頁
最早接觸到 JavaScript,是十多年前的QQ空間。當時的QQ空間是一個精緻的網頁,可以由使用者自行設定,好看的裝扮需要用Q幣購買,或者充值黃鑽才能獲得。而很多人不想充錢,又想讓自己的空間不那麼普通,於是有人發現直接在位址列輸入一段神秘程式碼,就能獲取裝扮。那時網上最常被搜尋的就是“QQ空間裝扮程式碼”。具體的原理我也沒搞清,到底是開發人員故意留的免費裝扮,還是被人找到了後門。總之,後來我在知道,那些程式碼裡有些就是 JavaScript。
時間再往前追溯,到網際網路剛剛出現的年代,最早的網路只有 HTML,畢竟網速和顯示裝置的限制擺在那裡,人們對於能看到純文字的網頁已經很驚奇了。後來漸漸地人民群眾對於美好生活的追求提高了,相應地網頁也要有更豐富多彩的效果。於是有了 CSS樣式,再後來,人們想在瀏覽器裡執行程式碼,換句話說,在使用者的裝置上直接執行通用的程式碼。於是出現了各種備選項,比如 Java 等等。最後是網景公司推出的 JavaScript 獲得了最廣泛的採用,這個名字據說也是為了蹭當年 Java 的熱度。
為了讓普通人最快速度上手,獲取最多的使用者,JavaScript 也有了一些特殊的適配,例如在語法上,字串可以用單引號,也可以用雙引號;表示式的末尾可以加分號,也可以不加分號。這樣避免了程式設計師因為語法上的好惡而放棄這門語言。
在功能上,提供方便的型別轉換,數字、陣列、函式都可以直接被 +
轉換為字串:
let a = 1;
a += "2" // '12'
let b = [1, 2, 3];
b += "2" // '1,2,32'
let c = () => {};
c += "2" // '() => {}2'
這樣的設計也許是因為網頁是需要顯示給使用者看的,資料被轉換為字串,是一種經常會使用到的操作。
OO
忘了是在什麼時候,可能是剛剛接觸程式設計的時候吧,就常常聽到人說起:“JavaScript 裡面一切都是物件”,那時候搞不懂啥是物件。現在我願意將其理解為一種類似雜湊表的結構,裡面有一些預製的項,例如,.constructor.name
就表示這個物件是由哪個類建立的,預設是 "Object" 類。數字是 "Number" 類,false
是 "Boolean" 類。但我現在至少可以舉出兩個例子來反駁上面這種論斷,undefined
和 null
都不是物件。
Object Oriendted(物件導向)的程式設計概念是由 Alan Kay 提出的,最早好像運用於 SmallTalk 語言裡面。強調的是在程式設計時模擬現實世界的物體,比如汽車,蘋果,動物等等,都可以看作是一個物件,物件之間透過訊息傳遞來交流。JavaScript 裡面有多少真正使用了物件導向的原則我不敢說,個人覺得,除了使用了 Object 這個名字,其它和麵向物件的本意似乎沒有多少關係,當然,物件導向和程式設計裡的一些其它概念一樣,早已嚴重偏離了其本意。
在 JavaScript 裡有兩種賦值,一種是值(value)傳遞,一種是引用(reference)傳遞。基礎的資料結構,例如數字,布林值,undefined
,null
是使用值傳遞,即賦值到新的變數時,是新建了一個資料, 與原來的資料再無瓜葛:
let a = 9;
let b = a;
a += 1; // 10
b // 9
而那些更復雜的物件,都是使用引用傳遞,即把資料的地址傳遞給新變數,相當於變數只不過是訪問資料的一個入口,而不是資料本身:
let a = {x: 1};
let b = a;
a.x += 1;
b.x // 2
這種對reference(引用)的大量使用,似乎已經成為了物件導向的標誌性特徵。藉助這種機制,程式設計師可以很方便地操作多個關係錯綜複雜的物件,這種情況在網頁程式設計中十分常見。所以,也可以說是JavaScript為了適應網頁程式設計而做出的設計。比如,一個頁面上可能有十幾個按鈕,十幾個輸入框,各種文字框,它們之間可能存在複雜的互動,這時這種隱式指標操作起來就很方便:
let a = {x: 1}
let b = {child: a}
b.child.x += 1;
a.x // 2
函式
函式有什麼特別的,哪個語言裡面沒有函式是吧?JavaScript 的函式也不例外,我們知道在一些別的語言裡是有Module(模組)這種東西的,函式必須定義在某一個模組裡面。JavaScript其實也差不多,函式必須屬於某一個物件。有人說不對啊,我直接寫一個:
function f() {
}
this
這樣的函式是不是不屬於任何物件呢?不是的,因為 JavaScript 本身是有一個全域性的物件,在瀏覽器裡就是 window
, 在 nodejs 裡我不知道是什麼,但肯定也是有一個全域性物件的。在函式裡面,使用 this
就可以訪問它所在的物件:
let a = {
name: "a",
f: function() {
return this.name
}
}
a.f() // 'a'
但要注意的是,JavaScript 的函式分為兩種,function
函式和箭頭函式,箭頭函式無論在哪裡被呼叫,裡面的this
都是其定義時所在定義域(scope)的this
:
let a = {
name: "a",
f: function() {
return () => this.name
}
}
a.f()() // 'a' function函式捕獲了外層的物件,賦值到其定義域內的 this 上,內部的箭頭函式繼承了這一 this
定義域 scope
這裡順帶提一嘴定義域的概念,在 JavaScript 裡面,只有 Function 能夠創造自己的定義域:
let a = {
x: x = 1
}
x // 1 普通物件是無法創造定義域的,所以我們能夠從外部訪問到 x
let f = () => {
y = 1
}
y // y is not defined 函式可以創造自己的定義域,從外部無法訪問到
class 也是一種 Function。這個在其它語言裡也算是比較常見的設計,將資料型別分為兩大類:函式和值。物件是屬於值
這一類的。
閉包
JavaScript 的 mutable 屬性還導致函式有了一種神奇的玩法,也是面試官最愛 —— 閉包。說實話我覺得這東西一般人用不好,但不妨礙大神們把它玩出花來。簡單來說,我們可以創造一個函式,同時創造一個只有這個函式能夠訪問的變數:
let add = (() => {
let x = 0;
return () => x += 1;
})();
add() // 1
add() // 2
這基於的還是Function的scope規則:函式體內定義的變數,在函式體外無法訪問。這有什麼好處呢?就是在其他語言裡面有類似 “private” 關鍵詞,可以定義物件私有的項(field),而 JavaScript 是沒有這種東西的,物件所有的項都是公開的。閉包使得JavaScript 裡也可以有私有項。
event loop
最後聊聊我理解的 event loop 吧。因為 JavaScript 一般情況下是單程式執行的,這個程式有一個訊息佇列,執行時會不定期地檢查訊息佇列裡的訊息,或者說事件(Event),按照FIFO的順序來處理這些事件:
setTimeout(() => console.log('1')) // 塞進 mailbox
setTimeout(() => console.log('2')) // 塞進 mailbox
console.log('0') // 列印
// 終於有時間可以檢查 mailbox
// 0
// 1
// 2
個人感覺 JavaScript 是把單程式的能力開發到了極致,提供了各種語法和工具來方便程式設計師在單程式的前提下去寫非同步的程式碼。這不失為一條獨特的道路。
總結
我很喜歡JavaScript這門語言,它簡單明瞭,易於上手。儘管在平時工作中我用它的機會不多,但在需要時,它總能幫我很快地解決問題。也是因為它的靈活性,暗藏了一些坑,一不留神可能踩進去,我的建議是對於第三方庫要審慎使用,多寫測試用例。
https://www.bilibili.com/vide...
本文參與了SegmentFault 思否寫作挑戰賽,歡迎正在閱讀的你也加入。