繼上一篇談到課程結束,這篇來談談這半年來的所學,用比較白話且引導式的方法來寫,另外穿插一些給學習者的小建議,也幫助我複習之前所學的一些概念。
學到現在,雖然不算優秀,但個人認為尚且合格,若我能做一件事情可以展現自己所知道的知識,又可以幫助一些對網頁程式設計有興趣的人,那我想,大概就是此時此刻寫這篇文章了。
這篇文章我會分成幾個段落,在每一個段落談談學習的內容,並不時穿插之前實作的筆記,所有筆記加上這篇文章,總字數應該有破十萬了,當然有參雜一些程式碼,不知道算不算多,不過從以前到現在真的很少打這麼多字。
如果你是完全沒有碰過程式的人,可以姑且看看一路的學習下來,有多少內容。如果你是稍微接觸過程式的人或者是高手,若我的文章敘述有錯誤或不清楚的地方,也請反饋給我,謝謝你們。
序
以構成網頁前端的三元素而言,我們都同意 Html 與 CSS 都很重要,但是我認為以程式設計面向來說,JavaScript 會是個重要的主戰場,它可能是你第一個接觸的程式語言,也可能也是最後一個,所以我也建議可以先從 JavaScript 下手。

JavaScript 幾乎從頭到尾貫穿我的學習之路,從一開始比較簡單的基本程式語法,到後面需要利用它來操作網頁的物件,使其產生動畫或跳出框框,甚至是送出我們輸入的帳號密碼,都需要 JavaScript 的支援。
在學習用 Vanilla JavaScript (就是不使用任何套件的純 JavaScript) 操作網頁節點之前,我左思右想都不懂一個程式語言是如何作用在網頁上的,因為沒學過的人很難去了解那個概念。小時候的電腦課讓我們知道網頁是由一堆 HTML Tag 組成,但就不懂網頁上的互動究竟是如何產生的。
但要用 JavaScript 操作網頁節點,就必須先理解網頁是如何渲染的,了解 DOM Tree 的相關概念後,我們才知道 JavaScript 可以操作網頁是基於什麼原理之上。
用白話的方式來比喻的話,JavaScript 就像是夾娃娃機的夾子,而娃娃就是網頁上的節點。你可以鎖定選取娃娃,抓起娃娃,把娃娃移動到你想移動到的新的位置,你也可以這個爪子把它移到洞口,或將這個娃娃送出這台夾娃娃機。
那或許你會想,這跟原本學習基本程式語言 for loop 與一些物件型別等等的又有什麼關係?試想這些語法都是你的零件,透過這些零件,我們可以強化我們的夾子,可能更快速或使用更有效率的方式夾娃娃。
學習 JavaScript 的過程就是這樣,即使到現在課程結束了,我還是不太敢說自己很有把握對自己的夾子有全盤性的了解,但至少,把娃娃夾出洞這件事情已經難不倒我了。
其實在學習最基本的 JavaScript 之前,有另外補齊最基本的網路概念,包括 HTTP 協議之下的 TCP/IP (三次握手)與前端和後端的 Request 與 Response ,之後又再更細分成 Header 與 Body,了解各種 HTTP Status Code 所代表的意涵,至於我寫的這些又是什麼意思呢?
原來上述所學習的內容早已充斥在我們生活之中,比如說你一定看過掛掉的頁面呈現 404,那你知道連線成功也有一個代號叫做 200 嗎?這些代碼就是 HTTP Status Code。我們依賴的 Wi-Fi 看似無線傳輸,其實底層再底層依舊是訊號塔與海底電纜。而每一個網頁瀏覽路徑的一來一往,原來不過也就只是一個要求,與一個回應罷了。

上述的知識,其實不用上課也可以學會,只要你願意對自己生活周遭的事物多些留意,並探究其因。雖然不算是很深的知識,卻讓我訝異於原來這些道理早就充斥在自己的生活之中,而我卻不自知。
明白了這些原理之後,我開始藉由 JavaScript 發送出第一個 Request,就如同那一支穿雲箭一樣 ? 它飛越了山稜與時空,當帶回了 Response 時,已經是半年之後的事情。
半年之間的所學
若依照課程進度走,我的學習可以被劃分成二十四週,但若要依我自己的分類,大概可以分成下列幾個階段:
零、基本工具 Git、GitHub Flow 與 CLI 指令 ( Week1 )
一、網路基礎概論、基本網頁架構與網頁三元素 ( Week 2— Week 8 )
二、後端程式碼撰寫 ( PHP )、資料庫與資訊安全 (Week 9— Week 15)
三、JavaScript 底層與前端工具適配 ( SASS / Webpack / Gulp ),與網站架構 SPA / MVC / SSR / CSR 理解 ( Week 16 —Week 20 )
四、前端框架學習 React / React-Router / Redux ( Week 21 — Week 24 )
如果你還有點耐心,我將在以下談談這半年來的學習心得與內容。
負一、前言
放在零之前,我覺得是個人學習習慣的問題。
在學習任何事物之前,你可能已經看過很多人的心得文,當你看著這些心得文,覺得他們都有某些你原本沒有的習慣,會突然覺得當下的自己好像欠缺了某些特質,於是你想開始學習,開始仿效他們已經做過的事。
但我想告訴你,請你先喜歡現在的自己,然後先停止去仿效,先不要急。
以我為例,一開始本來想配合課程,興致勃勃的想要好好寫好每日文章,但卻沒有堅持下去,原本的我頗為灰心,覺得自己是不是又要半途而廢了?
但事後證明,即使我沒有按照當時的計畫每日都記下自己的心路歷程,我最後還是可以整理成文。我之所以寫筆記,採用的方法不是按部就班,而是想寫就寫。
我常常跟同學聊天的時候聊到,我之所以寫筆記,主要的目的並非是要記錄成文,而是希望透過鍵盤的敲打,一字一句地敲進自己的腦袋之中。
若非自己的腦海有記憶,就算我回去看自己成文的筆記,也是有看沒有懂。你現在的學習方式,可能就是最適合你的學習方式,我沒有叫你保持不變,但請為了目的,而培養手段與方法,而不是看到別人的學習方法,就一味的仿效。
如果你覺得自己的學習方法已經很 OK,但似乎還沒有起色,請參考下面我的偶像大 H 的名言:
我知道你很努力,只是努力得還不夠久
寫文章是我的興趣,不管它有沒有獲得拍手,或是有沒有價值,很大情況下,我寫的文是沒有人會看的,但我只想說,一切的一切,都應該與我們的興趣相關,想做就做。
寫程式是你的興趣嗎?如果是,你可以繼續參考我的學習心得。
噢,對了,這半年來我是邊工作邊學的,這也是我的選擇。
零、Git 與 CLI
課程開始的第一週,因為作業繳交的部分採用 GitHub Flow,所以我們要先學會 Git,而 Git 最原始的用法就是透過 Command Line (CLI),CLI 的概念就是下指令,讓電腦按照我們的指令做事。
這週關於指令的操作都不算難,熟了就好,在 GitHub Flow 的概念才是重頭戲。我們直接去模擬工程師工作時的情境,假想你與同事正在開發一個專案,要怎麼做才可以在不修改到現有產品的情況下開發新功能?又或者主管要如何同意我修改的 Code 加到新的專案?
這樣的狀況就和我們打電動存檔很像,當你遊玩到 A進度,可以存檔在 a 欄位,當你又繼續遊玩到 A+進度,但你這時遊戲的進度,將會因為劇情中有不同的選擇而有不同的劇情走向,你想要兩種分支都體會到,你自然不會覆蓋掉原本的 a 欄位,而選擇儲存在 b 欄位。
大概就是這樣的概念而已,主進度就好比我們正在上線的產品 (主劇情),利用 Git 與 GitHub,來完成現實生活中的存檔任務。
這一週的作業是教你朋友使用 Git,我印象特別的深,因為就在我作業打完一萬多個字之後不知道為什麼突然被洗掉,Git 也找不到 log 記錄,所以我只好硬著頭皮調整心情,再將整個作業重新寫一次。
如果你有興趣,可以看看我的作業內容:跟你的朋友介紹 Git
一、網路基礎概論、基本網頁架構與網頁三元素 ( Week 2 — Week8 )
我們周遭的所有事物,其實都有故事。
而看似冰冷的網頁,一來一往間,也交織成富含資訊的血脈,分分秒秒傳遞著新知的熱血。
在這一階段,我們拆解了網頁的架構,褪去濾鏡,了解它的真身是眾多的標籤與語言。同一時間,我們去檢視它褪下的外衣,了解何謂 CSS,那些有顏色的框線與那些文字所在的位置語法,以及網頁是如何穿上它的。而後,我們開始為網頁,量身定做新的衣裳。
這段時間,我也去了解了網頁的由來,雖說不去探究太過往的史實,但仍學到了網頁是怎麼被帶到我們面前的。原來發送 Request 與返回 Response,再由瀏覽器解析並渲染 (render) 的過程是這麼一回事。
之後,我們開始運用 JavaScript 來操作網頁的元素。而在 Coding 過程中,第一次覺得困難的理論,就是捕獲與冒泡,我還記得當時,為了不讓 addEventListener 混淆我的視聽,而引用了某位政壇人士常說的某句話來幫助理解:
猴子不是爬到樹上才屁股紅,他本來屁股就是紅的,只是爬到樹上被你看到而已。
捕獲和冒泡的機制一直都是存在的,簡單介紹就是,當你觸及一個事件,比如說點選網頁,這個事件就會由外部元素一層一層傳遞至目標 (捕獲),再由目標往外發散出去 (冒泡),不論是匯集或發散,每一層都會經歷該事件的洗禮。
初學者剛開始學習很可能的錯亂是:我們以為是 addEventListener 賦予了該事件的存在,但其實不是,事件一直都存在,一直都有捕獲冒泡的傳遞,我們只是利用 addEventListener 掛一條絆線,讓事件通過時會鈴鈴響,觸發我們指定的函式而已。就像放了一棵樹擋在要道,猴子為了穿越而爬上樹,讓你看到了那顆紅屁股。
關於這一部份自己的理解補充,我寫在這裡
如果你沒有接觸過前端,你可能看不懂我在講什麼,但你一定看得懂紅屁股理論。其實這是 Huli 給我的啟發,也如同某位網紅曾說過的:
講得出專業術語的,不一定是專業,能用白話解釋給其他領域的人聽,才是專業的表現。
為了踏出邁向好的工程師的第一步,我們不一定非得要等到買了黃色小鴨之後才能侃侃而談。我試著用我自己的話語解釋一些我所瞭解的事情,我期待對方能聽懂,更期待能被指正。
開始解決一些學習上的疑問之後,開始學程式已經來到了第七週,我做出了一個自己還算滿意的作品,是一台可以連續運算的計算機。

當時老師開這份作業的時候,並不期待我們寫出太複雜的功能,單次運算能成功就好,但我並不想就這樣交差,於是在沒有吃午餐與晚餐的情況下,終於用自己的方法寫出了可以連續運算的計算機,做出來之後相當開心,因為有做出連續運算的同學並不多。儘管後面因為一些排版的錯誤導致按鍵亂掉,但我覺得能完成自己訂下的目標就已經足夠了。

在漫長的旅途中,我們當然很難讓自己一次又一次因為達到目標而感到滿足,但低潮時請去回想,我們當初為何選擇了現下想走的路。
除了上述的實作之外,對於初探 JavaScript 也有相當的心得,比如說型別、運算子與基本程式語言邏輯等等。我承認自己不算是有天賦的人,但若是從所學中找到興趣,就會想探究原理,比如說究竟什麼是基本型別不可變,又為何 { a : 1 } === { a : 1 } 為 false 等等,我都有在自己的心得筆記總複習中寫下為什麼,如何解釋,並搭配自己 Run 過的範例,讓自己真正了解。在求知的過程中真的挺快樂的。
當然還有很多沒有提到的部分,都記錄在我的複習筆記之中,關於前十週總共兩篇的總複習筆記:
第一篇是關於 Week1 — Week4,包括前述的 Git 與 CLI 和基本網路概念。另外內中對於基本 JavaScript 的介紹是我個人挺喜歡的部分。
第二篇是關於 Week6 — Week8,這篇就比較偏向學習筆記,心得的部分比較沒那麼多,主要是對於網頁三元素做基本的介紹,與瀏覽器的 JavaScript 如何運作。
二、後端程式碼撰寫 ( PHP )、資料庫與資訊安全相關 ( Week 9 — Week 15 )
什麼是後端呢?為什麼後端的概念,前端也要懂?
對我來說這種問題就和談戀愛差不多,如果你不了解時常與你相處的另一半,那這段感情可能也難以維持。
後端的概念,若僅僅是以入門來說,並不算太難懂,雖說這週開始編寫 PHP 的過程需要重新習慣,但是你想到的,Huli 也想到了,於是他讓我們與它黏了大約一個月,形影不離。
程式碼只是工具,重點是我們要解決什麼問題。第一個帶給你的情境,叫做 Server Side Render,就很像我們有一個部落格的首頁是 index.php,點選一個連結會連到子資料夾底下的 posts.php,資料夾路徑就成了 blog/posts.php 。我們發一個 Request,而後端返回一整個 posts.php 給瀏覽器 render,這就是 Server Side Render,簡稱為 SSR,而如果你沒做其他設定的話,它的路由也會長得像上述的資料夾路徑。
倘若你對一開始的學習階段介紹還有印象,應該會知道我把 SSR 排在第三階段,那為什麼第二階段就碰到它了?其實很多名詞跟概念都是前人經驗的累積才統一有一種說法,而我學習的路線,就是循著這條老路,更好地去理解前因後果。
這就讓我想起有一個工程師朋友,有一天我跟他聊到 MVC,他說大概懂就好,可以先不用太過探究其內容,可以上了職場遇到問題再研究。因為當你沒有實際碰到因為 MVC 所產生的問題時,再怎麼鑽研用語都搔不到癢處。

在這四週,雖然還沒正式學習何謂 MVC / SSR 或是 CSR 等專有名詞的概念,但等到未來揭曉答案的那一天,這樣的名詞分類才會讓你感同身受,哦!原來是這麼回事!
回到正題,在這個階段我們基於 SSR 的架構一步一步做出一個含有會員系統的留言板,每篇文章都可以留言,每篇留言也還能有子留言。留言系統的呈現出你的思考邏輯與對 PHP 語法的熟練,而我們另外有替留言板做會員登入系統,而這則伴隨著一個早就存在你我生活中的概念:Session 與 Cookie
你有沒有好奇過,為什麼登入過的網站再次造訪不用再輸入帳號密碼?當你卸下理所當然的濾鏡,想起當時那個年代,還沒有什麼人臉辨識系統,然後你猜想,或許在很久很久以前,登入網站就跟去 ATM 領錢一樣麻煩,每次都要重新輸入帳號密碼,而事實其實就是如此。
如果今天你去當一個大樓保全,公司不給你一疊通行證,你要怎麼辨別每個曾經進入過的人?
於是你聯想到以前去夜店 Happy 的時候,穿得帥氣又漂亮,卻要在手上蓋一個醜醜的印章,你也曾經想過,去買一個一模一樣的小叮噹印章,蓋在自己手上矇混進去,因為 Buddyguard 記憶力不好,一定只認章不認人。
沒想到隔天再去的時候,你發現蓋的印章從小叮噹變成了凱蒂貓,你暗自慶幸自己沒有搞鬼,不然肯定免不了挨一頓揍。
其實 Session 和 Cookie 大概就是這樣,Session 是整個夜店的驗證機制,而 Cookie 就是那顆印章,當你付錢給公關,你的手被蓋了章,所以當你再次進入時,Buddyguard 看到印章就會放你進去。
如果真的好巧不巧,被你弄到了一顆一模一樣的印章,也只限當天進入,因為隔天印章可能又換了。只是 Session 機制還能做得更多,比如說夜店有一個配對系統,當你進入夜店後,夜店的資料庫 App 會可能會登錄你家坪數多大,開什麼車,有多少錢,是什麼工作等等,而留言板作業也是利用這一點,來登錄會員的資料。
為了加深記憶,自己也在這一階段的總複習重新寫下了三階段解釋 Session 演進過程與 Cookie 的關係,對我而言,這也是我很感興趣的一部份。記得當時還蠢蠢地問老師 Medium 的草稿暫存功能是不是將草稿存在 Cookie,其實 Cookie 能存放的資料並不算多,約為 4 KB 左右,而要做出類似的功能,可以有很多方法實現。

除此之外,既然談到後端,就少不了資料庫與資料庫架構,以及伴隨而來的安全漏洞。上述的例子中我們可以嘗試偽造印章,你也可以想像 Cookie 是可以由客戶端自行編改的,而我們的做法就是複雜化驗證機制,將 Hacker 命中 Token 的機率降低再降低,所以我們也另外學習了何謂加密與雜湊。
另外 SQL Injection 也是基於很酷的原理。由於我們輸入的內容,如帳號密碼或者留言,可能會與後端預設的字串拼合之後送進資料庫,這時候如果輸入一些特定 (或惡意的) 的字元或字串,與後端預設的字串拼合後,讓資料庫通過驗證,可能會返回一些開發者預想外的機密資料。
比如說在輸入帳號密碼的時候輸入一些字串,使後端送進資料庫的 SQL 語法成為一個必定為 true 的條件式,當資料庫認為條件符合,會直接返回資料,進而成為漏洞。
安全漏洞當然不只上述舉例的這兩種,但藉由一些理論與實作,使我們清楚明瞭到開發人員的宗旨:不要相信任何來自 Client 的資料,以及當我們實作一個網站時,僅僅一個漏洞就可能讓網站全面淪陷,實在不可不慎。
在這一階段的尾聲,是部署自己的 SSR 留言板專案到自己架設的伺服器,並購買一個網域。在大多數同學選擇 AWS 作為虛擬機的時候,我因為看到了某篇文章轉移公司資料從 AWS 至 GCP 的心得,而嘗試選擇使用 GCP 架設自己的第一個網站,並寫了使用 GCP 搭建虛擬機淺教學,期望可以幫助一些也想利用同樣選擇架站的同學們
事後證明最需要這份文件的是自己,但也算是為之前的所學做了一個總成,後來有收到某位同學的感謝信,覺得很感動,不過他使用 GCP 之後發現扣錢扣太兇讓他飽受驚嚇,所以跳槽到 AWS 了。
再談下一個開始之前,我突然想到了在這一個階段,讓我卡了蠻久的一個作業內容,那就是盡己所能把留言板網站中增加留言與刪除留言的部分,換成 Ajax,由於剛好在這一週穿插了 jQuery 教學,所以就自然以 jQuery 語法來實戰 (也穿插了 Bootstrap,但其實我沒有很多琢磨)。
因為剛學會 jQuery 不久,儘管套件的語法操作起來相當簡潔,但仍有些陌生,另外一點,我需要去實作很久沒使用過的 Ajax。
第一次實作 Ajax,是在上一階段的時候,因應作業需求,我曾經利用 Ajax 串接 Twitch API,而更在一週之後,為了更熟練 Vanilla JavaScript,又再寫了一份小小的 Side Project,除了原本作業預設的串接 LOL 資料之外,又多了查詢其他遊戲火熱實況的選擇,比如說 CS: GO 與 APEX 等等 (這邊本來想貼上網站,但 Twitch API 似乎做了一些改版,導致 Request 失效 )
回到正題,把原本留言板的發送留言與刪除留言換成 Ajax 的難點在於,原本在自己留言板中,商業邏輯和 HTML 都寫在同一份 PHP 裡了,什麼意思?就是因為在一份 .php 檔案中,PHP 語法與 HTML 語法是藉由 <?php 與 ?> 來分隔,說到這裡,不免要說一下我很喜歡某位老師的一個說法:
每個 php 檔案中都有兩個世界,被 <?php 包住的是裡世界,需要伺服器處理過後才能反映邏輯內容,反之,則是表世界,不論用檔案或者瀏覽器打開,都是一樣的。
在下一階段瞭解 MVC 的概念之後,才發現原來我在當時的留言板寫法是:把要呈現給使用者的視覺 (View) 與處理程式邏輯的部分 (Model) 寫在一起了,只要有一段時間沒看自己的 Code,重看時都需要花時間去理解自己到底在寫什麼東西。懂了 MVC 的大致概念之後,才知道訴求是要將這些東西分開並模組化,以至於更好維護 。

回憶起自己在寫什麼東西之後,我開始實作,漸漸把部落格中發送留言與刪除留言的部份換成了 Ajax,由於 JavaScript 會幫我處理前端的變化,所以上述的操作就不會換頁了。

另外既然前端會幫我處理頁面,那意味著我原本寫在 php 裡面的 View 用不到了,所以每當我完成一部分功能,就會刪掉原本程式碼中 php echo 的部分,網站的程式碼乾淨了不少。
這一階段大概就是這樣了,藉由瞭解後端概念,再用 PHP 寫出混合 View 與 Model 的程式碼,並且再將部分交換資料的方式改用 Ajax 以進行優化,把原本後端該渲染的部分改為前端處理。
這幾週下來的部落格作業我部署在這裡,由於是練習用網站,所以請勿輸入真實的帳號密碼唷。GitHub 連結
順帶一提,這一階段的學習紀錄都在我這輪的複習筆記內,筆記一開始從基本的資料庫語法,到如何將資料庫的使用藉由 PHP 呈現,並於每週心得中,不斷改善我們的 Session 機制,也帶到了資訊安全攻擊類型的部分。
三、JavaScript 底層與前端工具適配 ( SASS / Webpack / Gulp ),與網站架構 SPA / MVC / SSR / CSR 理解 ( Week 16 — Week 20 )
第三階段延續著第一階段的基礎,繼續前端的學習。這一階段的學習可以大致上分為三個部分:
第一個部分是探討 JavaScript 執行原理,包括 Hoisting、Closure 與 Prototype 等等。
第二部分則是學習現下前端常使用的一些工具套件,比如說 Webpack、Gulp、SASS 與其他 CSS 預處理器 (也有談到 PostCSS )。
最後則是學習網頁架構,除了了解概念之外,也一併了解架構的淵源。
在 Huli 的教學中有個很重要的概念,就是當你嘗試去理解一門技藝或工具時,也要去了解它的由來與歷史背景,藉由故事的方式,從「為什麼會這樣」來明瞭前因後果,除了幫助記憶之外,很大的一部份也是在告訴你在什麼情境之下,我會需要去使用這樣的技術。
這一階段我會根據以上三個部分個別介紹,除了大致上講解內容以外,也會談到一些在學習時獲得的反饋。
JavaScript 執行原理
這一部份可以說是整個實驗導師計畫中我最喜歡的一個部分,大致上不單單只是介紹 JavaScript 執行原理,還有從解析 ECMAScript 文件中的說明來探討成因。
比如說 Hoisting 是如何發生的?我們要先探討作用域 Scope,那 Scope 又是如何成型的?即使知道是藉由函式宣告來劃分,但其實有個更好的解釋是透過 Execution Context 。
在這一部份我們模擬電腦編譯時的操作,一步步模擬初始化、進入 EC 並執行,並於同一時間對照 ECMAScript 的說明,進而瞭解 Hoisting 並不只是單單地將宣告函式與變數提升到最上層,Closure 也不單單只是回傳一個函式等等。
這一部份對於初學者而言可能比較難理解,但事實上只要你曾經在 Google 中搜尋 JavaScript,又或者你跟我一樣,在開始課程之前就先去翻了 YDJS,一定可以看到類似的專有名詞,卻又不知該如何入門。
而學習到現在,我理解到,其實很多的概念在探究成因之後並不難理解,只是理解得深或淺而已,比如說 Hoisting 在解釋上,一開始我們先練習模擬 JavaScript 執行的步驟一一拆解,更熟練之後,光看程式碼就可以知道 Housting 如何發生,函式優先還是變數優先,由淺入深也是一個過程,其實一切都沒有自己想的那麼難。
另外,談到 JavaScript 的執行原理,就不能不談到 Event Loop,其實 Event Loop 就像是一個不斷指向 call stack 與 callback queues 的轉針,一旦偵測到 call stack 中沒有程序要執行,就會將 callback queues 的 function 丟入 call stack 繼續執行任務。
定義上看起來非常好理解,起初我也這麼認為,直到我在嘗試解釋經典 For Loop 題目時,雖然順利解開了題目,但卻因為一些小好奇而遇到了瓶頸,如果你有興趣,可以看一下我的提問與後續和老師的討論與回答,以下我簡單講解一下這個討論,以下面這題而言:
for(var i=0; i<5; i++) {
console.log('i: ' + i)
setTimeout(() => {
console.log(i)
}, i * 1000)
}因為 call stack 會持續執行 console.log("i:"+ i),直到這邊都執行完 ( 依序為 0、1、2、3、4 ) 之後,才會將 callback queues 的 console.log(i) 丟到 Stack 執行。
如果你已經學過了 JavaScript 執行原理,不難明白之後執行的 console.log(i) 會輸出 5,因為這時候的全域變數 i 已經是 5 了,我們丟入 Queue 的並非依序是 console.log(1)、console.log(2)、console.log(3)、console.log(4),而都是 console.log(i),既然此時的 i 經過 For Loop 洗禮之後已經是 5,那自然 console.log(i) 是印出 5。
而我的問題是,在 callback queues 的任務被傳至 call stack 時,此時的 call stack 裡應該已經沒有 global EC 了 ( 因為執行完畢都被清空了 ),那我的 () => { console.log(i) } 是如何拿到 globao EC 的 i ?
內層作用域之所以可以拿到外層作用域的變數,是因為 [[Scope]],而該 EC 中的 [[Scope]] 其實就是上一層的 VO,所以我可以理解 () => { console.log(i) } 這個函式存有 global EC.VO,因為這樣才拿得到 i 值,但我們也知道 [[Scope]] 是函式宣告時才會初始化,那 () => { console.log(i) } 是何時宣告並進而產生 [[Scope]] 的呢?
這邊當然要感謝 Huli 為了我自己的疑問而參與討論與解惑,我才明白了原來關於函式還可細分為 FunctionDeclaration 與 FunctionExpression,前者是一般的函式宣告,僅限於 function() {…} 的形式,後者則是將函式作為一個參數或者要放入變數的值,若該函式又剛好是匿名函式,則會將當下 EC 的 VO 放入該函式的 [[Scope]] 當中
而 () => { console.log(i) } 正是 FunctionExpression,並且是匿名的,也就是當 setTimeout 執行時,global EC.VO 被放入了 () => { console.log(i) } 的 [[Scope]] 之中,所以 console.log(i) 的 [[Scope]] 才存取得到 i。
更詳細的部分都在上面的提問與討論了,這邊初學者可能還看不太懂,不過我也期望你在學習程式的時候,抱著多疑的心態大膽提問,並仔細求證,想必會有更多的收穫 (因為自己問的問題並不算多,現在想想其實應該多把握機會問問題的)。
在這一部分學習到的概念還有很多,都收錄在專屬 Week17 的筆記裡了,一直到現在有疑惑都還會回來複習學過的觀念,也證明自己一直是自己筆記的最大受益人。
各種前端工具包與套件
這一部份就是介紹耳熟能詳的 SASS 等 CSS 預處理器,再來介紹 Webpack、Gulp 來更快速並自動化的運行並打包我們開發前端時所需要的套件。
這別要特別講一下,其實有時候為了完成目的,我們很少會想要去理解工具各自代表的意義與差別,但對於這些工具,我其實沒有太多的感想好講,包括這一部份的筆記也是做得挺亂七八糟的,這邊我倒是比較推薦實作,搭配越多的實作使用越好。
工具就是要用,不用就會忘記怎麼用。不管是上述這些套件,甚至是程式語言本身,上了職場之後,許多工具不會有太多時間讓我們去理解原理,不妨先看看說明書如何實作,再去研究它描述檔的每一個欄位代表的意思,如果你很想快速進步,再去看看原始碼也是不錯的選擇。
但自從經歷了這個階段,我後續的開發都離不開這些工具了,但宗旨是:當你為了用而去學,那麼學習的過程就會快樂並踏實許多。
網頁架構
可以說是這個階段相當重要的一個部分,這邊回顧一下在第二階段,我們將 PHP 留言板要張貼留言與刪除留言的部分改成了 Ajax,這樣的做法實現了操作不換頁,使用者體驗也更好了,因為利用前端來改變 View,所以與此相關並由後端渲染的 View 就派不上用場而刪除了,後端程式碼也乾淨多了。
這其實就是 Server Side Render 與 Client Side Render 的差別,前者的操作會讓網頁換頁,而後者則會即時反映我們的操作。但 CSR 也是有缺點的,因為 DOM 是我們利用 JavaScript 產生並插入的,所以實際上在網站的原始碼中你看不見他們,這樣的網站 SEO 就不盡人意,而我們知道 SEO 做得好不好,也是一個網站相當重要的部分 ( 儘管現在 Google 爬蟲越來越智慧化,可以開始爬到一些使用 CSR 方法開發的網站 )。
利用 CSR,我們可以開始把與後端做資料交流的部分都改為 Ajax,並用 JavaScript 操縱 DOM 元素,越將這樣的做法擴及整個網頁,就越不用與其它頁面交流而換頁,因為不用換頁,而是用前端來改變視覺,到最後,幾乎所有的功能都可以在單頁不換頁的情況下完成操作,也可以說是在首頁完成。
這樣的做法就是單頁式應用 (Single-Page Application),等同於一個 App 的概念,藉由 CSR 的強大,我們幾乎可以讓整個網站都不換頁了,而這樣的好處在於,假設你正在看一個直播主實況,但你又想看直播網站的其他資訊,藉由 SPA 的效果,可以讓你的實況縮小在網頁的一旁,讓你繼續瀏覽網頁其他部分,因為不會換頁,當下的實況視窗也不會消失啦。
但這樣又產生了一個問題,在一般的 SSR 網頁,其路由的表達方式就和我們一般瀏覽檔案一樣,比如說 /index/admin/setPage/ 可能就是我的管理員設定後台,但若改成 SPA,因為不換頁,頁面都是在 /index,那我其他頁面的路由要表示?
你可能會想:又沒有關係,我也沒在用 URL,我都是從網頁點擊按鈕來瀏覽至各個頁面的。但重新整理怎麼辦呢?比如說雖然我在 /index,但實際上我已經旅行到下三個頁面了,這時候重新整理,瀏覽器發 Request 給 /index,我又被送回到了首頁,這樣超級破壞體驗。
對於這一點,後續當然也有很多的解決方案,而也因為 SPA 的概念逐漸普及,前端需要負責的部分越來越多,原本由後端回傳的 View,突然間都要前端來負責了,但這也意味著前端能發揮的地方又更多了,學到這裡的我只能告訴自己:嗯,能力越強,責任越大。
這一階段我寫了最後一次的複習週筆記,其中也包含了用自己的方法對 Prototype 做解釋,以補足學習 JavaScript 執行原理時較為生疏的部分,這一部分也獲得了 Huli 的讚許,感謝。

這一部份結束後,接下來終於要開始學習前端框架了,這時候的課程,已經來到了 Week20 的尾聲。
四、前端框架學習 React / React-Router / Redux ( Week 21 — Week 24 )
這一階段為期四週,分別為 React 初探、利用 React-router 解決前述路由問題、使用 Redux 的狀態共享機制,以及最後一週的如何在 Redux 解決非同步問題 ( 使用 Middleware )
先談談我們為什麼需要 React:
前述有講過我們利用 CSR 機制,當我們重新跟後端要資料時,就插入新的 DOM,比如說新增一則留言,我發送資料到後端資料庫,前端的部分也插入一個新元素,所以這時我們的資料與 DOM 應該是一致的。
但假設你要刪除一個 DOM 呢?
應該就是直接把這個 DOM 移除掉了,但這時候資料還是在。又或者你想讓兩者同步,寫一些函式在刪除 DOM 的時候,同時也刪除該筆資料,兩者不論新增或者編輯,都各自增加 / 刪除,感覺也比較保險。
其實在 Week18 的時候有指定這麼一項作業,就是把我上述講的這些用一種更聰明的方法來實現,也就是我不要像上述那樣同時進行資料與 DOM 的操作,這樣很累,而且可能出錯。我們用一種更直覺的方式來做,也就是只要關係到我要渲染的頁面的部分,我通通放入資料的集合當中,這個資料可能是陣列,也可能是物件等等,每當我有 CRUD 的需求時,我都直接修改資料就好。
那視覺怎麼辦呢?很簡單,只要我規定每次的操作,頁面都直接依照資料的狀況直接渲染就好了。比如說我有一個陣列:[1, 2, 3, 4, 5],頁面也顯示 1、2、3、4、5,當在這個陣列 push 一個 6,資料改變了,頁面直接重新 Load 資料並重新渲染成 1、2、3、4、5、6 就好了,讓視覺永遠 follow 資料,這樣我要對資料做什麼操作都可以。
但這樣會有一個問題,那就是既然什麼操作都要重新渲染,不就很耗效能?如果今天我有一萬筆資料,不就更動一筆就要再渲染另外九千九百九十九筆?而這九千九百九十九筆在更動前後都一模一樣。
有一個套件可以幫我們解決這個問題,並採用上述的方式,那就是 React.js ,React.js 可以幫我們解決這個問題,利用這個套件規定的編寫方式,我的頁面上每一個部分,比如說頁首、輸入框、文章 — 甚至僅僅是單個 DOM,都可以是一個個 Component (組件),在 Component 之中,我們可以自定義 state,就是上述資料的概念。
一旦經由使用者操作,state (資料) 發生改變 (setState),Component 中的 View 就會自動渲染 (Update),並且這整個過程可以拆解,中間會產生出一個 Virtual DOM,與原本的結構做比對,若有前後不同的部分,就僅僅重新 Render 不同的部分就好,不用做到整批渲染,大大節省效能。
以上就是 React 大致的概念了,而這週的作業也有三個,是我個人認為導師實驗計畫開始以來最難的一次作業:分別是寫出一個 Todolist、五子棋遊戲與簡易部落格。
這邊要特別談一下五子棋遊戲,這是繼之前計算機之後跟演算法相關的題型,黑子與白子誰手用 state 判斷沒有問題,比如說 isBlack 為 true 或 false 輪流切換,難點在於勝者判斷。
關於勝負判斷個人解法的部分我記錄在當週作業的 README.MD,雖然不是一個完美的解法,但因為是自己思考後嘗試做出來的,感到特別的開心,解法的部分讓我思考了整整一天半的時間 (有一些時間是跑去看 JOJO 啦),因為已經很久沒有這種破關的成就感了。

老師在另一位同學的作業批改中提供了自己的解法,事實上關於實驗導師計畫課程的檢討不會很死板,所以很容易就會產生大大小小的討論串,包括連前述關於 For Loop 經典題型的討論也是一例,個人很喜歡這樣的知識交流與討論,這一切都得歸功於同學們的勇於發問與 Huli 的教學熱忱。
之後課程進行到了 React-router,一樣由解決問題的需求去發掘我們需要的工具,那要解決什麼問題呢?因為由 React.js 套件開發的網頁可以說是基於 SPA 為導向,而前述有提到 SPA 的網頁有路由上的問題。在製作部落格的作業中,我們賦予不同的頁面給不同的 Component,而 React-router 可以讓我們自定義路由,並且監聽當路由為何時,去渲染出我們要的 Component。比如說我的路由是 /blog,就會 render 出首頁,路由若是 /blog/posts,就會 render 出文章列表等等。
這並不是什麼魔術,利用 HTML5 的 history API,我們也可以改變現下的路由,而 React-router 另外就這個基礎賦予了監聽能力,當偵測到 URL 改變時,就會去比對我們指定的 Component,若符合就渲染。
再進入下一週就輪到 Rudex 出場了,在原本的部落格作業中,我們已經做到了讓 Component 有自己的 state,同時父 Component 也可以將 state 透過 props 傳遞給底下的子 Component 做使用,除此之外,有了 React-router 的加入,我們也大致解決的 SPA 的路由問題 (當然這裡不包括部署之後的,那又是另外一些設定),感覺很美好,是嗎?
這段開始,Component 我都先用組件代稱。父組件可以傳遞 state 給子組件看起來沒有問題,而且這似乎是一個定律。有一個問題在部落格這樣一個不大的專案中似乎感覺不出來,那就是如果我有其他的組件也想取用某組件的 State,但這兩個組件並不是父子關係,那怎麼辦?
又如果這樣的案例,不只一例呢?
雖然我們知道組件之間即使不是父子,一層一層傳遞還是有可能傳得過去,但這樣子的作法,可能會造成你必須將要被取用的 state 提升層級,也可能會伴隨著巨量的重構與改寫,因為組件們構成的脈絡有很明顯的階層化,但我們有一個概念可以將其視為扁平,那就是 Redux。
Redux 的概念就像是懸浮在這整個階層上的一朵雲 (store),這朵雲裡放的 state 就像天降甘霖一般可供所有組件取用,不論你的組件在哪裡,在哪一層都一樣,只要你有需求,你可以選擇訂閱它,訂閱者可以選擇讀取或寫入 (也就是 dispatch(action)) 雲裡的資料,在概念上,就是這麼簡單。
有了 Redux 之後,感覺組件裡面也都不用放 state 了,通通都丟給天邊一朵雲就好了啊!
事實上你可以這樣選擇,但前面有提過,套件與工具要視需求選用,如果我整個專案都不需要一個傳遞超過兩層的 state,其實我可以選擇不用 Redux,所以問題似乎又回到原點了:開發者知道自己的需求嗎?
有時候,知道何時可以不用,比知道何時要用,似乎更能體現一位開發者對該技術的瞭解程度。我現在也很努力在往這個目標邁進,儘管還有很長的一段路要走就是了。
整理一下,有了 React、React-router 與 Redux 等套件,基本上就已經成為一個基於 React 的生態圈了,這也是為什麼我們習慣稱呼 React 是一個框架的原因。
到了 Week24 最後一週,我們使用了 middleware,在簡單介紹它之前,要先講講我們是如何在 Redux 架構下改變儲存在 store 中的資料。在 Redux 更改 state 與 React 預設的 setState 不同,我們將每種更改類型定義為各個 actions ,再 dispatch 給 reducer ,reducer 會根據這是哪一種 action,於 store 做對應的修改。而這樣的修改一直都是的直腸子,吃進什麼就拉出什麼,但如果我們想在中間做非同步操作,比如說 Ajax,要怎麼做?middleware 可以幫我們做到,它的作用時程在 dispatch => reducer 之間。
在上一週沒有使用 middleware 的時候,我將這些非同步的操作寫在 Component 裡了,因為以當時的架構來講, reducer 只有檢查 action 的功能並相對應地更新 state,而 action 本身其實就是個物件而已。
但這樣其實很不好,為什麼?事實上,一個良好的開發習慣是:在單個 Component 中,我們應盡量放入單純要渲染的部分以及相關的操作就好,由於非同步操作中可能包含一些商業邏輯的部分,如果把這些商業邏輯寫在 Component 之中,很有可能會重蹈我們過去的錯誤,也就是將不同目的的程式碼寫在一塊兒,就因為它放在這邊可以 work,而不去思考以後的可維護性,這樣是不對的。
有了 middleware 的概念,我們可以在 dispatch => reducer 的過程之中,做很多我們想要的操作,比如說一些邏輯操作就可以放在 middleware 中去做處理,處理完之後再交給 reducer。
最後這個部落格,就是經歷四週一邊學習一邊實作的成品,複雜度不算高,但它的確為這半年來的學習立下了一座標誌性的里程碑。GitHub

補充一下,其實一開始僅僅在專案中加入 middleware 並自認最後一週作業 OK 之後,Huli 並不這樣認為,因為我在許多程式碼的部分沒有簡化,一些非同步的操作也沒有放入 middleware 裡面,更重要的是,因為對 React 生命週期的不熟悉,進而導致在增刪文章後沒有正確返回列表頁面。
幸好,事後發現錯誤之後,儘管沮喪,還是及時編修了作業,老師也 merge 了 PR (雖然老師人很好每次都會 merge)。也讓我警惕到,儘管是最後一週的課程,仍要拿出第一週的心態來面對,努力或是怠惰,結果最後都會回到自己身上。
長達二十四週的學習時間,相較於一個人的職涯,可能僅僅只是踏出半步。要如何保持對知識的敬重與態度,一直到今天,我還在思考答案。
其他的部分
上述一路寫下來,其實還有還有一些部分沒有講到,比如說 Promise 的概念、this 是什麼?與 JavaScript 執行原理中的 Prototype,還是 React 的生命週期等等…
而課程還有後端框架的部分,不論是 PHP 框架 Laravel,或者是對熟悉 JavaScript 開發者而言較為友善的 Express,目前都還沒開始學習。
但仔細回想,這半年內,真的學了超級多東西,如果不是一直以來學習的內容大部分都有筆記實作,現在的我光靠記憶力絕對無法好好講解每一項所學的概念與意義。
那現在實力不錯了嗎?我覺得也沒有,課程結束之後記憶力開始衰退,很多東西都需要複習,把筆記重看一遍,或者繼續針對記憶力不足的部分筆記起來,比如說一些面試題,能不能從解題去把面試題背後的概念做一個介紹,如同最開始的跟你朋友介紹 Git 一樣,畢竟整理過後的東西才是自己的。
呼應開頭所講的,寫筆記是我自己的方法,不是每個想成為工程師的人都得這麼做,但如果你選擇想邊學邊作筆記,那當然好,只要這是你想要的,然後做起來有成就感與滿足,why not ?
後記
這半年來,由於邊工作邊學習,也不是沒有代價,因為平均每日睡眠不到六小時,假日也因為習慣短暫睡眠,通常不到七小時就醒了。
但還是很感謝我現在上班的公司,願意讓我有空檔時間偷偷寫 Code,也感謝 Huli,容許我帶職參與第三期計畫。很快地,我也將前往下一站,不論是工作,學習,還是下一個人際場域。
這依然不會是關於程式導師計畫的最後一篇文章,下一篇,應該就是關於課程結束之後,我的生活型態與學習方法遇到什麼樣的問題,以及我是如何適應並轉變的。
最後,如果我文章中仍有一些觀念上需要被糾正的錯誤,請不吝告訴我 ! 十分感謝 !
