書的封面

之前寫過一篇1.5 本書的誕生——《Beyond XSS:探索網頁前端資安宇宙》的幕後故事,裡面提到了我原本想要寫一本關於 JavaScript 的書,叫做「JavaScript 隨意聊聊」,寫到七八成之後因為種種原因先擱置了,跑去把另一本資安的先寫完。

那篇文提到的 0.5 本書「JavaScript 隨意聊聊」,就是現在的《JavaScript 重修就好》,這篇除了稍微介紹一下這本書以外,也來聊聊背後的故事。

為什麼一定要是書?

第一個理由我上篇有提過:系統性。

JavaScript 的部落格文章我寫過很多篇了,但總覺得零零散散的,想要把它們全部整合起來,變成更有系統性的作品。而寫書或許是最簡單把這些東西變成系統性的做法,畢竟都要出一本書了,不可能把部落格文章全部照搬過來一字不改吧?好歹要補個前言,把每篇都串起來,才能成為完整的一本書。

比起「想知道 this 是什麼可以看 huli 的文章」,或許我更想聽到的是「想更深入學習 JavaScript 可以去看 huli 的書」這句話,有種被認可的範圍增加了的感覺,成一家之言(?)

但老實說想把作品變得系統性,寫書並不是必要條件,為什麼一定要書、而且是實體書?你弄個 GitBook 或是像 Beyond XSS 架個網站,不也是很有系統性嗎?那為什麼一定要是書呢?

再者,其實在寫的過程中,我就隱約發現書本這個載體,對於某些內容不太友善。例如說 event loop 的運作過程好了,你再怎麼寫都比不上經典的影片:所以說event loop到底是什麼玩意兒?| Philip Roberts | JSConf EU 裡的動畫。雖然可以退而求其次,用一連串的圖片來解釋,但要論「容易理解」的話,影片還是更有效果的。

除了影片,網頁前端的內容用網頁本身作為載體,也能發揮非常強大的力量,如 Josh Comeau 的部落格文章:An Interactive Guide to CSS Grid,附上許多互動式的元件,邊學邊在網頁上展示給你看,還可以自己調整參數觀看結果,對讀者來說肯定是更容易理解的。

從這些案例中可以得知,儘管技術類書籍的理念通常是「講解某些東西,使讀者理解」,但在載體的選擇上,卻有著更多的考量,不一定是以這個為主軸。舉個其他領域的例子,許多人拍 YouTube 影片也不是因為「我想講的東西用影片表達會更好」,只是「無論如何,我就想拍影片」或是「因為 YouTube 很紅,有利可圖」,用文字 3 分鐘可以讀完的東西,還是拍成了 20 分鐘的影片,乘載的資訊量卻是相同的。

因此,對我來說選擇實體書籍這個載體,更多的可能是情懷。

無論電子書有多方便做得多好,可以的話,我還是會選擇實體書。我喜歡書拿在手上的那個實感,喜歡看封面、書腰以及作者介紹,享受一頁一頁翻開的那種感覺。

就算內容物相同,實體的東西有其不可抹滅的意義存在。或許這個說法可以改一下,所謂的「內容物」不僅包含書中的文字(這確實是相同的),連觸感、視覺上的感覺還有重量也都算是內容物的一部分,從這角度理解,電子書與實體書確實大不相同。

總之呢,我喜歡書,喜歡親眼看到、拿到書的感覺(讀不讀是另外一回事),因此儘管這整個系列文做成互動式網頁帶來的學習效果絕對更好,我依然會以書本作為第一優先。

(2013 年時我的書櫃,是我看最多書的時期)
書櫃

書的內容與部落格文章的關聯

這本書最一開始的規劃在這裡:JavaScript 隨意聊聊,原本就想好要寫幾個主題:

  1. JavaScript 介紹
  2. 型態
  3. Hoisting、closure 與 scope
  4. Object 與 prototype
  5. Asynchronous

從這個主題再往底下去拆,而有些以前部落格寫過的內容,小幅修改之後可以直接套用進來,就不需要整本都重新寫。

除了「原本就有文章,拿進來用很方便」以外,更多的其實是我不覺得我能比以前的自己寫得更好。有些主題隨著經驗變多,重寫一次會有更多看法,能夠補充更多東西,但有些主題是有時效性的,一旦過了那個時期,你就再也寫不出這麼貼切的文字。

書的內容一共有 22 個小章節,大約有一半 11 個小章節是源自於部落格文章,小幅調整之後移植到書中。所以若是有些讀者看一看覺得有點眼熟,好像在哪裡看過,這不是 Déjà vu,而是真的。

話說回來,當初有些部落格文章在寫的時候,原本就是按照書的規劃來寫的。是我寫一寫覺得:「阿,好像寫不完,那不如先發出去好了」,才發出去的,像這幾篇就是:

  1. 從歷史開始認識 JavaScript
  2. 你知道的 JavaScript 知識都有可能是錯的
  3. 從「為什麼不能用這個函式」談執行環境(runtime)
  4. 使用 JavaScript 的數字時的常見錯誤
  5. 來數數 JavaScript 的所有資料型別

前三篇再搭配更早寫的獨立文章 Don’t break the Web:以 SmooshGate 以及 keygen 為例,就成了書中的第一章:從重新認識JavaScript開始。

而後兩篇再搭配以前寫過的覺得 JavaScript function 很有趣的我是不是很奇怪為什麼打開檔案時會看到亂碼?跟著小明一起從傳紙條學習編碼,再加上我在 MOPCON 2021 給的講題:你懂了 JavaScript,也不懂 JavaScript,構成了書中的第二章:重要與不重要的資料型別。

第三章在聊物件時,基石也是幾篇以前發表過的文章,2017 的該來理解 JavaScript 的原型鍊了、2018 的深入探討 JavaScript 中的參數傳遞:call by value 還是 reference?以及 2021 的基於 JS 原型鏈的攻擊手法:Prototype Pollution,後續再搭配全新撰寫的幾個小章節。

接著第四章談底層運作,雖然以前也談過,多少能看到這些文章的影子,如三部曲我知道你懂 hoisting,可是你了解到多深?所有的函式都是閉包:談 JS 中的作用域與 Closure淺談 JavaScript 頭號難題 this:絕對不完整,但保證好懂,但這次是以不同的角度去切入,這個等等會再聊更細一點。

第五章非同步,則是以 2019 這個只有上集的斷尾作品JavaScript 中的同步與非同步(上):先成為 callback 大師吧!為基底繼續發展,在 6 年後終於把中集還有下集一次補完(是的,6 年前我想寫的東西就跟現在差不多了,可見我的拖延症從來都不是說說而已)。

以上就是書籍中有參考到部落格文章的部分,看起來很多,實際上也真的不少,如同我之前說的,大概有一半的篇幅都是。每篇部落格文章在移植到書籍之前都有做了一些修正,例如說同樣是 prototype pollution,在部落格文章中是舉 Vue 當範例,在本書中則是改成用 Google reCAPTCHA,盡可能做到讓內容不要完全一致。

最後,比起講述哪些東西是從部落格移植過來的,不如談談哪些是完全新寫的好了:

  1. 字串的編碼與使用上需注意的事項
  2. 型別轉換
  3. defineProperty 與 Proxy
  4. 深層複製與淺層複製
  5. V8 引擎執行過程
  6. Closure 在實務上的運用
  7. Promise 與 async/await
  8. Microtask/task 在 React 以及 Vue 上的運用

這些其實我部落格想寫很久了但一直拖稿,是趁著寫書的機會一次搞定的。對了,這也是為什麼想寫書的另一個理由:強迫治療拖延症。

章節的脈絡

我一直覺得若是能找到一個方法把點連成線,就能大幅增進自己對於現有知識的理解(儘管大部分都是你本來就知道的東西)。因此書籍的編排基本上也是按照這條我認為的線在走。

其中最重要的就是第一章:「從重新認識 JavaScript 開始」。

對我來說,知識決定自信,對某些事物的理解越深刻,無論做什麼事都會更有自信,說出「我辦得到」。只會把 AI 的回覆貼出來,説「可是 AI 這樣講」的人,是沒有腦子的。在探討一些知識類的問題時,當你說出口的東西沒有證據支撐,就沒有聽的必要,因為無法判定真偽。

因此最重要的一件事,就是需要知道 JavaScript 的真理要去哪裡找,當 AI 告訴你「JavaScript 是直譯式語言」時,你必須知道該怎麼驗證這句話的真確性。用不用 AI 輔助都無所謂,重點是你的答案必須基於「因為 V8 就是這樣實作的」或是「因為 ECMAScript 這樣寫」,而不是「因為 AI 這樣講」。

因為莫名的個性使然,對於我感到興趣的知識,我會對真實性有異常嚴格的要求。舉例來說,「不小心就滑進去」或是「咻的滑進摩鐵了」是誰講的?

答案是 PTT 鄉民自己造謠的,接著媒體跟隨著推波助瀾。阿基師有進摩鐵是事實,但並沒有講這兩句話,原話逐字稿是:「結果哪會想到他車門一過來,前面一走一轉個彎就進去了,我根本來不及反應阿,講真話」,金句中特別滑稽的「滑」或是「咻」這兩個字完全沒有出現。

舉這個例子只是想說明我對感到興趣的東西,會想要試圖去查明真相,而 JavaScript 也是。這點為我帶來很多的自信,因為這不是人人都具備的能力。

除此之外,看一件事情的角度會影響你的思考方式。

有些人會認真看待像是 wtfjs 這些東西,覺得 JavaScript 這是什麼鬼,哪有程式語言這樣搞的。

但對我來說就只是笑笑就過了,沒什麼大不了的。JavaScript 與其他程式語言最不同的地方在於它跑在網頁上,而且又秉持著盡量不做 breaking changes 的這個原則,不想把以前可以動的東西搞壞。

在這個原則底下,已有的缺陷也沒辦法修正,只能一直增加新的東西。

那在已知這個語言就是這樣運作(動態型別加弱型別盡量不讓程式壞掉)的前提下,一直強調部份缺陷的意義就不大了。再者,許多例子都是平常不會看到的 edge case,拿這些 edge case 去指出一個語言的缺陷之處,對我來說也是意義不大的。

這些在書籍的第二章裡面有講到,我會把型別的知識分為重要與不重要的,不重要的笑笑看過就好,不用太認真,把時間花在重要的知識上會更有價值。

我並不是在説 JavaScript 是個設計良好的程式語言,沒這回事。我是想說儘管它有些地方設計不良,但它改不了,而身為前端工程師我們也逃不開。因此,比起花時間在關注那些根本不會出現的狀況下([]+[] 或是 {}+[]),不如去學更有價值的知識。

再來是第三章「物件與有趣的 prototype」,從物件開始,帶到 prototype、call by value/reference 以及 Vue 的雙向綁定還有深層複製等等。

這裡的第一個重點是理解 prototype 這件事,這個我以前的文章就有寫過了,許多人分不清楚應該都是搞混 __proto__ 與 prototype 兩者之間的關係,是名詞的混淆。

另一個常見的搞混原因是根本不知道 prototype 這東西要幹嘛,自然也就只能死記硬背,根本學不會。但只要從原本的目的物件導向開始切入,就能讓整個脈絡變得清晰。

既然都談到了物件,談到了 defineProperty,那自然而然要談離大家最近的東西:Vue 的狀態。看看 Vue2 是如何使用 defineProperty 的,以及為什麼 Vue3 要切換到 Proxy。既學習了這兩個的用法,又看到了 Vue 的原始碼,不香嗎?

而最後談的深層複製這個主題我也想寫很久了,從還沒有 structuredClone 時就想寫了。之前第一次自己實作深層複製時就發現這主題滿有趣,是可以一直往下問的。

舉例來說,手寫一個簡單的深層複製不難,可以先從複製物件開始。

成功之後換成陣列,此時就可以問說陣列需不需要特別處理,沒處理會變怎樣?再來試試看複製其他物件如 Date,支援更多型別。

上面這些都處理完以後,下一個就是:「那循環引用怎麼解決?」,解決了以後可以問第二個問題,引用關係要保留怎麼辦?如 [a,a,b] 複製完要是 [a_1,a_1,b_2],前兩個的 reference 要保持一致。

當我們實作完一個陽春版的以後,就掌握了基本概念。

下一步可以去看看知名的開源專案如 lodash 是怎麼實作的,跟我們的實作差在哪裡,有哪些細節可以學習?

接著有很多人都知道現在有 structuredClone 可以用,不需要自己手寫,那它底層的實作是什麼?

對,我特別愛談底層實作,書中會帶大家一起看看在 Chromium 裡面,structuredClone 實際上是怎麼做的。當其他人只能聊到 API 的層次,而你能再往下一層談瀏覽器實作,就比別人多了一層理解,掌握了更多知識,也能擁有更多自信。

而第四個章節「從 scope、closure 與 this 談底層運作」也是如此,這主題是我放在最後面寫的一個主題,也是我會拖稿的原因之一。

原本我是想仿照以前的文章,從 ECMAScript 的角度去切入,以前是用 ES5 來當範例,現在用 ES6 之後的新版模型,談 Realm、Agent 以及 Execution Context 等等,這部分需要花不少時間去研究。

然而,我看一看發現第一是它不好弄懂,第二是就算弄懂了,這些知識也有點太硬,不是這麼容易啃得下去。那不如換個方向,一樣聊底層,但是不聊規格,我們聊實作。

所以與其像舊文那樣去談規格,我選擇去談 V8,談 JavaScript 是怎麼被執行的,V8 又做了哪些事情。我認為這樣的脈絡,會比光談規格更容易理解整體發生了什麼事。

而另一個重點是之後在談 closure 時,我也屏棄了以前部落格文章談規格的做法,改成了談範例。因為我後來發現許多人之所以學不好,是因為根本不知道學這個要幹嘛,要用在哪裡。要破解這題,找幾個實際範例就對了!

因此我找了幾個實際用到的範例,去談為什麼要用 closure。

最後的第五章「理解非同步」則是實現了多年來的願望,把沒寫完的部落格文章主題補完。一樣從最常見的 event loop 下手,先談最廣為人知的 task,接著進入到 Promise 去談 microtask。

理解非同步原理以後,再搭配實際案例一起學習。

而這個案例我挑的是 Vue 跟 React,如 Vue 的 nextTick 跟 React Fiber,都用到了非同步,而且都碰到過問題。

舉例來說,想要安排一個 task 你會怎麼做?

最直覺的做法是 setTimeout(fn, 0),但這做法的問題是當你遞迴呼叫時,延時會自動變成 4ms,因此我們需要更好的做法:MessageChannel

細節在書裡面都有,這邊就不細談了,但從 Vue 跟 React 的原始碼還有 Issue 中,我們可以學到「為什麼要用這個」或是「為什麼不用這個」,是學習非同步時非常好的範例。

以上就是本書的大致內容,應該把大部分 JavaScript 的核心概念都寫進去了。

總結

寫完這本書以後,最開心的是我自己。

我一直想把這些零散的文章集結起來,變成一本系統性的書籍,如今終於達到了這個里程碑,耶依。

話說這本書的原名其實是叫做《JavaScript 二周目》,是電玩遊戲用語,指的是破關一次之後,再打第二次會出現新的任務或道具等等,滿符合我想表達的意境。但跟出版社討論過後覺得知道這名詞的人不多,因此改成現在這個重修就好(老笑話了,會寫在飲料杯上的那種),意境也差不多,但應該更好懂一點。

但我還是滿喜歡原本這個書名,讓它在這篇文章出現一下留個紀念。然後,如果你覺得封面跟重修沒什麼關係,你是對的,那個封面原本是針對二周目設計的,我還滿喜歡這封面的。

最後不免俗地放個天瓏書局的購買連結:JavaScript 重修就好,若是想從海外購買,可以試試看博客來,電子書的話就要自己搜一下了,沒搜到的平台應該就是沒有。