我們來分析一下究竟哪些因素讓前端開發這么困擾。
先看看界面部分吧。
#1. 命令式還是聲明式
毫無疑問,就寫界面來說,聲明式的代碼編寫效率遠高於命令式:
<Panel title="Test"> <Button label="Click me"/> </Panel> Panel p = new Panel(); p.title = "Test"; Button b = new Button(); b.label = "Click me"; p.add(b);
第一種容易寫,容易理解。
#2. 控件標簽集
不管你的軟件面向什么行業,至少都要一些控件,或者是基本的表單輸入,或者是復雜的比如樹形表格,里面還可以跨行跨列渲染的。
如果我們有一套映射到控件的標簽,那么寫代碼是肯定會簡單很多的,比如說,在HTML里面沒有原生的Panel,那么,剛才第一段代碼可能就要變成:
<div class="panel panel-default"> <div class="panel-heading"> <h3 class="panel-title">Simple HTML Loader</h3> </div> <div class="panel-body"> <button>Click me</button> </div> </div>
我們為了使得界面代碼編寫更高效,毫無疑問會傾向於把這么一堆東西簡化成一個Panel標簽,這樣就會逐步建立一套面向自己行業的標簽集。
#3. 帶邏輯的控件
剛才這個例子為什么簡單呢,因為它只是一個普通容器,靜態的,不帶邏輯,所以即使你用什么靜態模板也能解決問題。如果復雜一點,是一個TabNavigator,就要考慮切換的事件,再復雜一些是個樹形表格,那就更麻煩了。
我們來看jQuery提供的插件方式實現TabNaviator:
<div id="tabs"> <ul> <li><a href="#tabs-1">Nunc tincidunt</a></li> <li><a href="#tabs-2">Proin dolor</a></li> <li><a href="#tabs-3">Aenean lacinia</a></li> </ul> <div id="tabs-1"> </div> <div id="tabs-2"> </div> <div id="tabs-3"> </div> </div> <script> $(function() { $( "#tabs" ).tabs(); }); </script>
從我個人的角度看,這種代碼很愚蠢。蠢在何處呢?HTML這類聲明式的界面描述語言,寫起來本來應當直觀一些的,但是被這么一搞,又往命令式的方向去了。而且兩種東西混雜,聲明和渲染居然分了兩處,又增加了維護的成本。
難道就沒有別的辦法來解決這個問題嗎?
我們看看其他語言和框架,比如Flex和Silverlight。
<mx:TabNavigator id="tn" width="100%" height="100%"> <!-- Define each panel using a VBox container. --> <mx:VBox label="Panel 1"> <mx:Label text="TabNavigator container panel 1"/> </mx:VBox> <mx:VBox label="Panel 2"> <mx:Label text="TabNavigator container panel 2"/> </mx:VBox> <mx:VBox label="Panel 3"> <mx:Label text="TabNavigator container panel 3"/> </mx:VBox> </mx:TabNavigator>
上面這段是Flex里面的TabNavigator,在這個鏈接底部有運行結果:TabNavigator
為什么它可以看不到邏輯的代碼,但是又確實能有動作呢,因為它的實現類是mx.containers.TabNavigator,在這個代碼里,可以自己手動去處理一切內部實現,但是暴露給業務開發人員的就是這么簡單的標簽。
我們看看在HTML和JS這個體系里用什么辦法去解決。不要提JSF這類服務端技術,因為它的思路也是不好的,展示代碼的生成和渲染都不在一個地方,會有很多問題。
#4. Polymer與Angular
早期IE里有HTC,也就是HTML Components,因為別的瀏覽器廠商不喜歡,所以快要消亡了。在W3C新的HTML規范里,有一個Web Components,參見這里:Introduction to Web Components
這個東西跟HTC的思想本出同源,它引入了Custom Elements和Shadow DOM這兩個概念,也就是說,我可以自定義一個標簽,然后在內部隨便怎么折騰,用這個標簽的人可以很方便。
很美好,是不是,但是只適用於比較新的瀏覽器,基於這個理念架構的框架Polymer的目標也只是支持一些比較新的瀏覽器。Polymer
那么怎么辦呢?我們還有Angular,它也可以自定義標簽,然后用directive的方式寫內部實現。
<tabs> <pane title="Localization"> </pane> <pane title="Pluralization"> </pane> </tabs>
<script id="components.js"> angular.module('components', []) .directive('tabs', function() { return { restrict: 'E', transclude: true, scope: {}, controller: function($scope, $element) { var panes = $scope.panes = []; $scope.select = function(pane) { angular.forEach(panes, function(pane) { pane.selected = false; }); pane.selected = true; } this.addPane = function(pane) { if (panes.length == 0) $scope.select(pane); panes.push(pane); } }, template: '<div class="tabbable">' + '<ul class="nav nav-tabs">' + '<li ng-repeat="pane in panes" ng-class="{active:pane.selected}">'+ '<a href="" ng-click="select(pane)">{{pane.title}}</a>' + '</li>' + '</ul>' + '<div class="tab-content" ng-transclude></div>' + '</div>', replace: true }; }) .directive('pane', function() { return { require: '^tabs', restrict: 'E', transclude: true, scope: { title: '@' }, link: function(scope, element, attrs, tabsCtrl) { tabsCtrl.addPane(scope); }, template: '<div class="tab-pane" ng-class="{active: selected}" ng-transclude>' + '</div>', replace: true }; }) </script>
這么一來,也就有些接近我們的目標了,看到現在,我們還記得目標是什么嗎?是盡可能精簡的面向領域的容器和控件標簽集,有了這個,寫界面代碼才能更簡單。
#5. 為什么HTML默認標簽集這么小
事情結束了嗎?沒有呢。我們的HTML體系為什么標簽集這么小?因為他要解決的是通用領域的東西,怎樣才能通用呢?要的是盡可能無歧義。
怎樣的東西會沒有歧義?那就是它的含義盡可能少,比如說單行文本輸入框,總沒人對它有歧義吧,它無非就是可以設置最大最小長度,是否只讀,是否禁用,最多通過某種規則來限制輸入字符,最多最多,也就這些可做的了,大家都認同。
Button就不同了,一開始他是<input type="button" value="Click"/>,后來大家想要各種各樣的button,於是開放了<button></button>這樣的標簽,可以在里面寫各種HTML,我記得當時很多人在中間加上下和左右兩層marquee,簡直玩壞了。
現在HTML里面又有了數字輸入,日期時間輸入這樣的東西,數字的沒什么疑問,就是最大最小值,步進值等等,日期時間這個就復雜了,它怎么做,都有人不滿意。有人要日期排左邊,有人要時間排上面,有人只要年和月,有人只要分和秒。有人要點空白表示選中,有人要雙擊日期表示選中,還有人想用農歷、波斯歷、尼泊爾歷,簡直沒完了,還不如不做,誰要誰自己做……
所以,面向各領域的人們,自己動手,豐衣足食吧。
#6. 界面修飾
好了,控件集的問題解決了,我們來看看界面的修飾。
你們發現沒有,不管用什么非HTML的標簽體系,可能寫代碼會很快,但是有時候要修飾界面,比如只是調整一下所有容器的邊距,某些按鈕的圓角之類,就會生不如死。
這時候你會發現,HTML里面的CSS真是神器,什么都能干,而且是面向切面的,只要你的HTML結構是良好的,完全不需要調整這個層面的代碼。為什么其他體系的CSS沒有這么強呢?比如說Flex也可以寫CSS,QT也可以寫CSS。
因為CSS的部分實在是太復雜了,復雜到整個瀏覽器里面絕大部分的代碼都在處理這方面的東西,像Google的Chrome團隊有1000多人,別的體系沒法有這么大投入,只能看着羡慕。
上次看到一個問題,近30年來軟件開發體系有哪些本質的改進?我覺得CSS真的可以入選,這是一個把結構和展現完全分離的典范,並且實現得很好。
我們的前端開發一般都是面向某個領域的,不管什么領域,CSS方向都可以有一個很獨立的規划,因為它可以不影響界面的結構。所以這個方面,其實不太會對前端開發造成太多壓力,壓力只集中在維護CSS的人群身上。
好了,上面扯了那么多,其實到現在還在界面的層次,一直沒有去談到真正的邏輯。那么,最讓我們困擾的部分是哪里呢?
#7. 模塊化和加載
Web前端開發有個最苦悶的事情就是選型,因為HTML這個體系很開放,提供的默認能力又不是很足夠,如果要做復雜交互的東西,會需要很多額外的工作。有各種框架從各種角度來解決問題,但怎么把這些東西整合到正好符合自己的需要,是一個很花精力的事情,很多時候恨不得自己把全部輪子都造一遍。
真正的開發工作中,跨瀏覽器,踩各種坑應該是最煩悶的事,其他部分,如果有做好自己領域里標簽的定義,或者不用標簽用其他方式,應該不算特別困難。
有人說JavaScript語言本身比較松散,所以寫業務邏輯比較頭疼,這不算大問題。基於B/S的開發,有一個大坑是你在運行的時候要先把代碼加載過來,然后才能跑。你看那些C/S軟件,有這困擾嗎?再看看后端程序員,誰還要關心自己的代碼執行之前要做的事情?
所以后端程序員寫前端代碼,都情不自禁地會引入一大堆庫。我們形象一點來描述一下這個過程:
嗯,大家都用jQuery,我也引入,抄了兩段代碼發現真不錯。咦,我要個樹控件,網上逛了一圈,拿了個zTree回來。再埋頭苦干半個小時,缺數據表格控件,於是過了一會,jQuery UI被整體引入了。再埋頭苦干,上網亂點了點,瀏覽器跳出個廣告,一看叫做Kendo UI,看看發現不錯,引進來再說,用里面的某個控件。又過了一陣,聽說最近Angular很火啊,看了看例子,表單功能怎么那么強,我也要用!搗鼓搗鼓又加進去了。項目里又要用圖表庫,看了半天眼睛都花了,百度的ECharts不錯哦,引進來。哎呀我界面怎么那么丑,人家的怎么那么清爽,查看源碼,一看,Bootstrap,去官網一看,真乃神器,不用簡直對不起自己。
沒多久之后,這個界面已經融合了各種主流框架,代碼寫法五花八門,依賴了幾M的JS庫,更要命的是里面某些JS有沖突,某些樣式也互相覆蓋,快瘋了。
這里有哪些問題呢?
- JS代碼要先加載到界面才能執行,而這么幾M的代碼加載過來就要好久了,然后每個框架還要把自己初始化,又耗不少時間,半分鍾之后自己寫的JS才開始執行,用戶等得都快懷孕了。
- 不管是JS還是CSS,都應當控制基准的代碼,這件事的主要意義是避免沖突,因為整個體系都比較松散,如果不加控制,就會造成沖突。即使在服務端寫Java,也有類簽名一致性之類的問題,所以這個部分必須要重視。
剛才這兩點,第二點暫時不是我們要探討的范圍,第一點,引出的話題就是異步加載,這是一個可以展開說很多的話題,也不再說了。異步加載和緩存是面對復雜場景必做的優化措施。
但是這個里面規范就有好幾種,具體實現方式就更多了。ES6的module也許可以解決這個問題。harmony:modules [ES Wiki]
#8. 邏輯的分層
網站型和應用型Web程序對分層的需求是不一樣的。網站型的邏輯大部分都在處理UI,而應用型可能有很多業務邏輯,這部分需要更好的組織,以便復用,或者即使我們的目標不包括復用,為了這個代碼的可維護性,也需要有比較好的組織方式。
本質上這些組織方式與傳統的客戶端軟件開發沒什么不同,主要要做的無非就是UI層的隔離,或者模板化,或者別的什么方式。純邏輯的代碼大家都會寫,但這個邏輯怎么跟界面產生關系,這是個問題。
有些框架通過在HTML元素上設置額外屬性,然后啟動的時候讀取,在框架內部做一些相關的事情,比如Angular、Avalon和Knockout。有的框架在視圖層中讓開發人員手動去處理界面,就像未引入框架的那樣,比如Backbone,兩者是各有利弊的。
前面這種,一般功能是會很強大,但是它自身所做的東西必須足夠多,多得幫你做掉絕大部分本來該自己做的事,你才會特別爽。所以,用這類框架來做表單型應用的時候,是會非常舒服的,因為這些需求他做框架的時候能預見,所以比如校驗、聯動、存取之類的都會處理掉。假如你要做一個繪圖類應用,這就麻煩了,不管你是用Canvas還是SVG,它所能幫到的都不多。這時候,后面這類可能反而適合一些。
這些數據分層框架的原理是什么呢?是要做一層表單與數據的對應關系,所以他要檢測數據的變動,比如一個Object,它某個值變更了,要去把對應的界面更改之類。這里面也有很多的坑,可以一步一步踩過來。。。
到現在,我大致可以回答你的問題,什么情況下前端開發會比較輕松呢?
- 針對自己領域的界面標簽庫比較完善,或者易於擴展
- 樣式容易調整,並且獨立於界面元素
- 邏輯模塊化,層次分明,在某種統一規范上存在大量可用庫
咦,我這三點好像在說微軟的WPF體系嗎?