大前端公共知識雜談
近年來,隨著移動化聯(lián)網(wǎng)浪潮的洶涌而來與瀏覽器性能的提升,iOS、Android、Web 等前端開發(fā)技術(shù)各領風騷,大前端的概念也日漸成為某種共識。其中特別是 Web 開發(fā)的領域,以單頁應用為代表的富客戶端應用迅速流行,各種框架理念爭妍斗艷,百花競放。而 Web 技術(shù)的蓬勃發(fā)展也催生了一系列跨端混合開發(fā)技術(shù),希望能夠結(jié)合 Web 的開發(fā)便捷性與原生應用的高性能性;其中以 Cordova、PWA 為代表的方向致力于為 Web 應用盡可能添加原生體驗,而以 NativeScript、ReactNative、Weex 為代表的利用 Web 技術(shù)或者理念開發(fā)原生應用。
平心而論,無論哪一種開發(fā)領域或者技術(shù),他們本質(zhì)上都是進行圖形用戶界面(GUI)應用程序的開發(fā),面對的問題、思考的方式、架構(gòu)的設計很大程度上仍然可以回溯到當年以 MFC、Swing、WPF 為主導的桌面應用程序開發(fā)時代,其術(shù)不同而道相似。
任何的前端開發(fā)學習中,我們都需要掌握基本的編程語言語法與接口;譬如在 Android 開發(fā)中使用的 Java 或者 Kotlin,在 iOS 開發(fā)中使用的 Objective-C 或者 Swift,在 Web 開發(fā)中使用的 JavaScript、HTML 與 CSS 等。編程語言的學習中我們往往關(guān)注于語法基礎、數(shù)據(jù)結(jié)構(gòu)、功能調(diào)用、泛型編程、元編程等內(nèi)容,譬如如何聲明表達式、如何理解作用域與閉包、如何進行基本的流程控制與異常處理、如何實踐面向?qū)ο缶幊獭⑷绾芜M行網(wǎng)絡請求通信等等。
接下來我們就需要了解如何構(gòu)建基礎的界面,譬如利用 HTML 與 CSS 繪制簡單 Web 頁面、利用代碼創(chuàng)建并使用簡單的 Activity、利用 StoryBoard 快速構(gòu)建界面原型等等。然后我們需要去學習使用常見的系統(tǒng)功能,譬如如何進行網(wǎng)絡交互,如何訪問遠端的 RESTful 接口以獲取需要的數(shù)據(jù)、如何讀取本地文件或者利用 SharedPreference、localStorage、CoreData 來存取數(shù)據(jù)、如何進行組件間或者應用間信息交互等內(nèi)容。
到這里我們已經(jīng)能夠進行基礎的界面開發(fā),并且為其增添必要的特性,不過在真實的項目中我們往往還會用到很多的組件或者插件,iOS 或者 Android 中為我們提供了豐富的 SDK,譬如 UITableView 或者 RecycleView 可以幫助我們快速構(gòu)建高性能列表組件,Android 5.0 之后默認的 Material Design 也是非常優(yōu)秀的界面樣式設計指南;而 Web 開發(fā)中我們往往需要引入第三方模式庫,譬如著名的 BootStrap、React Material UI、Vue element 都為我們提供了很多預置的樣式組件,react-virtualized 也為我們提供了高性能的類似于 ListView 這樣的部分項渲染機制。
然后我們需要將應用真實地發(fā)布給用戶使用,我們需要考慮很多工程實踐的問題,譬如如何進行測試與調(diào)試、如何進行性能優(yōu)化并且在生產(chǎn)環(huán)境下完成應用狀態(tài)跟蹤、熱更新等操作、如何統(tǒng)一開發(fā)團隊的代碼風格與約定等等;這里 Web 因為其特性而自帶了熱更新的功能,而在 Android 或者 iOS 我們則可以利用插件化技術(shù)或者 JSPatch 來實現(xiàn)熱更新。
Java 與 Swift 都是強類型語言,其能夠在編譯階段幫開發(fā)者排查問題減少潛在風險;而我們也可以使用 TypeScript 或者 Flow 為 JavaScript 添加靜態(tài)類型檢測的特性,在 VSCode 等現(xiàn)代編輯器中同樣可以達到類似于 Android Studio、XCode 中的即時檢查與提示的功能。
最后,隨著應用功能的增加、代碼庫的擴展,我們需要考慮整體的應用架構(gòu)與工程化的問題;在應用架構(gòu)中我們往往需要考慮模塊化、組件化以及狀態(tài)管理等多個方面,選擇合適的 MVC、MVP、MVVM、Flux、VIPER 等不同的架構(gòu)模式來引導應用中的代碼組織與職責分割;我們也需要考慮選擇合適的構(gòu)建與部署工具來簡化或者自動化應用發(fā)布流程,在 Android 開發(fā)中我們會選擇 Gradle 及其自帶的多模塊特性來管理依賴與分割代碼,而在 Web 中我們可以使用 Webpack、Rollup 等工具來自動處理依賴并且進行構(gòu)建,iOS 中我們也可以選擇 CoocaPods。
到這里我們會發(fā)現(xiàn)雖然具體的代碼實現(xiàn)、使用的技術(shù)不同,但是 Android、iOS 以及 Web 乃至于 React Native 等開發(fā)中,我們需要解決的問題、能夠用到的架構(gòu)設計模式都是可以相互借鑒的。在我們從某個領域遷移到其他領域時,我們能很方便地知道應該學習些什么,不同的技術(shù)、工具他們的職責是什么,應該選擇怎樣的架構(gòu)或者設計模式。古語云,欲窮千里目,更上一層樓,我們想要真正掌握某種客戶端開發(fā)技術(shù),最好是要了解我們應該掌握哪些方面,本文即是對筆者日前總結(jié)出的泛前端知識圖譜(Web/iOS/Android/RN) 的簡要闡釋。
編程語言
編程語言的學習是我們進入軟件世界的基礎階梯,著名的 Code Complete 一書中提到:Program into Your Language, Not in it. 我們不應該將自己的編程思維局限于掌握的語言提供的那些特性或者概念,而是能夠理解這些語法特性背后能提供的抽象功能與原理,從而能夠根據(jù)自己想要達到的目標選擇最合適的編程語言。而從另一個角度來看,無論哪一門編程語言的學習也是具有極大的共性,從嚴謹而又被詬病過度冗余的 Java 到需要用游標卡尺的 Python,從掙扎著一路向前的 JavaScript 到含著金湯匙出生的 Swift、Rust,我們都能夠發(fā)現(xiàn)其中的相通與互相借鑒之處。
語法基礎
任何一門編程語言的學習都需要從基本的表達式(Expression)語法開始學習,我們需要了解如何去聲明與使用變量、如何為這些變量賦值、如何使用運算符進行簡單的變量操作等等。在很多語言之中都有所謂的傳值還是傳引用的思量,譬如 Java 與 JavaScript 本質(zhì)上就是 Pass-by-Value 的語言,只不過會將復雜對象的引用值傳遞給目標變量。這個特性又引發(fā)了所謂淺復制與深復制、如何進行復合類型深拷貝等等需要注意的技術(shù)點。
除此之外,作用域與閉包也是很多語言學習中重點討論的內(nèi)容,在 JavaScript 與 Python 的學習中我們就會經(jīng)常討論如何利用閉包來保存外部變量,或者在循環(huán)中避免閉包帶來的意外變量值。表達式是一門編程語言語法基礎的重要組成部分,接下來我們就需要去學習流程控制與異常處理、函數(shù)定義與調(diào)用、類與對象、輸入輸出流、模塊等內(nèi)容。流程控制的典型代表就是分支選擇與循環(huán),譬如不同的語言都為我們提供了基礎 for 循環(huán)或者更方便地 for-in 循環(huán),而在 JavaScript 中我們還可以使用 forEach 與 for-of 循環(huán),Java 8 之后我們也可以基于 Stream API 中的 forEach 編寫聲明式地循環(huán)執(zhí)行體,而 Python 中的列表推導也可以看做便捷的循環(huán)實現(xiàn)方式。
異常處理也是各個編程語言的重要組成部分,合理的異常處理有助于增強應用的魯棒性;不過很多時候會出現(xiàn)濫用異常的情況,我們只是一層一層地拋出而并未真正地去處理或者利用這些異常。Java 中將異常分為了受控異常與不受控異常這兩類,雖然 JavaScript 等語言中并未在數(shù)據(jù)類型中有所區(qū)分,但是卻可以引入這種分類方式來進行不同的異常處理;有時候 Let it Crash 也是不錯的設計模式。
Eric Elliott 曾在博文中提及,軟件開發(fā)實際就是 Function Composition 與 DataStructure Design;函數(shù)或者方法是軟件系統(tǒng)的重要基石與組成。我們需要了解如何去定義函數(shù),包括匿名函數(shù)以及 Lambda 表達式等;盡管 Java 中的 Lambda 表達式是對于 FunctionalInterface 的實現(xiàn),但是鑒于其表現(xiàn)形式我們也可以將其劃歸到函數(shù)這個知識類別中。
接下來我們需要了解如何定義與傳入函數(shù)參數(shù),在 C 這樣的語言中我們會去關(guān)心指針傳遞的不同姿勢;而在 JavaScript 中我們常常會關(guān)心如何設置默認參數(shù),無論是使用對象解構(gòu)還是可選參數(shù),都各有利弊。Objective-C 與 Swift 中提供的外部參數(shù)就是不錯的函數(shù)自描述,Java 或 Python 中提供的不定參數(shù)也能夠幫我們更靈活地定義參數(shù),在 JavaScript 中我們則可以通過擴展操作符實現(xiàn)類似的效果。
然后我們就需要去考慮如何調(diào)用函數(shù),最典型就是就是 JavaScript 中函數(shù)調(diào)用的四種方式,我們還需要去關(guān)心調(diào)用時函數(shù)內(nèi)部的 this 指針指向。而裝飾器或者注解能幫我們更好地組織代碼,以類似于高階函數(shù)的方式如洋蔥圈般一層一層地剝離與抽象業(yè)務邏輯。最后在函數(shù)這部分我們還需要關(guān)心下迭代器與生成器,它們是不錯的異步實現(xiàn)模式或者流數(shù)據(jù)構(gòu)建工具。
近幾年隨著前端富客戶端應用的迅猛發(fā)展與服務端并發(fā)編程的深入應用,函數(shù)式編程以及 Haskell 這樣的函數(shù)式編程語言也是引領風騷。盡管面向?qū)ο缶幊桃灿兄芏嗥渌蝗嗽嵅〉牡胤?,但是在大型復雜業(yè)務邏輯的應用開發(fā)中我們還是會傾向使用面向?qū)ο缶幊痰姆妒?這就要求我們對于類與對象的基本語法有所掌握。
我們首先要去了解如何定義類,定義類的屬性、方法以及使用訪問修飾符等方式進行訪問控制。其次我們需要了解如何從類中實例化出對象,如何在具體的語言中實踐單例模式等。然后我們就需要去了解面向?qū)ο蟮睦^承與多態(tài)的特性,應該如何實現(xiàn)類繼承,子類與父類在靜態(tài)屬性、靜態(tài)方法、類屬性、構(gòu)造函數(shù)上的調(diào)用順序是怎樣的;以及如何利用純虛函數(shù)、抽象類、接口、協(xié)議這些不同的關(guān)鍵字在具體語言中實現(xiàn)多態(tài)與約定。最后我們還需要去關(guān)注下語言是否支持內(nèi)部類,譬如 Java 就分為了靜態(tài)內(nèi)部類、成員內(nèi)部類、局部內(nèi)部類與匿名內(nèi)部類這四種不同的分類。在整個語法基礎部分的最后,我們還需要去了解下輸入輸出流與模塊化相關(guān)的知識,譬如 Java 9 中即將推出 JPMS 模塊化系統(tǒng),而 JavaScript 的模塊化標準則歷經(jīng)了 CommonJS、AMD、UMD、ES6 Modules 等多輪變遷。
數(shù)據(jù)結(jié)構(gòu)與功能
語法基礎是我們掌握某門編程語言的敲門磚,而學習內(nèi)建的數(shù)據(jù)結(jié)構(gòu)與功能語法則是能夠用該語言進行實際應用開發(fā)的重要前提。在數(shù)據(jù)結(jié)構(gòu)的學習中我們首先要對該語言內(nèi)建的數(shù)據(jù)類型有所概覽,我們要了解如何進行常見的類型與值判斷以及類型間轉(zhuǎn)換;譬如如何進行引用與值的等價性判斷、如何進行動態(tài)類型檢查、如何對復合對象的常用屬性進行判斷等等。
很多編程語言中會將數(shù)據(jù)類型劃分為原始類型(Primitive)與復合類型(Composite),不過這里為了保證通用性還是將學習復雜度較低的數(shù)據(jù)類型劃歸到基本類型中。常見的基本類型囊括了數(shù)值類型、空類型、布爾類型、可選類型(Optional)以及枚舉類型(Enum)等等。學習數(shù)值類型的時候我們還需要了解如何進行隨機數(shù)生成、如何進行常見的科學計算,這也是基礎的數(shù)值理論算法的重要組成。JavaScript 中提供了 undefined 與 null 兩個關(guān)鍵字,二者都可以認為是空類型不過又有所區(qū)別;而可選類型則能夠幫我們更好地處理可能為空地對象,避免很多的運行時錯誤。
接下來我們就要將目光投注于字符串類型上,我們需要了解如何創(chuàng)建、刪除、復制、替換某個字符串或者其他內(nèi)容;很多語言也提供了模板字符串或者格式化字符串的方式來創(chuàng)建新字符串。我們還需要知道如何對字符串進行索引遍歷,如何對字符串進行常見的類型編碼以及如何實踐模式匹配。模式匹配中最直接的方式就是使用正則表達式,這也是我們應用開發(fā)中經(jīng)常會使用到的技術(shù)點。除此之外我們還需要關(guān)注字符串校驗、以及如何進行高效模糊搜索等等內(nèi)容;我們也可以學習使用 KMP、Sunday 等常見的模式匹配算法來處理搜索問題。
然后我們需要學習常見的時間與日期處理方式,了解如何時間戳、時區(qū)、RFC2822、ISO8601 這些基礎的時間與日期相關(guān)的概念,了解如何從時間戳或者時間字符串中解析出當前編程語言支持的時間與日期對象。我們還需要了解時區(qū)轉(zhuǎn)換、時間比較以及如何格式化地展示時間等內(nèi)容,有時候我們還需要利用日歷等對象進行事件的增減以及偏移計算。
接下來就是非常重要的集合類型,無論哪種編程語言都會提供類似于 Array、List、Set、Dictionary、Map 等相關(guān)的數(shù)據(jù)結(jié)構(gòu)實現(xiàn),而我們也就需要去了解這些常見集合類型中的增刪復替以及索引遍歷這些基礎操作以及每個集合的特點;譬如對于序列類型我們要能熟練使用 map、reduce、filter、sort 這些常見的變換進行序列變換與生成。進階而言的話我們可以多了解下這些數(shù)據(jù)結(jié)構(gòu)的底層算法實現(xiàn),譬如 Java 8 中對于 HashMap 的鏈表/紅黑樹實現(xiàn),或者 V8 中是如何利用 Hidden Class 進行快速索引的。接下來的話我們可以對于像 Java 中 SteamAPI 或者各種語言的 Immutable 對象的實現(xiàn)方式有所了解,還有就是常見的 JSON、XML、CSV 這些類型的序列化與反序列化操作庫也是實際開發(fā)中經(jīng)常用到的。
接下來我們就需要對語言提供的常用外部功能相關(guān)的 API 或者語法有所掌握,主要也是分為存儲、網(wǎng)絡與系統(tǒng)進程這三個部分。在存儲部分我們需要掌握如何與 MySQL、Redis、Mongodb 等關(guān)系型或者非關(guān)系型數(shù)據(jù)庫進行數(shù)據(jù)交互,掌握如何對文件系統(tǒng)進行如文件尋址、文件監(jiān)控等操作,并且還需要能夠使用一些譬如 Java 堆外存儲這樣的應用內(nèi)緩存來存放數(shù)據(jù)。而網(wǎng)絡部分我們應該掌握如何利用 HTTP 客戶端進行網(wǎng)絡交互、如何使用相對底層的 Socket 套接字建立 TCP 連接、或者使用語言內(nèi)置的一些遠程調(diào)用框架與遠端服務進行交互。
最后我們需要對如何利用語言進行系統(tǒng)進程操作有所了解,本部分筆者認為最重要的當屬并發(fā)編程相關(guān)知識。在而今服務器性能不斷提升、處理的數(shù)據(jù)量越來越多的情況下,我們不可避免地需要使用并發(fā)操作來提高應用吞吐量。并發(fā)編程領域我們應該去學習如何使用線程、線程池或者協(xié)程來實現(xiàn)并發(fā),如何利用鎖、事務等方式進行并發(fā)控制并保持數(shù)據(jù)一致性,如何使用回調(diào)、Promise、Generator、Async/Await 等異步編程模式。除此之外,我們還需要對切面編程、系統(tǒng)調(diào)用以及本地跨語言調(diào)用有所了解。
工程實踐與進階
編程語言初學階段的最后我們需要了解下工程實踐以及一些偏原理與底層實現(xiàn)的進階內(nèi)容。首先開發(fā)者應當對具體編程語言中如何實現(xiàn) S.O.L.I.D 編程原則與數(shù)十種設計模式有所了解,當然也不能邯鄲學步只求形似,而是能夠根據(jù)業(yè)務功能需求靈活地選擇適用的范式。而在團隊開發(fā)中我們往往還需要統(tǒng)一團隊內(nèi)的樣式指南,包括代碼風格約定中常見的命名約定、文檔與注釋約定、項目與模塊的目錄架構(gòu)以及語法檢查規(guī)范等。接下來我們還需要對語言或者常用開發(fā)工具的調(diào)試方式有所了解,掌握基本的單步調(diào)試等技巧,并且能夠為代碼編寫合適的單元測試用例。
工程實踐方面的最后則是要求我們對代碼性能優(yōu)化所有了解,盡量避免反模式。進階內(nèi)容的話則相對更加地抽象或者需要花費更多的精力去學習,其中包括泛型編程、元編程、函數(shù)式編程、響應式編程、內(nèi)存管理、數(shù)據(jù)結(jié)構(gòu)與算法等幾個部分。泛型編程與元編程中的反射、代碼生成、依賴注入等還是屬于語言本身提供的語法特性之一,而函數(shù)式編程與響應式編程則偏向于實際應用開發(fā)中有所偏愛的開發(fā)范式。即使 Java 這樣純粹的面向?qū)ο蟮恼Z言,當我們借鑒純函數(shù)、不可變對象、高階函數(shù)、Monad 等函數(shù)式編程中常見的名詞時,也能為代碼優(yōu)化開辟新的思路。響應式編程是非常不錯的異步編程范式,這里我們還需要注意下并發(fā)編程與異步編程之間的差異。而內(nèi)存管理則有助于我們理解編程語言運行地底層機制,譬如對于 JVM 或者 V8 的內(nèi)存結(jié)構(gòu)、內(nèi)存分配、垃圾回收機制有所了解的話能夠反過來有助于我們編寫高性能地應用程序,并且對于線上應用錯誤的調(diào)試也能更加得心應手。
界面基礎
用戶界面是前端應用程序的核心組成部分,而我們涉足前端開發(fā)的第一步往往也就是從簡單的界面搭建開始。我們可能是在 Android 中編寫簡單的基于 XML 布局的 Activity,在 iOS 中利用 StoryBoard 快速構(gòu)建導航界面,或者在 Web 中使用某個框架實現(xiàn) TODOList。而界面開發(fā)最基礎的部分就是布局與定位,無論在何端開發(fā)中我們往往都會使用相對布局、絕對布局、彈性布局、網(wǎng)格布局等布局方式;并且面向多尺寸的屏幕我們往往也需要進行響應式布局的考慮,從橫豎屏響應式切換到不同分辨率下的布局與尺寸的調(diào)整,都是為了給予用戶較好的使用體驗。而了解了布局與定位之后,我們往往就需要來學習如何使用基本的界面容器,譬如常見的滾動視圖、導航視圖、頁卡視圖與伸縮視圖。
Android 與 iOS 往往也為我們對這些基本容器進行了較好地封裝,而 Web 中則往往需要我們自己動手去實現(xiàn)相應功能。譬如在滾動視圖中,我們需要去提供常見的滾動事件控制,典型的有如何在不同環(huán)境下保證平滑滾動體驗、如何設置優(yōu)美的滾動條、如何設置滾動監(jiān)聽等等。除此之外,我們往往還需要針對列表或者長閱讀界面封裝一些高級事件響應,譬如上拉加載、下拉刷新以及無限滾動時需要的滾動觸發(fā)規(guī)則實現(xiàn)。
作為最常見的用戶交互方式之一,無論是在移動端還是桌面端,我們也都需要實現(xiàn)一些優(yōu)美的動畫;譬如視差滾動就可以給用戶帶來不一樣的視覺感受,而像 Swiper 這樣的整頁滾動則是很好地產(chǎn)品展示或者講演頁的交互方式。在基礎的界面容器使用中我們已經(jīng)接觸了一些用戶交互的監(jiān)聽與響應的實現(xiàn),接下來我們則是需要深入全面地了解用戶交互相關(guān)內(nèi)容。最基礎的我們需要了解常用事件與手勢操作,了解如何進行事件監(jiān)聽與綁定、如何捕獲事件并且進行分發(fā)、如何進行縮放、拖拽、搖晃等復雜手勢動作地監(jiān)聽與識別、如何響應鍵盤事件并且進行響應處理。
除此之外,筆者將音視頻錄制與播放,指紋、計步器等傳感器的使用,本地通知與遠程推送等內(nèi)容也都歸納于了用戶交互這個分類下。在 Android 與 iOS 開發(fā)中相信對于這些 API 的使用并不會陌生,而隨著 HTML5 的流行以及現(xiàn)代瀏覽器的發(fā)展,相信未來 Web 應用也會越來越多地添加這些與系統(tǒng)層面進行交互地功能。我們在本部分還需要了解下動畫與變換、繪圖及數(shù)據(jù)可視化等相關(guān)內(nèi)容。常見的動畫引擎包含了屬性控制與幀動畫兩種方式,前者更趨向于命令式編程而后者則適用于聲明式編程;除了了解這些基礎的語法,我們還需要對常用的動畫進行收集與匯總,以便在項目開發(fā)中能夠靈活應用。而隨著大數(shù)據(jù)時代的到來,數(shù)據(jù)可視化相關(guān)應用也成了前端開發(fā)常見的任務之一。
在這個部分,我們需要對 SVG、Canvas、WebGL 等相關(guān)繪畫基礎有所了解,能夠運用 D3.js 或者其他類似的庫進行簡單圖形繪制。并且我們要能夠利用 ECharts 等優(yōu)秀的外部繪圖庫進行散點圖、折線圖、流程圖等常見類型圖表進行繪制。最后,地圖以及相關(guān)技術(shù)也是我們需要去了解的,作為開發(fā)者我們要能夠基于百度地圖等第三方 API 或者 SDK 開發(fā)導航、地理位置信息可視化等相關(guān)的功能。
系統(tǒng)功能
與界面基礎相對的就是常見的系統(tǒng)功能以及 API 的使用語法,其主要分為系統(tǒng)與進程、數(shù)據(jù)存儲以及網(wǎng)絡交互這三個部分。
進程與存儲
在開發(fā)多界面應用程序或者利用 Service、ServiceWorker 等方式啟動后臺線程時,我們就需要考慮如何進行組件間通信;譬如在 Android 開發(fā)中我們可以利用 Otto 等庫以消息總線的方式在 Activity、Fragment、Service 等組件之間傳遞消息。而在 Android 或者 iOS 開發(fā)中我們也常常需要考慮并發(fā)編程,可能會涉及到如何利用 Thread、GCD 等方式實現(xiàn)多線程并行、如何利用 RxJava 等響應式擴展優(yōu)化異步編程模型、如何利用鎖等同步方式進行并發(fā)控制等等內(nèi)容。有時候我們也需要去更多地了解系統(tǒng)服務相關(guān)的內(nèi)容,特別是在 Android 或者桌面應用程序開發(fā)時,我們需要考慮如何實現(xiàn)守護進程以協(xié)調(diào)并且保障各個組件的正常運行。
在系統(tǒng)與進程部分的最后,我們還需要去接觸些系統(tǒng)輔助相關(guān)的功能實現(xiàn),譬如如何進行運行環(huán)境檢測、如何利用 DeepLink 進行 APP 之間跳轉(zhuǎn)、如何進行應用的權(quán)限管理等等。接下來我們討論下數(shù)據(jù)存儲部分應該掌握哪些內(nèi)容,最簡單的就是類似于 SharedPreference、NSUserDefaults、localStorage 這樣的鍵值類存儲;復雜一點的情況我們可能會利用到 SQLite 或者 IndexedDB 這樣的簡化關(guān)系型或者文檔型數(shù)據(jù)庫,有時候 Realm 這樣的第三方解決方案也是不錯的選擇。很多時候我們還需要了解如何控制緩存或者剪貼板中的內(nèi)容,以及如何對文件系統(tǒng)進行基本的操作,譬如讀寫配置文件與資源文件、瀏覽列舉文件系統(tǒng)中的文件并且根據(jù)不同的文件類型選用不同的處理方式。
網(wǎng)絡交互
而網(wǎng)絡交互部分更多地關(guān)注如何與服務端或者第三方系統(tǒng)進行交互,實際上對于如何在需求動態(tài)變化的情況下較好地協(xié)調(diào)服務端與客戶端對于接口的定義是很多項目開發(fā)的痛點。不過從基礎使用的角度,我們首先需要了解如何利用網(wǎng)絡客戶端進行基于 HTTP 或 HTTPS 的網(wǎng)絡請求。這部分我們需要了解如何構(gòu)造、分析、編碼 URI,如何管理請求頭、設置請求方法與請求參數(shù),如何同步、異步或者并發(fā)地執(zhí)行請求,如何進行響應解析,如何進行復雜的請求管理等等內(nèi)容。
除了這些,我們還要能夠利用基礎的 Socket 進行通信,這樣有助于我們理解通信網(wǎng)絡與 TCP/IP 實現(xiàn)原理;我們往往還需要關(guān)心如何利用 WebSocket 等技術(shù)實現(xiàn)推送與長連接功能,如何進行遠程與本地方法調(diào)用等等。除了這三個偏功能實現(xiàn)的知識點,我們還可以嘗試去了解下系統(tǒng)的底層設計原理。譬如在 Android 開發(fā)中我們可以嘗試去了解 Dalvik 虛擬機的工作原理,使用 Xposed 或類似工具進行系統(tǒng)層面的一些操作;對于 Web 開發(fā)而言我們可以去更多地關(guān)注瀏覽器工作原理,了解現(xiàn)代瀏覽器的運行機制等等內(nèi)容。
界面插件
在掌握了如何構(gòu)建基本的界面并且為應用添加必須的功能之后,我們就需要去嘗試進行應用項目開發(fā)。每個應用可以按照用戶交互地邏輯切分為多個獨立界面,而每個界面的開發(fā)中我們往往又需要編寫導航、菜單、列表、表單等等可重復使用的界面插件。實際上前端開發(fā)中最核心的工作之一就是界面插件的開發(fā),好的開發(fā)者能夠在項目開發(fā)中沉淀出可復用的界面插件庫;這類可復用的界面插件往往會獨立于具體的業(yè)務邏輯,其分類自然也應按照顯示或者交互邏輯本身,而不應該受制于不同的業(yè)務場景。筆者習慣地會將界面插件區(qū)分為指示器(Indicator)、輸入器(Picker)、列表與表單(TableGrid)、對話框(Dialog)、畫廊(Galley)、WebView 等幾個部分。
指示器與輸入器
指示器與輸入器算是兩個寬泛的界面插件分類,最常見的指示器當屬文本顯示類別的插件,譬如標簽。標簽多用于表單中的輸入域描述、用戶引導等場景,而除了文字標簽之外我們也會使用圖標或者所謂的 Tags。除此之外我們還會關(guān)注于 MarkDown 等富文本的展示、如何針對不同屏幕對頁面進行排版與字體設置、如何針對不同地區(qū)的用戶進行國際化切換、如何為文本添加合適的動畫等等方面。
在應用開發(fā)中我們也會添加專門的介紹或引導頁,一方面引導用戶使用,另一方面也可以進行后臺資源請求與處理;譬如我們往往會在應用啟動時設置閃屏頁(Splash),記得最早在 Uber 見到以短視頻為背景的閃屏頁很有耳目一新的感覺。除此之外,我們常見的指示器還包括了進度指示與時間指示這兩種。在進行數(shù)據(jù)請求或者數(shù)據(jù)處理等需要用戶等待的場景中,我們往往會給用戶以進度條方式地友好反饋,這種進度條就是典型地進度指示。常用的進度條設計有線性進度條、圓形進度條或者固定在頁首或者頁尾的進度條,有些設計中我們也會以背景投射地方式反饋當前進度,這種方式可能更具有視覺沖擊力。而除了進度條之外,無限循環(huán)的加載效果、分頁器或者步驟跟蹤顯示器也是常見的進度指示的表現(xiàn)形式之一。而所謂的時間指示即譬如界面上放置的擬物時鐘或者電子時鐘、常見于社交媒體上的時間軸或者日歷效果以及倒計數(shù)效果等。
而輸入器的典型代表則為按鈕與文本輸入,譬如我們除了常見的 Primary、Secondary 按鈕之外,我們可能還會用到懸浮按鈕、可擴展的按鈕或者在喜歡與點贊時用到的具有一定動畫效果的按鈕。而文本輸入系列的插件中,除了常見的文本框或者富文本編輯器,有時我們也需要去編寫具有自動補全或者類似于密碼、勾選之類的特殊格式的輸入框。選擇器也是我們常用到的輸入器之一,譬如開關(guān)、單選按鈕、勾選按鈕、分段輸入以及常用于兩個列表互選的左右穿梭器等等。除了這些,搜索、菜單、解鎖界面也是歸屬于輸入器這個類別中。
列表、畫廊與對話框
在這兩個大類之外算得上最常用的插件的當屬列表、網(wǎng)格與表單這個系列的控件;基本上每個應用都會包含列表或者網(wǎng)格布局,對于海量數(shù)據(jù)的列表渲染也是前端常見的挑戰(zhàn)之一。Android 中內(nèi)置的 RecycleView 與 iOS 中內(nèi)置的 UITableView 都為我們提供了不錯的懶加載、局部渲染的功能,而 Web 中我們往往需要自己定制或者尋求第三方庫的幫助。對于列表的交互也是常見問題之一,除了允許用戶正常的點擊,我們還需要添加左滑右滑時的反饋、可伸縮或者允許排序、拖拽的方式進行交互,有時候還需要為了列表項添加進出時的轉(zhuǎn)場動畫,以這種微互動增加整個界面的友好性。
最后我們來聊聊畫廊與對話框,畫廊最典型的插件就是提供圖片或者視頻預覽的走馬燈效果的輪播插件,筆者也是將圖片加載、呈現(xiàn)、處理相關(guān)的插件劃分到了畫廊這一系列插件中。而在端開發(fā)中我們常常需要對相冊或者緩存中的圖片進行瀏覽,或者將圖片以瀑布流的方式呈現(xiàn)給用戶,這種性質(zhì)的插件也應歸屬到畫廊這一類中。對話框的分類則稍顯的有些生硬,譬如 ActionSheet、HUD 是系統(tǒng)提供的消息提示性質(zhì)的插件,這種彈出與顯示層自然會劃歸到對話框這個系列的組件中。而在 Web 中我們常常需要自定義的模態(tài)對話框、覆蓋層也屬于對話框系列,有時候我們還需要考慮如何為對話框提供拖拽支持,或者在對話框顯示和消失之際添加轉(zhuǎn)場動畫。
工程化與應用架構(gòu)
前面我們討論了開發(fā)某個前端應用所需要的必備技能,而在需要持續(xù)交付的團隊項目開發(fā)中,我們還需要考慮很多工程實踐相關(guān)的方法與技巧。命令式編程到聲明式編程的變化,將更多地功能性工作交于框架處理,而開發(fā)人員更加地專注于業(yè)務邏輯的實現(xiàn)。
工程實踐
代碼調(diào)試是每個程序員都掌握的技能,不過如何較好地調(diào)試代碼以快速定位錯誤所在卻并不是那么容易。在開發(fā)中我們常常需要熱加載、增量編譯等相關(guān)技術(shù)來避免過長的等待,而單步調(diào)試則能夠幫助我們梳理代碼邏輯、循序漸進地發(fā)現(xiàn)問題所在??赡? iOS、Android 的開發(fā)人員更習慣使用單步調(diào)試,而在 Web 或者 Node.js 開發(fā)中我們也應適當?shù)囟嗍褂?Chrome 等工具進行代碼的單步調(diào)試;有時候單步調(diào)試也是不錯的瀏覽分析第三方源代碼庫的方式。
另一方面,日志無論在開發(fā)環(huán)境還是生產(chǎn)環(huán)境中都能夠幫我們記錄應用運行狀態(tài)等信息。接下來我們還要了解應用開發(fā)周期中不同階段使用的單元測試、集成測試以及端到端測試的具體的實現(xiàn)方式,在團隊協(xié)同開發(fā)中統(tǒng)一代碼風格與約定,能夠利用多種方式對應用進行性能優(yōu)化,以及在發(fā)布到生產(chǎn)環(huán)境之后能夠混淆加密、進行應用更新以及應用狀態(tài)跟蹤。
應用架構(gòu)
所謂架構(gòu)二字,核心即是對于對于富客戶端的代碼組織/職責劃分,從具體的代碼分割的角度,即是功能的模塊化、界面的組件化、應用狀態(tài)管理這三個方面??v覽這十年內(nèi)的架構(gòu)模式變遷,大概可以分為 MV 與 Unidirectional 兩大類,而 Clean Architecture 則是以嚴格的層次劃分獨辟蹊徑。從筆者的認知來看,從 MVC 到 MVP 的變遷完成了對于 View 與 Model 的解耦合,改進了職責分配與可測試性。而從 MVP 到 MVVM,添加了 View 與 ViewModel 之間的數(shù)據(jù)綁定,使得 View 完全的無狀態(tài)化。
最后,整個從 MV 到 Unidirectional 的變遷即是采用了消息隊列式的數(shù)據(jù)流驅(qū)動的架構(gòu),并且以 Redux 為代表的方案將原本 MV* 中碎片化的狀態(tài)管理變?yōu)榱私y(tǒng)一的狀態(tài)管理,保證了狀態(tài)的有序性與可回溯性。 實際上從 MVC、MVP 到 MVVM,一直圍繞的核心問題就是如何分割 ViewLogic 與 View,即如何將負責界面展示的代碼與負責業(yè)務邏輯的代碼進行分割。所謂分久必合,合久必分,從筆者自我審視的角度,發(fā)現(xiàn)很有趣的一點。
Android 與iOS中都是從早期的用代碼進行組件添加與布局到專門的 XML/Nib/StoryBoard 文件進行布局,Android 中的 Annotation/DataBinding、iOS 中的 IBOutlet 更加地保證了 View 與 ViewLogic 的分割(這一點也是從元素操作到以數(shù)據(jù)流驅(qū)動的變遷,我們不需要再去編寫大量的 findViewById。而Web的趨勢正好有點相反,無論是 WebComponent 還是 ReactiveComponent 都是將 ViewLogic 與 View 置于一起,特別是 JSX 的語法將 JavaScript 與 HTML 混搭,頗有幾分當年 PHP/JSP 與 HTML 混搭的風味。
從代碼組織的角度來看,項目的構(gòu)建工具與依賴管理工具會深刻地影響到代碼組織,這一點在功能的模塊化中尤其顯著。譬如筆者對于 Android/Java 構(gòu)建工具的使用變遷經(jīng)歷了從 Eclipse 到 Maven 再到 Gradle,筆者會將不同功能邏輯的代碼封裝到不同的相對獨立的子項目中,這樣就保證了子項目與主項目之間的一定隔離,方便了測試與代碼維護。同樣的,在 Web 開發(fā)中從 AMD/CMD 規(guī)范到標準的 ES6 模塊與 Webpack 編譯打包,也使得代碼能夠按照功能盡可能地解耦分割與避免冗余編碼。而另一方面,依賴管理工具也極大地方便我們使用第三方的代碼與發(fā)布自定義的依賴項,譬如 Web 中的 NPM 與 Bower,iOS 中的 CocoaPods 都是十分優(yōu)秀的依賴發(fā)布與管理工具,使我們不需要去關(guān)心第三方依賴的具體實現(xiàn)細節(jié)即能夠透明地引入使用。
因此選擇合適的項目構(gòu)建工具與依賴管理工具也是好的GUI架構(gòu)模式的重要因素之一。不過從應用程序架構(gòu)的角度看,無論我們使用怎樣的構(gòu)建工具,都可以實現(xiàn)或者遵循某種架構(gòu)模式,筆者認為二者之間也并沒有必然的因果關(guān)系。而組件即是應用中用戶交互界面的部分組成,組件可以通過組合封裝成更高級的組件。組件可以被放入層次化的結(jié)構(gòu)中,即可以是其他組件的父組件也可以是其他組件的子組件。根據(jù)上述的組件定義,筆者認為像 Activity 或者UIViewController 都不能算是組件,而像 ListView 或者 UITableView 可以看做典型的組件。 我們強調(diào)的是界面組件的Composable&Reusable,即可組合性與可重用性。
當我們一開始接觸到 Android 或者 iOS 時,因為本身 SDK 的完善度與規(guī)范度較高,我們能夠很多使用封裝程度較高的組件;凡事都有雙面性,這種較高程度的封裝與規(guī)范統(tǒng)一的 API 方便了我們的開發(fā),但是也限制了我們自定義的能力。同樣的,因為 SDK 的限制,真正意義上可復用/組合的組件也是不多,譬如你不能將兩個 ListView 再組合成一個新的ListView。在 React 中有所謂的 controller-view 的概念,即意味著某個 React 組件同時擔負起 MVC 中 Controller 與 View 的責任,也就是 JSX 這種將負責 ViewLogic 的 JavaScript 代碼與負責模板的 HTML 混編的方式。
界面的組件化還包括一個重要的點就是路由,譬如 Android 中的 AndRouter、iOS中的 JLRoutes 都是集中式路由的解決方案,不過集中式路由在 Android 或者 iOS 中并沒有大規(guī)模推廣。iOS 中的 StoryBoard 倒是類似于一種集中式路由的方案,不過更偏向于以 UI 設計為核心。筆者認為這一點可能是因為 Android 或者 iOS 本身所有的代碼都是存放于客戶端本身,而 Web 中較傳統(tǒng)的多頁應用方式還需要用戶跳轉(zhuǎn)頁面重新加載,而后在單頁流行之后即不存在頁面級別的跳轉(zhuǎn),因此在 Web 單頁應用中集中式路由較為流行而 Android、iOS 中反而不流行。
所謂可變的與不可預測的狀態(tài)時軟件開發(fā)中的萬惡之源,我們盡可能地希望組件的無狀態(tài)性,那么整個應用中的狀態(tài)管理應該盡量地放置在所謂 High-Order Component 或者 Smart Component 中。在 React 以及 Flux 的概念流行之后,Stateless Component 的概念深入人心,不過其實對于 MVVM 中的 View,也是無狀態(tài)的 View。通過雙向數(shù)據(jù)綁定將界面上的某個元素與 ViewModel 中的變量相關(guān)聯(lián),筆者認為很類似于 HOC 模式中的 Container 與 Component 之間的關(guān)聯(lián)。隨著應用的界面與功能的擴展,狀態(tài)管理會變得愈發(fā)混亂。
【本文是51CTO專欄作者“張梓雄 ”的原創(chuàng)文章,如需轉(zhuǎn)載請通過51CTO與作者聯(lián)系】