最近有位(大家都知道是誰)的『前輩』在微博上天天叫喚,相信很多人都看煩了。我跟他也算是吵了一架,污染了大家的時間線,先說聲抱歉。但是我覺得讓這么一個撒潑的人誤人子弟,實在看不下去,所以咱上點干貨以正視聽。
對待新事物的態度問題
我這個人在技術討論的時候信奉很簡單的一個道理:沒有研究過就沒有發言權。對於我不懂的東西,我要么閉嘴,要么去研究、試用之后再開口。在我看來,這是進行技術討論的一種基本涵養。技術這個行當,永遠會有新東西出來,不進則退。更關鍵的是,前端比起整個軟件工程乃至計算機科學體系來說,是個相對新生草莽的領域,近年來前端生態的發展其實都是在向其他領域吸收和學習,不論是開發理念、工程實踐還是平台本身(規范、瀏覽器)。所謂的『根正苗紅』的前端,不過是整個發展進程中探索的一個階段而已,那個時代的最佳實踐,很多到今天都已經不再適用。過往的經驗固然有價值,但這些經驗如果不結合對新事物本身的了解,就很難產生正確的判斷。這里需要強調的是,學習新事物並不是為了不考慮實際需求的濫用,而是為了獲取足夠的信息從而作出更靠譜的判斷。在這一點上,某人的態度是不停強調自己過去的八年資歷,強調『經驗和觀察』的重要性,卻對新事物本身采取蔑視和抗拒到了誇張的程度,寧可在微博上撕逼,也不願意花一兩個小時翻牆了解一下外面的世界。舉例來說,他在批評 Angular/React 的時候是以『就是把服務器 MVC 那一套搬到了前端』這樣的預設去批判的,完全雞同鴨講;在闡述他自己那套 Widget + OO 的時候,也壓根不了解這套思維和現在基於組件的新框架的共通點。這樣偏頗的思維方式,如何做得出靠譜的決策?作為一個技術負責人簡直可以說是不負責任。盲目跟風不可取,盲目抗拒也不可取,要有自己的判斷,但是這個判斷要建立在足夠的一手信息量基礎上。
為什么要用『新技術』?
說完態度,我們來深入談談為什么這些『新技術』會有市場。首先明確一點:任何技術都有針對的適用場景取舍。在對一個技術進行評估后發現不適合,這很正常,比如項目的類型、規模、歷史包袱,以及團隊的學習能力,都是制約技術選型的因素。但這並不妨礙我們分析一項技術本身解決了什么問題,以及我們的實際需求中是否存在這些問題。接下來我們一項項分析:
1. CSS 預/后處理器
比如:Sass, Less, Stylus, PostCSS。CSS 本身的設計有很多不利於工程化、影響開發效率的地方,這正是 CSS 預/后處理器要解決的問題。
(1) 默認的全局 namespace。在全局 namespace 下,任何一條規則都可能產生全局的影響,不利於模塊化的多人協作;同時選擇器的優先級如果沒有嚴格的書寫規范,很快就會難以管理,然后產生各種 !important hack。這一點借助預處理器雖然不能完全解決,但借助 nesting 可以讓書寫體驗得到改善。
(2) 作為一個 DSL 缺乏抽象能力:沒有變量,沒有函數,沒有運算符,沒有混入和繼承,代碼的可復用性差,經常需要大量重復,而通過組合類名的方式來復用靈活性非常有限。這些都可以借助預處理器進行緩解,甚至可以抽象出常用技巧的混入,比如一個混入解決垂直居中,大大加強 CSS 代碼的書寫效率和可維護性。
(3) 文件組織:通過原生的 @import 引入其他文件會產生過多的請求,而預處理器可以直接合並成一個文件,在文件組織上不再有顧慮。
(4) 智能避免重復勞動:自動根據目標瀏覽器范圍添加前綴。
2. JavaScript 編譯器
比如:Babel, CoffeeScript, TypeScript。為什么要編譯 JavaScript,本質上目標依然是:提高開發效率,提高可維護性。以 Babel 為例,JS 本身的 prototype 原型繼承,之前幾乎每個人都有自己實現一套 OO 模擬,現在有原生的 class extends 語法,從語言層面進行統一;函數的參數結構和默認值,避免了手動的默認值分配和參數為 0 的坑;箭頭函數避免了 this 上下文的坑;塊級的 let/const 避免了 var hoisting 的坑;templateString 避免繁瑣的手動字符串拼接;更好的 Unicode 支持;ES2015 模塊(為什么,見這個演講 //benjamn.github.io/empirenode-2015/#/) ; 還有 async await 對於異步流程處理本質上的改善。一個更好的語言,一個已經正式發布的標准,瀏覽器支持情況不一,有人寫了工具讓你今天就能用,某人對此的態度居然是『沒有問題也要創造問題』…
CoffeeScript 和 TypeScript 很顯然有各自的主要受眾:Ruby 開發者和 Java/C# 開發者。這時候某人又要搬出『偽前端』的大帽子來了,問題是,誰能搞前端並不是你說了算,前端的風氣也不會因為這兩個東西而受什么影響。這兩個東西的意義就在於它們能夠提高特定人群的開發效率,會去用它們的人很少是因為跟風,而是因為它們確實符合了一部分人的開發習慣。你不屬於那部分人,不代表這些東西沒有意義,它們被噴真是躺着也中槍。(web前端學習交流群:328058344 禁止閑聊,非喜勿進!)
3. 模塊化/構建工具
比如:RequireJS, SeaJS,Webpack, Browserify, SystemJS。模塊化的重要性想必不必多言了,連某『前輩』都在用 RequireJS。前面那兩個現在已經漸漸式微了,那為什么要有后面這三個?其中一個核心價值在於基於模塊規范的包管理方案。由於對於 Node 包格式的兼容,使得后面三個方案都可以利用 npm 作為包管理的機制。有了包管理器,你可以將跨項目的基礎庫進行細粒度的單獨封裝,通過 semver 版本保證 API 兼容性,在多個項目中按需復用代碼邏輯,還可以直接使用發布在 npm 上的海量第三方庫。更進一步,配合下面要提到的組件化框架,更可以實現 UI 組件的跨項目復用。SeaJS/spm 和 Arale 其實有這個願景,但是玉伯明智的發現了社區的方向而選擇了避免重復的努力。
另一方面,是在於后面這些新工具強大的擴展機制(尤其是 Webpack)所帶來的一種新的前端打包思路:不僅僅是 JavaScript,而是將 HTML、CSS 和其他靜態資源統統作為『模塊』來看待。因為在實際開發中,不僅僅是 JavaScript 的模塊之間存在依賴關系,HTML、CSS 和其他靜態文件之間也會有依賴關系。實際開發中,開發環境和生產環境中這些靜態資源之間的相對路徑關系經常是不一樣的,這就導致我們以往在開發環境到生產環境的上線過程中有很多繁瑣的步驟,比如改寫靜態資源引用的 URL(版本戳,靜態資源域名/CDN),圖片優化,根據文件大小做成內聯、模塊的切分和按需加載等等,這些瑣碎的事情固然可以手動解決,但我們要的是效率!效率!一次配置完畢,讓開發者能夠將后續精力專注於應用本身而不是其他東西。除了上面提到的幾個國外方案,國內也有優秀的類似方案 FIS。 最后,就是基於構建工具我們能夠提供更好的開發體驗。Webpack 的熱重載,在修改代碼后不重載頁面的情況下替換單一模塊,對開發體驗帶來質的提升。舉例來說,你在修改一個打開應用后需要 N 次操作才能看到的組件,如果你改一次就要重復這些操作,那樣效率實在太低。
4. 組件化框架
比如:React, Angular 2, Vue。在我看來現代化的組件化框架提供三個核心價值:
(1) 數據到 DOM 的聲明式映射。無論是 virtual dom render 還是模板,其本質都是聲明式地描述『基於這樣的數據,最終應該呈現給用戶這樣的界面』。在大部分場景下,用戶通常不需要再進行命令式的 DOM 操作。聲明式的代碼比命令式的代碼更簡潔,更容易維護。
(2) 組件的組織方式。一個組件的各個部分是分散在多個文件中,還是有合理的組織方式?組件如何發布、如何在多個項目中復用?對此,React 的選擇是把所有東西都放進 JS,而 Vue 則是基於構建工具實現類似 Web Component 的單文件組件格式(也可以拆分,同時支持預處理器)。Angular 2 目前要么分開多個文件,要么直接將 HTML/CSS 作為字符串內聯。
(3) 組件之間如何組合與溝通。這里的共同要點也是聲明式 > 命令式:通過在 render function / 模板中用標簽形式在父組件中渲染子組件,讓數據驅動組件的存在,從而自然地得到樹狀的組件樹結構,而不是命令式地用 this.add(child) 這樣的方法去管理組件樹。數據溝通方面,通常都采用了從上至下的單向數據傳遞,而子組件則可以通過事件冒泡或是傳遞一個回調的方式來對父組件做出反饋。注意這里的重點是,子組件並不能任意地改寫父組件的狀態,無論是觸發事件還是調用回調,最終父組件發生了什么還是由父組件自身來決定的,這就保證了子組件對父組件的解耦,從而使得子組件可移植/復用。在大型應用里,還要考慮如何讓一個事件的后果能夠被清晰的理解,讓一個開發者可以迅速理解另一個人的代碼的意圖,讓應用不會隨着規模的增長而失控?這則是 Flux Redux 這樣的狀態管理方案試圖解決的問題。其核心在於讓副作用可控,最終目的也是可維護性。
除了以上三點之外,還有額外的一點,那就是 CSS 和組件的關系。理想情況下,一個高內聚的組件應當包含這個組件所需要的 js 邏輯、HTML 結構和 CSS。但是由於之前提到過的 CSS 全局 namespace 的問題,使得跟隨組件的局部 CSS,尤其是可移植性,一直是一個難題。這一點上目前有幾個方案:React 為代表的 CSS in JS,CSS modules,Shadow DOM(依賴瀏覽器實現),以及 Vue/Angular 2 的編譯時局部 CSS/模板改寫。幾個方案各有千秋,但核心是都一定程度上解決了 CSS 全局 namespace 的問題,使得跟隨組件的 CSS 不再影響外部,獲得了可移植性,從而讓組件達成真正的高內聚。而高內聚的組件才可以獨立作為包分發,實現跨項目復用。
5. 測試/代碼規范工具
據某『前輩』自己說,他因為時間不夠,所以是放棄寫測試的。為什么時間不夠呢,因為工具落后,開發效率太低唄。而且用古老的比如 Qunit / YUI test 寫的測試,必須手動打開一個一個瀏覽器測,也沒有代碼覆蓋率信息。現在呢,代碼規范有 ESLint,單元測試有 Karma,集成測試有 CasperJS/Nightwatch,代碼覆蓋率用 istanbul,配置完畢后一行命令自動多瀏覽器測試匯報結果和覆蓋率,如果搭好持續集成環境,甚至不用你自己手動跑,push 到倉庫就行了。這些東西也不需要團隊人人都懂,架構師搭好底子,新人只要用就行了,但是卻可以大幅提高整體的代碼質量。
說了這么多,無非是為了說明,新技術的出現,永遠是因為有對應的問題可以解決才出現的,其核心訴求都是提高開發效率和可維護性,這正是作為工程師應該追求的東西。在研究、配置、推廣任何新技術的過程中,肯定會有成本,你覺得為了解決這些問題不值得付出這些成本,不代表別人也是這樣,更不代表你有資格去蔑視別人為解決問題做出的努力。至於揣測他人是『靠信息不對等闖名聲』,除了小人之心我還能說什么呢?