前言
The only constant in the world is change.
世界上唯一不變的是變化。--《誰動了我的奶酪》作者 斯賓塞·約翰遜
IT 行業變化太快了,尤其是前端開發(Frontend Development)。如果能穿越回 10 年前,碰上一位 Web 開發軟件工程師,他一定會告訴你玩轉前端就是精通 jQuery 和搞定 IE 瀏覽器兼容性。不過隨着前端的不斷髮展,jQuery 遭遇 “官方逼死同人” 逐漸退出歷史舞臺(元素選擇和操作被標準的 DOM API 所統一);而飽爲詬病的 IE 瀏覽器兼容性問題,因爲 IE 市場的逐漸萎縮以及一些兼容性工具(Polyfill)的出現,讓其從之前的核心優化問題降級爲如今的瘙癢問題,不再成爲前端工程師的標配。
如今的前端開發,有着玲琅滿目的專業術語和紛繁複雜的技術生態,可能會讓初入前端開發的工程師感到惴惴不安:要學的東西實在是太多了。現在的前端工程師如果不瞭解 Webpack、Babel、Node.js、NPM/Yarn、ES6/7、React/Vue、Sass/Less、TypeScript、ESLint、Canvas/SVG 等現代化前端知識,就難以讓人信服自己的專業背景。2021 年的前端工程師可能是真正意義上的工程師(Engineer),他們通常需要運用大量的專業知識來解決工程化問題,包括如何將項目進行模塊化,如何設計組件間的交互,如何提高可複用性,如何提升打包效率,優化瀏覽器渲染性能,等等。他們不再像以前,只需要 HTML/CSS/JS 一個套路來開發靜態頁面。
本文將着重就現代前端開發的主題,來詳細介紹前端工程化的各個重要技術,幫助讀者瞭解現代前端頁面的複雜和多樣性是如何構造的。本文是一篇關於前端工程的科普文,即使你不瞭解前端技術,也可以從本文受益。
打包部署
要說現代前端開發跟 10 年前最大的不同點是什麼,我肯定會說是打包部署(Bundling & Deployment)。生活在前端 “石器時代”(jQuery 主流時期)的開發者肯定很難想象如今的前端項目需要編譯(Compile)。以前的大部分 Web UI 是通過 MVC 的方式來提供給用戶的,前端頁面大部分是通過後端模版渲染生成靜態資源(HTML/CSS/JS)不經任何加工提供給瀏覽器的。也就是說,當時前端的靜態資源相當於後端服務表現層(Presentation Layer)的一部分,複雜的交互通常都由後端來完成。因此前端靜態資源通常非常簡單,不需要經過複雜的編譯打包,最多就是壓縮(Minify)成更小的文件。
而現在的情況完全不同了,用戶對於交互性的需求逐漸增大,通常不願意花過多的時間來等待頁面響應。傳統的 MVC 模式受限於網絡傳輸,通常需要幾百毫秒甚至更多來完成一次頁面導航,而這是非常影響用戶體驗的。因此,前端項目逐漸分離出來,單獨作爲一個應用運行在瀏覽器中,交互在瀏覽器端進行,速度非常快。但是,複雜交互都集中在前端裏面,讓前端項目變得更復雜,更臃腫,也更難以管理。因此,龐大的項目源碼需要被壓縮成很小的靜態文件,儘可能少的佔用網絡資源傳輸給瀏覽器。現在稍微大一點的前端項目的依賴文件就會超過 500MB。相信你不會傻到將這麼多文件一股腦塞給用戶吧!
因此,你需要一個非常強大的打包和編譯工具。Webpack 是現在比較主流的打包工具,能夠配合編譯工具(例如 Babel)將各種類型的項目源代碼文件(例如 .vue、.ts、.tsx、.sass)打包成原生的 .js、.css、.html 等靜態文件,直接提供給瀏覽器運行。下圖是 Webpack 官方網站的流程示意圖。Webpack 通過檢查源代碼文件的各種依賴關係,分析編譯後生成對應的靜態資源文件。這就跟編譯 Java、C# 代碼編譯是一個道理。
Webpack 的配置由一個 JavaScript 文件來定義,文件名通常爲 webpack.config.js
。下面是一個最基礎的 Webpack 配置。
const path = require('path');
module.exports = {
entry: {
main: './src/main.js' // 入口文件
}
output: {
path: path.resolve(__dirname, 'dist'), // 打包後的輸出路徑
filename: 'bundle.js' // 打包後的輸出文件名
},
module: {
rules: [
{ test: /\.css$/, use: 'css-loader' }, // .css 文件轉換模塊
{ test: /\.ts$/, use: 'ts-loader' } // .ts 文件轉換模塊
]
}
};
這段配置代碼定義了打包的入口(Entry)、輸出(Output)以及編譯所用到的加載規則(Rules),這些都可以通過一個配置文件來完成。定義好配置文件 webpack.config.js
,在項目根目錄下執行 webpack
命令,就會默認加載該配置文件,然後讀取源代碼並打包爲編譯後的靜態文件。您可能會注意到,有 module.rules
這個數組結構的配置項,這裏可以定義各種文件類型的加載器(Loaders),也可以看作編譯器。在這個例子中,css-loader
是處理 .css 文件的加載器,它會將 @import
、url()
、import
、require
等關鍵詞轉換爲 原生 CSS/JS 中兼容的代碼。而爲了讓 css-loader
生效,您需要在項目根目錄下運行 npm install css-loader --save
來安裝該加載器。同理,處理 .ts 文件的 ts-loader
也需要通過 npm install ts-loader --save
來使其生效。其實,爲了讓 Webpack 能處理各種文件類型(例如 .vue、.tsx、.sass),通常都需要安裝對應的加載器,這其實給了 Webpack 非常強的可擴展性。通過安裝加載器的方式,讓不同類型的文件統一成原生前端代碼,這也使我們能夠非常有效的管理複雜而龐大的前端項目。
由於本文不是一個參考指南,因此關於 Webpack 的具體配置本文不打算詳細介紹。如果需要了解 Webpack 的更多內容,可以去 Webpack 官方文檔(https://webpack.js.org/concepts)深入研究。當然,前端打包工具並不止 Webpack 一種,還有其他一些比較受歡迎的工具,例如 Rollup.js、Gulp.js、Parcel.js、ESBuild 等等。感興趣的讀者可以深入研究一下。
Webpack 以及其他打包工具的驅動引擎是 Node.js,也就是說它們是作爲 Node.js 的一個依賴來運行的。其實,任何一個現代化的前端項目,都離不開 Node.js,還有其包管理系統 NPM。我們將在下一個小節介紹前端項目的模塊化,包括 NPM。
模塊化
模塊化(Moduarization)是任何技術工程項目中非常重要的概念。一個複雜的穩定運行的大型系統通常是由多個子模塊一起構成的,它們之間相互通信、共同協作來完成一些非常複雜的邏輯或任務。複雜性爲維護人員帶來的麻煩通常是非線性的,也就是說,在系統成長到一定規模的時候,要增加單位規模所需要花費的成本會成指數級增加。如果我們不採取一定措施限制維護成本,那這樣規模的項目幾乎是不可維護的。爲了讓開發者能夠有效管理大型項目,我們的做法通常是減少複雜性和可理解性,儘可能的減少系統複雜性增加的速度。而一種有效降低複雜性的方式,就是分而治之,對於軟件項目來說就是模塊化。模塊化讓各個負責相同或類似功能的子系統獨立運行(高內聚),並適當開放一些接口提供給其他子模塊(低耦合)。這樣整個大型系統由多個子模塊描述,而這些子模塊都有各自的特性和功能,讓維護人員能夠準確的定位到各功能對應的模塊,從而大大減少了維護成本。“高內聚,低耦合” 是模塊化的核心理念,其最終的目的,是降低系統複雜性和提高可維護性。模塊化對於日漸複雜而且動輒幾百兆代碼的前端項目來說,是非常非常重要的。
NPM
對於前端項目來說,NPM(Node Package Manager)不可或缺的前端項目構建工具。NPM 顧名思義是 Node.js 的包管理工具,類似於 Python 的 pip,Java 的 Maven,C# 的 Nuget,等等。對於一些已經存在的模塊或功能,我們不需要從頭開始編寫,因爲軟件工程界所倡導的 DRY 原則(Don't Repeat Yourself),否則我們會花大量的時間在重複造輪子上。NPM 就是前端項目中的依賴管理工具,它能夠幫助你安裝構建前端項目所需要的包和庫,然後再通過前文的 Wepback 等打包工具將源代碼編譯成原生前端靜態文件。
描述前端項目依賴的配置文件叫做 package.json
,類似於 Python 中的 setup.py
,Java 裏的 pom.xml
,可以定義項目的依賴包、運行入口、環境要求等等。package.json
可以手動生成,也可以用 npm init
的方式創建。下面是一個樣例 package.json
文件。
{
"name": "crawlab-frontend",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint",
"test": "jest"
},
"dependencies": {
"@fortawesome/fontawesome-svg-core": "^1.2.32",
"@fortawesome/free-brands-svg-icons": "^5.15.1",
"@fortawesome/free-regular-svg-icons": "^5.15.1",
"@fortawesome/free-solid-svg-icons": "^5.15.1",
"@fortawesome/vue-fontawesome": "^3.0.0-2",
"@popperjs/core": "^2.6.0",
"@types/codemirror": "^0.0.103",
"@types/md5": "^2.2.1",
"atom-material-icons": "^3.0.0",
"codemirror": "^5.59.1",
"core-js": "^3.6.5",
"element-plus": "^1.0.1-beta.7",
"font-awesome": "^4.7.0",
"md5": "^2.3.0",
"node-sass": "^5.0.0",
"normalize.css": "^8.0.1",
"vue": "^3.0.0",
"vue-i18n": "^9.0.0-beta.11",
"vue-router": "^4.0.0-0",
"vuex": "^4.0.0-0"
},
"devDependencies": {
"@babel/preset-typescript": "^7.12.7",
"@types/jest": "^26.0.19",
"@typescript-eslint/eslint-plugin": "^2.33.0",
"@typescript-eslint/parser": "^2.33.0",
"@vue/cli-plugin-babel": "~4.5.0",
"@vue/cli-plugin-eslint": "~4.5.0",
"@vue/cli-plugin-router": "~4.5.0",
"@vue/cli-plugin-typescript": "~4.5.0",
"@vue/cli-plugin-vuex": "~4.5.0",
"@vue/cli-service": "~4.5.0",
"@vue/compiler-sfc": "^3.0.0",
"@vue/eslint-config-typescript": "^5.0.2",
"eslint": "^6.7.2",
"eslint-plugin-vue": "^7.0.0-0",
"sass-loader": "^10.1.0",
"scss-loader": "^0.0.1",
"typescript": "~3.9.3"
}
}
其中,比較重要的是 dependencies
和 devDependencies
這兩個配置項,它們定義了該項目所依賴的包。它們是 Key-Value 的形式,Key 爲依賴的包名,Value 是對應的依賴 包的版本號或者版本號匹配表達式。當項目創建好後,執行 npm install
就可以自動的將依賴包從包託管網站 https://npmjs.com 上拉取下來。所有安裝好的模塊會出現在 node_modules
目錄下,每個包對應的目錄名稱以其包名命名。當我們打包編譯源代碼時,項目構建工具會自動分析源代碼的依賴,並從 node_modules
找到對應的模塊來進行編譯和構建。
scripts
配置項是項目開發、調試、構建、測試、部署的命令行別名(Alias),幫助您快速啓動項目。它也是 Key-Value 的形式,Key 爲別名,Value 爲具體對應的執行命令。例如,根據上面的配置,如果執行 npm run build
,相當於執行了 vue-cli-service build
,即通過 vue-cli-service
這個官方命令行工具來打包編譯前端項目(vue-cli-service
其實是針對 Vue 項目封裝的 Webpack 構建工具)。
除了前面提到的要點,NPM 還有其他很多配置功能。詳細信息可以參考官方文檔(https://docs.npmjs.com)。如果你想更深入瞭解前端包管理,推薦學習一下 Yarn,這是 Facebook 開源的包管理工具,跟 NPM 非常像,但有一些實用功能,例如依賴緩存。
項目結構
如果說上面說的 NPM 以及包管理系統是宏觀層面項目間的模塊化,那麼這個小節介紹的項目結構則是微觀層面項目內的模塊化。爲了保證前端項目代碼的可維護性,前端開發工程師通常需要掌握清晰而合理的代碼組織方式,包括如何對不同類型文件或組件進行分類和管理,以及如何組織項目目錄的層次和結構。下面是一個典型的 Vue3 的前端項目代碼結構。
.
├── LICENSE
├── README.md
├── babel.config.js // Babel 編譯器配置文件
├── jest.config.ts // Jest 測試工具配置文件
├── package.json // 項目定義文件
├── public // 公有靜態目錄
│ ├── favicon.ico
│ └── index.html
├── src // 源代碼根目錄
│ ├── assets // 靜態文件目錄,包括 .scss、.svg、.png 等
│ ├── components // 組件
│ ├── i18n // 國際化
│ ├── interfaces // 類型定義
│ ├── layouts // 佈局
│ ├── main.ts // 主入口文件
│ ├── router // 路由
│ ├── shims-vue.d.ts // Vue 的 TS 適配文件
│ ├── store // 狀態管理
│ ├── styles // 樣式
│ ├── test // 測試
│ ├── utils // 公共方法
│ └── views // 頁面
├── tsconfig.json // TS 配置
└── yarn.lock // yarn 依賴鎖定
可以看到,一些基礎配置文件都在根目錄下,src
目錄是源代碼的主要目錄,可以理解爲核心代碼都在 src
目錄下。而在 src
目錄下,有一些子目錄,這裏是前端項目的核心模塊,包括頁面(Views)、路由(Router)、組件(Components)、佈局(Layouts)、狀態管理(Store)等。當然,不是每個項目都是一樣的組織形式,可能或多或少會有一些不同,但頁面、組件、路由這些通常都是重要的模塊需要獨立出來。組件一般是可複用的,是頁面的組成部分;路由是實現前端導航匹配頁面的功能;頁面就是各種網頁的實際內容。其實,這種代碼的組織形式跟傳統的後端項目類似,也是將負責不同功能的高內聚的模塊獨立出來,各自形成同一類型的結構。
這樣做的好處在於,開發者可以通過對所要解決的問題進行分類,從而能夠精確的定位到相關的模塊,快速完成開發任務或解決疑難問題。這就跟整理家裏雜物或者文件歸檔一樣,如果經常收拾整理,會讓生活或者工作環境變得更井井有條。
當然,也不是說傳統的前端項目就沒有模塊化的概念,只是在 jQuery 時代,如何進行模塊化甚至需不需要模塊化都沒有一個統一標準。如果你在 10 年前做過前端項目,你應該對那些臭不可聞的 “屎山” 代碼深有體會。如今,隨着前端技術的不斷髮展,前端行業已經衍生出很多幫助前端工程師進行工程化的工具和方法論,除了本小節的模塊化以外,還有接下來將要介紹的前端框架,也就是現在熟知的 React、Vue、Angular 三大框架以及其他一些小衆的框架。
前端框架
jQuery(石器時代)
很多時候,一個新事物的誕生和走紅絕對不是偶然的。就像 2006 年橫空出世的 jQuery 一樣,它解決了當時用原生方法操作 DOM 的痛點。jQuery 通過簡潔的 API 和高效的性能,成爲當時前端界的標配。要說 jQuery 的成功,是建立在當時原生 JavaScript 的缺陷上:囉嗦而臃腫的語法,複雜而難以理解的設計模式,瀏覽器標準的不統一。這給當時的 Web 開發人員帶來了巨大的痛苦。而 jQuery 作爲 JavaScript 的一個輕量級的工具庫,很大程度上彌補了 JS 的不足,因此得到了大量開發者的歡迎,並且隨着互聯網產業的發展成爲了後來很多年的前端霸主。很多其他的知名第三方庫,例如 Bootstrap,也是建立在 jQuery 上的。
在越來越多的開發者開始擁抱 jQuery 時,不少人也開始抱怨它的不足。在用戶體驗變得越來越重要的趨勢下,部分開發者開始將原本一些後端的交互邏輯遷移到前端,以追求更快的響應速度和更流暢的用戶體驗。而爲了達到這個目的,不少開發者開始大量利用 jQuery 通過 Ajax 異步請求來實時渲染前端頁面,也就是用 JS 代碼來操作 DOM。而這一變化則帶來了大量 jQuery 用戶的抱怨:他們認爲用 jQuery 實現帶有複雜交互邏輯的 UI 是一件非常頭疼的事情。首先,jQuery 嚴重依賴於 DOM 元素的 CSS 或 XPath 選擇器,這爲項目模塊化帶來了非常大的麻煩:所有的元素對於一個前端應用來說幾乎是全局,這意味着 class
、id
等屬性對於項目穩定性來說變得異常敏感,因爲稍不注意你可能就誤改了不該修改的元素,污染了整個應用。其次,jQuery 實現前端交互的原理是直接操作 DOM,這對於簡單應用來說還好,但如果前端界面有大量需要渲染的數據或內容,或者要求頻繁的改變界面的樣式,這將產生非常大的性能問題,將導致瀏覽器卡頓,嚴重影響用戶體驗。第三點,jQuery 沒有一個編寫前端項目的統一模式,程序員們可以根據自己的喜好隨意發揮,這個也是造成前端應用 Bug 橫飛的原因之一。總的來說,大量使用 jQuery 來實現前端交互使前端項目變得非常脆弱(Fragile)。
AngularJS(鐵器時代)
2009 年,一位 Google 工程師開源了他的業餘項目 AngularJS,這個全新的框架給當時被 jQuery 統治的前端行業帶來了革命性的進步。當時這位工程師只用 1,500 行 AngularJS 代碼在 2 周內實現了 3 人 花了 6 個月開發的 17,000 行代碼的內部項目。由此可見這個框架的強大。AngularJS 的主要特點是 HTML 視圖(HTML View)與數據模型(Data Model)的雙向綁定(Two-way Binding),意味着前端界面會響應式的隨着數據而自動發生改變。這個特性對於習慣寫 jQuery 的前端工程師來說是既陌生又期待的,因爲這種編寫模式不再要求主動更新 DOM,從而將節省大量主動操作 DOM 的代碼和邏輯,這些 AngularJS 全部幫你自動完成了。下面是 AnguarJS 雙向綁定的示意圖。
AngularJS(1.0 版本的名稱,後來更名爲 Angular)能做到一部分組件化(Componentization),但支持得並不完整。利用 Directive 來組件化一些公共模塊會顯得力不從心。這給後來的兩大框架 React(由 Facebook 開發)以及 Vue(由前 Google 工程師尤雨溪開發)帶來了靈感和機遇。
React、Vue & Angular(三足鼎立)
2013 年,Facebook 在 JS ConfUS 上公開發布了 React 這個全新前端框架。React 獨樹一幟,創造了虛擬 DOM(Virtual DOM)的概念,巧妙的解決了前端渲染的性能問題;同時,React 對組件化支持得非常到位。另一方面,React 的數據-視圖單向綁定(One-way Binding)很好的解決了一致性問題,也非常好的支持 TypeScript,這能夠爲開發大型前端項目帶來穩定性和健壯性。不過也因此形成了不小的門檻。React 初學者通常需要理解大量的基礎概念,例如 JSX,才能順利的編寫 React 應用。
而 1 年以後,後起之秀 Vue 由前 Google 工程師尤雨溪公開發布。它另闢蹊徑,在吸收 React 與 AngularJS 優點的同時,也做了一些優秀的創新。Vue 把視圖(HTML)、數據模型(JS)和樣式(CSS)整合到一個 .vue 文件中,並沿用 AngularJS 的雙向綁定,這降低了前端開發的門檻。如果你寫過 AngularJS,你會非常容易上手 Vue。另外,Vue 在組件化的支持上也做得非常棒。最近發佈的 Vue3 也加入了 TypeScript 的支持,讓其能夠充分支持大型前端項目。想進一步瞭解 Vue3 的讀者可以關注 "碼之道" 公衆號(ID: codao),查看歷史文章《TS 加持的 Vue 3,如何幫你輕鬆構建企業級前端應用》,裏面有關於 Vue3 新特性以及如何利用它構建大型項目的非常詳細的介紹。
自 2.0 版本開始,AngularJS 更名爲 Angular,優化了之前的部分不足,而且全面擁抱了 TypeScript。Angular 與 AngularJS 從語法上來看幾乎是兩個完全不同的框架。Angular 強制要求使用 TS 開發,這使其變得非常適合構建大型前端項目。不過,這也讓初學者需要同時學習 TS,以及 Angular 中本身非常複雜的概念,使得其學習曲線非常陡峭。
以下是對目前這三大前端框架的總結。
屬性 | React | Vue | Angular |
---|---|---|---|
創始者 | 尤雨溪(前 Google 工程師) | Misko Hevery(Google) | |
首次發佈時間 | 2013(距今 8 年) | 2014(距今 7 年) | 2009(距今 12 年) |
國內使用量 | 高 | 高 | 低 |
國外使用量 | 高 | 低 | 中 |
上手難度 | 高 | 低 | 非常高 |
適用項目規模 | 大中型 | 大中小型 | 大型 |
生態豐富度 | 高 | 中 | 低 |
數據綁定 | 單向 | 雙向 | 雙向 |
TypeScript | 支持 | 支持 | 強制要求 |
文件大小 | 43KB | 23KB | 143KB |
Github Stars | 145k | 158k | 58k |
優點 | 快速,穩定,生態好 | 簡單,靈活性高,易學習,文檔詳細 | 複用性強,強制 TS,適合企業級項目 |
缺點 | 學習成本高,文檔更新慢 | 穩定性略差,社區不大 | 複雜,非常高的學習曲線,文檔不詳細 |
未來展望
React、Vue、Angular 如今在前端領域裏已成爲前端工程師必須瞭解、掌握甚至精通的前端框架。不過,隨着移動端的崛起,以及新技術的出現,可以預見到這種 “三足鼎立” 的趨勢將在未來幾年發生巨大的變化。WebAssembly 的出現給前端傳統的 HTML/CSS/JS 技術帶來了挑戰;混合開發(Hybrid Development)、小程序(Mini-Program)、PWA 等技術讓前端工程師能夠涉足移動端開發;新興的前端框架,例如 Svelte、Elm.js、ReasonML 等,隨着知名度的不斷增加,都可能會對現有的技術產生影響。總之,正如本文開頭所說的,“The only constant in the world is change”。
組件化
對於現代化前端工程來說,組件化是一個核心概念。組件化實際上是前端 UI 界面的模塊化。對於複雜的前端應用來說,很多組件,例如按鈕、導航、輸入框等,都是可以複用的 UI 模塊,可以用在各種各樣的頁面中。另外,組件之內還可以套用子組件,子組件中還可以套用子子組件,這使得前端界面的組織變得非常靈活。如果你設計妥當,當面對老闆或產品經理的需求變更時,例如佈局調整、樣式變更、功能擴展等,你都可以從容不迫的應對。組件化因爲太重要了,每個前端工程師都需要掌握其中的設計原理,不管你使用哪種前端框架。
如果目標是實現一個大型的擴展性強的前端項目,前端工程師除了需要提前組織好代碼結構(佈局、組件、路由)等,還需要考慮設計一些基礎組件,簡化後續的開發與維護工作。其中,最基礎的組件應該算是佈局組件(Layout Components)了,你可能需要設計頂部(Header)、側邊欄(Sidebar)、主容器(Main Container)以及底部(Footer)等等。當然,一些常用的功能組件也需要考慮,例如輸入框(Input)、表單(Form)、彈出框(Modal)等等。建議不要重複造輪子,你可以根據自己的框架選擇合適的 UI 框架,例如 React 的 Ant Design,Vue 的 ElementUI,只有比較特殊的組件需要自己來實現。
另外,前端工程師也需要非常熟悉如何設計組件間的通信。一般的父子組件間通信可以通過屬性傳遞(Property Propagation)以及事件發射(Event Emission)來實現。對於多層間的通信,可以考慮用 Context 或 Event Bus 來處理。兄弟組件間的通信通常是用**狀態管理(Store)**來實現。
當然,組件化的設計模式並不是千篇一律,前端工程師們一般都從實踐和借鑑中積累經驗,形成自己的一套知識體系。對於初學者來說,可以看看前人們發佈的開源框架,例如 Ant Design 或 ElementUI,裏面的源碼以及實現過程是公開的,研究裏面的源代碼可以幫助你有效掌握已經經過項目實踐考驗的設計理念。
類型約束
JavaScript 在前端開發中扮演着非常重要的角色,是交互邏輯的核心。然而,JavaScript 是一個弱類型動態語言,意味着各種基礎類型之間可以互相轉化,因此用 JS 編寫的程序的穩定性和健壯性比較堪憂。這對於構建大型前端應用來說是個致命的缺點。幸運的是,微軟於 2012 年發佈了 TypeScript,它可以利用靜態代碼檢測機制將 JavaScript 用強類型約束起來。TypeScript 是 JavaScript 的超集,也就是說,TS 包含 JS 的全部語法和特性,並且在此基礎上加入了類(Class)、接口(Interface)、裝飾器(Decorators)、命名空間(Namespace)等面向對象編程(OOP)的概念。
對 TypeScript 不熟悉的讀者可以參考碼之道公衆號之前的文章《爲什麼說 TypeScript 是開發大型前端項目的必備語言》,可以關注 "碼之道" 公衆號(ID: codao)查看歷史文章。
正如 TypeScript 官網上所定義的,TypeScript 是 "Typed JavaScript at Any Scale"(任何規模的類型約束 JavaScript)。TypeScript 的主要特點就是類型系統,它通過加入類型來擴展 JavaScript。如果你瞭解靜態類型(Static Typing)和動態類型(Dynamic Typing)的區別,你應該會清楚靜態類型的優勢:可預測性,健壯性,穩定性。通過編譯,TS 代碼可以轉化爲原生的 JS 代碼,因此在瀏覽器兼容性上,TS 代碼沒有任何問題。
下面兩段段代碼可以清楚的理解分別用 純 JS 和 TS 編寫前端應用的區別。首先看純 JS(沒有 TS)的樣例代碼。
// JS Example
// 沒有任何類型定義
// 數據類型約束全靠文檔或記憶
// 合法用戶實例
const validUser = {
name: 'Zhang San',
sex: 'male',
age: 40,
};
// 非法用戶實例,編譯時不會報錯
const invalidUser: User = {
sex: 'unknown',
age: '10',
};
console.log(invalidUser.name); // undefined
console.log(invalidUser.name.substr(0, 10)); // Uncaught TypeError: Cannot read property 'substr' of undefined
可以看到,整段代碼不會報語法錯誤(Syntax Error),因此可以順利在 JS 引擎中運行。運行到 console.log(invalidUser.name)
時會打印 undefined
,同樣不會拋錯。不過這給後面的代碼帶來了隱患。當執行接下來的一句 console.log(invalidUser.name.substr(0, 10))
時,會報 Uncaught TypeError: Cannot read property 'substr' of undefined
這個在 JavaScript 中非常常見的錯誤。對於大型項目來說,一旦開發者遇到這樣的錯誤,通常是無從下手的,因爲你不知道這個 undefined
是如何產生的:來自後臺 API、或來自前面的代碼邏輯錯誤、還是數據問題?
再看看下一段 TS 的代碼。
// TS Example
// 性別
type Sex = 'female' | 'male';
// 用戶類
interface User {
name: string;
sex: Sex;
age: number;
}
// 合法用戶實例
const validUser: User = {
name: 'Zhang San',
sex: 'male',
age: 40,
};
// 非法用戶實例. 編譯報錯
const invalidUser: User = {
sex: 'unknown',
age: '10',
};
console.log(invalidUser.name); // 編譯報錯
console.log(invalidUser.name.substr(0, 10)); // 編譯報錯
由於用 type
和 interface
定義了類型,TS 編譯器可以在預編譯的時候掃描代碼提前發現錯誤,例如缺少字段或者字段非法等等。編譯這段代碼時,你會發現錯誤會在編譯時提前拋出來。因此開發者能夠在運行代碼之前提前預警可能的 Bug,並對此採取措施。
TypeScript 幾乎是大型前端項目的標配,幾乎是每個前端工程師的必備技能。
其他
前端工程化相關的知識還有很多,前文只介紹了一些核心知識。其他的一些前端工程相關的知識還包括但不限於以下內容:
- 代碼風格約束。幫助統一代碼風格,例如 ESLint。
- 單元測試。有效的避免 Bug,工具包括 Jest、Mocha 等。
- 多項目管理。拆分大型項目爲不同的子項目或模塊,幫助管理多模塊項目,代表工具有 Lerna。
- 項目部署。CI/CD、Nginx 等。
總結
本文從前端行業的發展開始,介紹了關於前端技術發展的歷史背景,解釋了前端工程化的重要性和必要性,並從打包部署開始,講解了現代化前端工程的打包部署過程以及實現工具。接下來,本文還介紹了前端工程化的另一個重要概念,模塊化,解釋了隨着前端應用變複雜的趨勢,產生的模塊化的需求,以及如何實現模塊化。之後,還介紹了前端框架的發展,包括最初的 jQuery,和後來的 AngularJS,以及現在的 React、Vue、Angular 三大前端框架,還有未來的前端框架發展展望。除此之外,本文還介紹了組件化和類型約束,解釋了爲什麼要在大型項目中使用 TypeScript。
總之,前端行業是一個高速發展的行業,特別是最近幾年,互聯網的高速發展催生了前端工程的發展,讓前端工程師成爲名副其實的工程師崗位。前端工程師之所以能稱作工程師,絕不僅僅是因爲他們能寫前端代碼,而是他們能利用架構思維和工程化思維來構建和管理大型複雜的前端應用,讓用戶能體驗到後臺功能複雜、但前臺使用簡單的各種 Web 或移動應用。前端技術以及前端工程化還在繼續發展,隨着新技術的出現和不斷成熟,相信我們還會學習到更多有趣而實用的前端知識。今天入坑前端的你,還學得動麼?
社區
如果您對筆者的文章感興趣,可以加筆者微信 tikazyq1 並註明 "碼之道",筆者會將你拉入 "碼之道" 交流羣。