淺談:我的前端學習之路

ClayGao
ClayGao
Nov 5 · 35 min read

繼上一篇談到課程結束,這篇來談談這半年來的所學,用比較白話且引導式的方法來寫,另外穿插一些給學習者的小建議,也幫助我複習之前所學的一些概念。

學到現在,雖然不算優秀,但個人認為尚且合格,若我能做一件事情可以展現自己所知道的知識,又可以幫助一些對網頁程式設計有興趣的人,那我想,大概就是此時此刻寫這篇文章了。

這篇文章我會分成幾個段落,在每一個段落談談學習的內容,並不時穿插之前實作的筆記,所有筆記加上這篇文章,總字數應該有破十萬了,當然有參雜一些程式碼,不知道算不算多,不過從以前到現在真的很少打這麼多字。

如果你是完全沒有碰過程式的人,可以姑且看看一路的學習下來,有多少內容。如果你是稍微接觸過程式的人或者是高手,若我的文章敘述有錯誤或不清楚的地方,也請反饋給我,謝謝你們。


以構成網頁前端的三元素而言,我們都同意 Html 與 CSS 都很重要,但是我認為以程式設計面向來說,JavaScript 會是個重要的主戰場,它可能是你第一個接觸的程式語言,也可能也是最後一個,所以我也建議可以先從 JavaScript 下手。

圖片來自 https://kknews.cc/tech/68zlgav.html

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 看似無線傳輸,其實底層再底層依舊是訊號塔與海底電纜。而每一個網頁瀏覽路徑的一來一往,原來不過也就只是一個要求,與一個回應罷了。

其他的 Status code ?

上述的知識,其實不用上課也可以學會,只要你願意對自己生活周遭的事物多些留意,並探究其因。雖然不算是很深的知識,卻讓我訝異於原來這些道理早就充斥在自己的生活之中,而我卻不自知。

明白了這些原理之後,我開始藉由 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 給我的啟發,也如同某位網紅曾說過的:

講得出專業術語的,不一定是專業,能用白話解釋給其他領域的人聽,才是專業的表現。

為了踏出邁向好的工程師的第一步,我們不一定非得要等到買了黃色小鴨之後才能侃侃而談。我試著用我自己的話語解釋一些我所瞭解的事情,我期待對方能聽懂,更期待能被指正。


開始解決一些學習上的疑問之後,開始學程式已經來到了第七週,我做出了一個自己還算滿意的作品,是一台可以連續運算的計算機

利用事件代理的原理去監聽每個按鍵,是說,還是有一點 bug

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

一個被排版摧毀的最佳範例

在漫長的旅途中,我們當然很難讓自己一次又一次因為達到目標而感到滿足,但低潮時請去回想,我們當初為何選擇了現下想走的路。

除了上述的實作之外,對於初探 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 所產生的問題時,再怎麼鑽研用語都搔不到癢處。

咦,原來 python 也是物件導向語言嗎 QQ

在這四週,雖然還沒正式學習何謂 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 的大致概念之後,才知道訴求是要將這些東西分開並模組化,以至於更好維護 。

改寫之前,View 和 Model 寫在一起相當雜亂

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

Week13 的作業,網誌背景圖片來自 Unsplach

另外既然前端會幫我處理頁面,那意味著我原本寫在 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 執行原理,還有從解析 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 題目時,雖然順利解開了題目,但卻因為一些小好奇而遇到了瓶頸,如果你有興趣,可以看一下我的提問與後續和老師的討論與回答,以下我簡單講解一下這個討論,以下面這題而言:

因為 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 啦),因為已經很久沒有這種破關的成就感了。

圖是自己的解法,看完 Huli 的解法之後覺得自己的思路仍有進步空間

老師在另一位同學的作業批改中提供了自己的解法,事實上關於實驗導師計畫課程的檢討不會很死板,所以很容易就會產生大大小小的討論串,包括連前述關於 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

背景來自 Unsplash,很喜歡自己弄的配色,網誌右上角也有貼心的當日天氣

補充一下,其實一開始僅僅在專案中加入 middleware 並自認最後一週作業 OK 之後,Huli 並不這樣認為,因為我在許多程式碼的部分沒有簡化,一些非同步的操作也沒有放入 middleware 裡面,更重要的是,因為對 React 生命週期的不熟悉,進而導致在增刪文章後沒有正確返回列表頁面。

幸好,事後發現錯誤之後,儘管沮喪,還是及時編修了作業,老師也 merge 了 PR (雖然老師人很好每次都會 merge)。也讓我警惕到,儘管是最後一週的課程,仍要拿出第一週的心態來面對,努力或是怠惰,結果最後都會回到自己身上。

長達二十四週的學習時間,相較於一個人的職涯,可能僅僅只是踏出半步。要如何保持對知識的敬重與態度,一直到今天,我還在思考答案。


其他的部分

上述一路寫下來,其實還有還有一些部分沒有講到,比如說 Promise 的概念、this 是什麼?與 JavaScript 執行原理中的 Prototype,還是 React 的生命週期等等…

而課程還有後端框架的部分,不論是 PHP 框架 Laravel,或者是對熟悉 JavaScript 開發者而言較為友善的 Express,目前都還沒開始學習。

但仔細回想,這半年內,真的學了超級多東西,如果不是一直以來學習的內容大部分都有筆記實作,現在的我光靠記憶力絕對無法好好講解每一項所學的概念與意義。

那現在實力不錯了嗎?我覺得也沒有,課程結束之後記憶力開始衰退,很多東西都需要複習,把筆記重看一遍,或者繼續針對記憶力不足的部分筆記起來,比如說一些面試題,能不能從解題去把面試題背後的概念做一個介紹,如同最開始的跟你朋友介紹 Git 一樣,畢竟整理過後的東西才是自己的。

呼應開頭所講的,寫筆記是我自己的方法,不是每個想成為工程師的人都得這麼做,但如果你選擇想邊學邊作筆記,那當然好,只要這是你想要的,然後做起來有成就感與滿足,why not ?


後記

這半年來,由於邊工作邊學習,也不是沒有代價,因為平均每日睡眠不到六小時,假日也因為習慣短暫睡眠,通常不到七小時就醒了。

但還是很感謝我現在上班的公司,願意讓我有空檔時間偷偷寫 Code,也感謝 Huli,容許我帶職參與第三期計畫。很快地,我也將前往下一站,不論是工作,學習,還是下一個人際場域。

這依然不會是關於程式導師計畫的最後一篇文章,下一篇,應該就是關於課程結束之後,我的生活型態與學習方法遇到什麼樣的問題,以及我是如何適應並轉變的。

最後,如果我文章中仍有一些觀念上需要被糾正的錯誤,請不吝告訴我 ! 十分感謝 !

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade