這份文章向後端開發者或想要學習前端開發的初學者介紹前端開發的基礎。你得先了解一些程式語言的基礎。
JavaScript 的常見執行環境:
- 瀏覽器:瀏覽器原生支援 JavaScript 執行,JavaScript 在這裡可以呼叫瀏覽器的全域函數、變數,以及存取瀏覽器環境內的全域物件(如
window
、document
、localStorage
等)與瀏覽器互動。 - 後端環境:Node.js (or Deno)。我們在這裡進行開發,包括套件管理、打包與轉譯、模擬前端以及進行測試。
瀏覽器環境
解析
瀏覽器以 HTML
文件為入口,首先對文件進行解析,從上到下讀取,遇到標籤就處理。
瀏覽器以 script
標籤載入 js
腳本,並且執行載入的 js
腳本。但執行時機並不固定,主要包括以下幾種:
<!-- 會阻塞解析、下載完成後立即執行 -->
<script src="main.js"></script>
<!-- 解析完成後執行、保持順序 -->
<script src="main.js" defer></script>
<!-- 下載完成後立即執行、順序不保證 -->
<script src="main.js" async></script>
<!-- 模組腳本,等解析完才執行、支援 import/export -->
<script type="module" src="main.js"></script>
DOM
瀏覽器讀完並解析整份 HTML
文件後,就會建構文件物件模型(DOM, Document Object Model)。
DOM 是樹狀結構,表示整份 HTML
文件的結點(Node)以及相互關係。這個模型可以供瀏覽器對各結點進行操作,依此改變 HTML
文件的結構、樣式與內容。因應改變,瀏覽器會進行重繪(Repaint)或重排(Reflow):
- 重繪:當 DOM 的顏色、字體等不影響佈局的屬性改變時,瀏覽器只會重新繪製元素。
- 重排(回流):當 DOM 的結構、尺寸、字體等影響佈局的屬性改變時,瀏覽器會重新計算元素的位置和大小。
此外,瀏覽器會進行事件綁定,我們可以把事件綁定在 DOM 的節點的使用者操作或其生命週期上。依此來讓網頁具有互動性。
執行:上下文
JS 的執行環境分為全域上下文(Global Context)和函式上下文(Function Context)。
- 全域上下文:在載入
script
時建立,包括全域詞彙環境(包含函式與變數)、全域物件(如window
、document
、localStorage
等)。 - 函式上下文:在函式被呼叫時建立,有自己的詞彙環境。
在執行過程中,上下文會被推入執行堆疊(Execution Stack),並且在執行完畢後被彈出。
執行:主執行緒
JS 在主執行緒上維護一個呼叫棧,所有同步程式碼(函式呼叫、表達式評估、DOM 操作)都在此依序執行。耗時的迴圈或函式(如大量 DOM 重排、複雜計算)會阻塞後續程式與畫面更新,造成頁面卡頓。
執行:事件循環(Event Loop)
由兩個隊列組成:
- 宏任務隊列(Macro-task):
setTimeout
、setInterval
、DOM 事件、I/O 回呼 - 微任務隊列(Micro-task):
Promise.then
、MutationObserver
執行流程:
- 從宏任務隊列取出一個任務執行
- 該任務完成後,執行並清空所有微任務
- 重複執行下一個宏任務
事件循環全程依然在主執行緒執行,依舊會影響畫面更新,因此不是嚴格意義上的非同步執行緒。
執行:Web Worker
Web Worker 是瀏覽器提供的獨立執行緒,可以處理計算密集型任務,並且不會阻塞主執行緒。在瀏覽器內部建立獨立的執行緒,並且在執行緒間透過 postMessage
/ onmessage
與主執行緒交換資料。
延伸機制包括:
- Shared Worker:讓多個分頁/腳本共用同一個 Worker
- Service Worker:攔截與快取網路請求,常用於 PWA
- Worklet:為特定任務(如 Audio / CSS Painting)設計的輕量執行環境
後端環境
後端環境是我們一般的開發環境。JS 不需要編譯,但在開發上,我們會需要做打包(bundling)和轉譯(transpilation)等。一般流程為:
- 編寫原始碼
- 依賴圖解析
- 轉譯(並啟動開發伺服器,非真實環境)
- 打包與最佳化
- 輸出靜態資產
- 部署(部署到伺服器,真實環境)
編寫原始碼
一個現代化的前端專案,不會維護單一的 js
檔案,而是會在多個 js
(或 ts
、jsx
、tsx
、vue
等)檔案中相互引用。模組化的方式有兩種:
- CommonJS:Node.js 的模組系統,使用
require
和module.exports
來引入和匯出模組。 - ES Modules:現代瀏覽器和大部分打包器原生支援的模組系統,使用
import
和export
來引入和匯出模組。
除了內部的模組化,我們也會需要使用外部的套件,這些套件會以 package.json
來管理。這些套件以及它們的依賴套件會從遠端下載,通常被安裝在 node_modules
目錄下,並可以 import
(或 require
)來引入模組。
由於這個下載與安裝有版本相依問題,我們會需要使用套件管理工具來管理套件。
Node.js 的套件管理工具為 npm
,但現在最流行的套件管理工具是 pnpm
和 yarn
。
我們通常會希望能 .gitignore
上面那個目錄,因為那通常很大,因此會讓管理工具生成一個鎖定版本的 package-lock.json
、pnpm-lock.yaml
或 yarn.lock
文件加入 git 追蹤,來確保部署時的穩定性。
依賴圖解析與轉譯
在開發期間,打包工具(如 webpack
、vite
)會從入口檔案出發,根據檔案的相互引入解析專案的依賴,並且建立依賴圖(Dependency Graph)。
如果有使用 TypeScript
、JSX
或需要向下相容的支援,會先交給轉譯器,如 tsc
、babel
或 esbuild
轉譯成目標語言(通常是 ES5)。
我們此時可以在本機啟動開發伺服器,這讓我們可以在本地端進行開發,並且透過依賴圖,在開發過程中能進行熱更新(Hot Module Replacement)、懶加載等,來讓前端工程師擁有更好的開發體驗。
打包與最佳化
打包工具會根據依賴圖將所有模組打包成一個或多個 js
檔案,並且進行最佳化,包括:
- Tree Shaking:移除未使用的程式碼
- Code Splitting:將程式碼分割成多個小檔案,來減少載入時間
- Minification:壓縮程式碼,減少檔案大小
- Hashing:為每個檔案生成唯一的 hash,避免瀏覽器快取失效
- Asset Inlining:將小型資產(如圖片、字型)內嵌到
JS
檔案中,避免額外的 HTTP 請求 - Scope Hoisting:將所有模組的程式碼合併到一個函式中,減少函式呼叫的開銷
- CSS 最佳化:將 CSS 抽出,並進行壓縮、合併與 tree shaking
輸出靜態資產
打包工具會將打包後的資產(js
檔案、css
檔案、圖片、字型等)輸出到指定的目錄,通常是 dist
目錄。
部署
將靜態資產部署到 CDN 或 Web 伺服器上,讓瀏覽器能在真實環境載入、解析並執行最佳化的檔案。
一些現代框架(如 Next.js
、Nuxt.js
、SvelteKit
、Remix
、Qwik
等),不僅僅會部署靜態資產,還會部署(通常是無伺服器函式,可以在邊緣環境下運行)後端 API 端點以及可服務渲染的伺服器端,來增進效能並且提供更好的 SEO 支持。