您好,登錄后才能下訂單哦!
這篇文章主要講解了“軟件架構之如何理解前后端分離與前端模塊化”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“軟件架構之如何理解前后端分離與前端模塊化”吧!
在正式說明前后臺架構分離之前,我們來看一下多年之前,傳統軟件開發的架構模式。
還記得零幾年我上大學的時候,在初學 Java Web 開發時,課本上介紹的還是 JSP + Servlet 這種很傳統的架構模式,這時候前端和后端業務邏輯代碼都在一個工程里面,還沒有分離開來,這種開發模式屬于 Model1 模式,雖然實現了邏輯功能和顯示功能的分離,但是由于視圖層和控制層都是由 JSP 頁面實現的,即視圖層和控制層并沒有實現分離。
隨著學習的深入以及漸漸流行的企業應用開發,我們漸漸的擯棄這種技術選型,并開始在項目中使用了若干開源框架,常用的框架組合有 Spring +Struts/Spring MVC + Hibernate/Mybatis 等等,由于框架的優越性以及良好的封裝性使得這套開發框架組合迅速成為各個企業開發中的不二之選,這些框架的出現也減少了開發者的重復編碼工作,簡化開發,加快開發進度,降低維護難度,隨之而火熱的是這套技術框架背后的開發模式,即 MVC 開發模式,它是為了克服 Model1 存在的不足而設計的。
MVC 的具體含義是:Model + View + Controller,即模型+視圖+控制器,
Model 模型層: 它常常使用 JavaBean 來編寫,它接受視圖層請求的數據,然后進行相應的業務處理并返回最終的處理結果,它負擔的責任最為核心,并利用 JavaBean 具有的特性實現了代碼的重用和擴展以及給維護帶來了方便。
View 視圖層: 代表和用戶交互的界面,負責數據的采集和展示,通常由 JSP 實現。
Controller 控制層: 控制層是從用戶端接收請求,然后將請求傳遞給模型層并告訴模型層應該調用什么功能模塊來處理該請求,它將協調視圖層和模型層之間的工作,起到中間樞紐的作用,它一般交由 Servlet 來實現。
MVC的工作流程如下圖所示。
同時,項目開發在進行模塊分層時也會劃分為三層:控制層,業務層,持久層。控制層負責接收參數,調用相關業務層,封裝數據,以及路由并將數據渲染到 JSP 頁面,然后在 JSP 頁面中將后臺的數據展現出來,相信大家對這種開發模式都十分熟悉,不管是企業開發或者是個人項目的搭建,這種開發模式都是大家的首選,不過,隨著開發團隊的擴大和項目架構的不斷演進,這套開發模式漸漸有些力不從心。
接下來,我們來分析下這套開發模式的痛點。
首先,JSP 必須要在 Servlet 容器中運行(例如 Tomcat,jetty 等),在請求 JSP 時也需要進行一次編譯過程,最后被譯成 Java 類和 class 文件,這些都會占用 PermGen 空間,同時也需要一個新的類加載器加載,JSP 技術與 Java 語言和 Servlet 有強關聯,在解耦上無法與模板引擎或者純 html 頁面相媲美。其次每次請求 JSP 后得到的響應都是 Servlet 通過輸出流輸出的 html 頁面,效率上也沒有直接使用 html 高。由于 JSP 與 Servlet 容器的強關聯,在項目優化時也無法直接使用 Nginx 作為 JSP 的 web 服務器,性能提升不高。
在這種開發模式下的工作流程通常是:設計人員給出頁面原型設計后,前端工程師只負責將設計圖切成 html 頁面,之后則需要由后端開發工程師來將 html 轉為 JSP 頁面進行邏輯處理和數據展示。在這種工作模式下,人為出錯率較高,后端開發人員任務更重,修改問題時需要雙方協同開發,效率低下,一旦出現問題后,前端開發人員面對的是充滿標簽和表達式的 JSP 頁面,后端人員在面對樣式或者交互的問題時本就造詣不高的前端技術也會捉襟見肘。
在某些緊急情況下也會出現前端人員調試后端代碼,后端開發人員調試前端代碼這些讓人捧腹的現象,分工不明確,且溝通成本大,一旦某些功能需要返工則需要前后端開發人員,這種情況下,對于前后端人員的后期技術成長也不利,后端追求的是高并發、高可用、高性能、安全、架構優化等,前端追求的是模塊化、組件整合、速度流暢、兼容性、用戶體驗等等,但是在 MVC 這種開發模式下顯然會對這些技術人員都有一定的掣肘。
項目初期,為了快速上線應用,選擇使用這種開發模式來進行 Java Web 項目的開發是非常正確的選擇,此時流量不大,用戶量也不高,并不會有非常苛刻的性能要求,但是隨著項目的不斷成長,用戶量和請求壓力也會不斷擴大,對于互聯網項目的性能要求是越來越高,如果此時的前后端模塊依舊耦合在一起是非常不利于后續擴展的。舉例說明一下,為了提高負載能力,我們會選擇做集群來分擔單個應用的壓力,但是模塊的耦合會使得性能的優化空間越來越低,因為單個項目會越來越大,不進行合理的拆分無法做到最好的優化,又或者在發版部署上線的時候,明明只改了后端的代碼,前端也需要重新發布,或者明明只改了部分頁面或者部分樣式,后端代碼也需要一起發布上線,這些都是耦合較嚴重時常見的不良現象,因此原始的前后端耦合在一起的架構模式已經逐漸不能滿足項目的演進方向,需要需找一種解耦的方式替代當前的開發模式。
隨著公司業務的不斷發展,僅僅只有瀏覽器端的 Web 應用已經逐漸顯得有些不夠用了,目前又是移動互聯網急劇增長的時代,手機端的原生 App 應用已經非常成熟,隨著 App 軟件的大量普及越來越多的企業也加入到 App 軟件開發當中來,為了盡可能的搶占商機和提升用戶體驗,你所在的公司可能也不會把所有的開發資源都放在 web 應用上,而是多端應用同時開發,此時公司的業務線可能就是如下的幾種或者其中一部分:
瀏覽器端的 Web 應用、iOS 原生 App、安卓端原生 App、微信小程序等等,可能只是開發其中的一部分產品,但是除了 web 應用能夠使用傳統的 MVC 模式開發外,其他的都無法使用該模式進行開發,像原生 App 或者微信小程序都是通過調用 RESTful api 的方式與后端進行數據交互。
隨著互聯網技術的發展,更多的技術框架被提了出來,其中最革命性的就是前后端分離概念的提出。
何為前后端分離,我認為應該從以下幾個方面來理解。
當業務變得越來越復雜或者產品線越來越多,原有的開發模式已經無法滿足業務需求,當端上的產品越來越多,展現層的變化越來越快、越來越多,此時就應該進行前后端分離分層抽象,簡化數據獲取過程,比如目前比較常用的就是前端人員自行實現跳轉邏輯和頁面交互,后端只負責提供接口數據,二者之間通過調用 RESTful api 的方式來進行數據交互,如下圖所示:
此時就不會出現 HTML 代碼需要轉成 JSP 進行開發的情況,前端項目只負責前端部分,并不會摻雜任何后端代碼,這樣的話代碼不再耦合。同時,前端項目與后端項目也不會再出現耦合嚴重的現象,只要前后端協商和定義好接口規范及數據交互規范,雙方就可以并行開發,互不干擾,業務也不會耦合,兩端只通過接口來進行交互。
在 MVC 模式開發項目時,往往后端過重,“控制權”也比較大,既要負責處理業務邏輯、權限管理等后端操作,也需要處理頁面跳轉等邏輯,在前后端分離的模式中,后端由原來的大包大攬似的獨裁者變成了接口提供者,而前端也不僅僅是原來那樣僅處理小部分業務,頁面跳轉也不再由后端來處理和決定,整個項目的控制權已經由后端過渡至前端來掌控,前端需要處理的更多。
前端項目和后端項目隔離開來、互不干涉,通過接口和數據規范來完成項目功能需求,這也是目前比較流行的一種開發方式。
在前后端分離的架構模式下,后臺負責數據提供,前端負責顯示交互,在這種開發模式下,前端開發人員和后端開發人員分工明確,職責劃分十分清晰,雙方各司其職,不會存在邊界不清晰的地方,并且從業人員也各司其職。
前端開發人員包括 Web 開發人員、原生 App 開發人員,后端開發則是指 Java 開發人員(以 Java 語言為例),不同的開發人員只需要注重自己所負責的項目即可。后端專注于控制層(RESTful API)、服務層 、數據訪問層,前端專注于前端控制層、 視圖層,不會再出現前端人員需要維護部分后端代碼,或者后端開發人員需要去調試樣式等等職責不清和前后端耦合的情況,我們通過兩張項目開發流程簡圖來對比:
此時,開發過程中會存在前后端耦合的情況,如果出現問題前端需要返工、后端也需要返工,開發效率會有所影響。現在,前后端分離后流程簡圖如下:
前后端分離后,服務器端開發人員和前端開發人員各干各的,大家互不干擾,。在設計完成后,Web 端開發人員、App 端開發人員、后端開發人員都可以投入到開發工作當中,能夠做到并行開發,前端開發人員與后端開發人員職責分離,即使出現問題,也是修復各自的問題不會互相影響和耦合,開發效率高且滿足企業對于多產品線的開發需求。
前后端分離后,各端應用可以獨立打包部署,并針對性的對部署方式進行優化,不再是前后端一個統一的工程最終打成一個部署包進行部署。以 Web 應用為例,前端項目部署后,不再依賴于 Servlet 容器,可以使用吞吐量更大的 Nginx 服務器,采用動靜分離的部署方式,既提升了前端的訪問體驗,也減輕了后端服務器的壓力,再進一步優化的話,可以使用頁面緩存、瀏覽器緩存等設置,也可以使用 CDN 等產品提升靜態資源的訪問效率。對于后端服務而言,可以進行集群部署提升服務的響應效率,也可以進一步的進行服務化的拆分等等。前后端分離后的獨立部署維護以及針對性的優化,可以加快整體響應速度和吞吐量。
當我們去了解某個事物的時候,首先我們需要去了解它的歷史,才能更好的把握它的未來。
世界上第一款瀏覽器 NCSAMosaic ,是網景公司(Netscape)在1994年開發出來的,它的初衷是為了方便科研人員查閱資料、文檔(這個時候的文檔大多是圖片形式的)。那個時代的每一個交互,按鈕點擊、表單提交,都需要等待瀏覽器響應很長時間,然后重新下載一個新頁面。
同年 PHP(超文本預處理器) 腳本語言被開發出來,開啟了數據嵌入模板的 MVC 模式,同時期比較類似的做法有以下幾種:
PHP 直接將數據內嵌到 HTML 中。
ASP 的 ASPX,在 HTML 中嵌入 C# 代碼。
Java 的 JSP 直接將數據嵌入到網頁中。
這個時期,瀏覽器的開發者,以后臺開發人員居多,大部分前后端開發是一體的,大致開發流程是:后端收到瀏覽器的請求 ---> 發送靜態頁面 ---> 發送到瀏覽器。即使是有專門的前端開發,也只是用 HTML 寫寫頁面模板、CSS 給頁面排個好看點的版式。在這一時期,前端的作用有限,往往只是切圖仔的角色。
1995年,網景公司的一位叫布蘭登·艾奇的大佬,希望開發出一個類似 Java 的腳本語言,用來提升瀏覽器的展示效果,增強動態交互能力。結果大佬喝著啤酒抽著煙,十來天就把這個腳本語言寫出來了,功能很強大,就是語法一點都不像 Java。這樣就漸漸形成了前端的雛形:HTML 為骨架,CSS 為外貌,JavaScript 為交互。
同時期微軟等一些公司也針對自家瀏覽器開發出了自己的腳本語言。瀏覽器五花八門,雖然有了比較統一的 ECMA 標準,但是瀏覽器先于標準在市場上流行開來,成為了事實標準。導致,現在前端工程師還要在做一些政府古老項目的時候,還要去處理瀏覽器兼容(萬惡的 IE 系列)。
不管怎么說,前端開發也算是能寫點邏輯代碼了,不再是只能畫畫頁面的低端開發了。隨著1998年 AJax 的出現,前端開發從 Web1.0邁向了Web2.0,前端從純內容的靜態展示,發展到了動態網頁,富交互,前端數據處理的新時期。這一時期,比較知名的兩個富交互動態的瀏覽器產品是。
Gmail(2004年)
Google 地圖(2005年)
由于動態交互、數據交互的需求增多,還衍生出了jQuery(2006) 這樣優秀的跨瀏覽器的 js 工具庫,主要用于 DOM 操作,數據交互。有些古老的項目,甚至近幾年開發的大型項目現在還在使用 jQuery,以至于 jQuery 庫現在還在更新,雖然體量上已經遠遠不及 React、Vue 這些優秀的前端庫。
自 2003 以后,前端發展渡過了一段比較平穩的時期,各大瀏覽器廠商除了按部就班的更新自己的瀏覽器產品之外,沒有再作妖搞點其他事情。但是我們程序員們耐不住寂寞啊,工業化推動了信息化的快速到來,瀏覽器呈現的數據量越來越大,網頁動態交互的需求越來越多,JavaScript 通過操作 DOM 的弊端和瓶頸越來越明顯(頻繁的交互操作,導致頁面會很卡頓),僅僅從代碼層面去提升頁面性能,變得越來越難。于是優秀的大佬們又干了點驚天動地的小事兒:
2008 年,谷歌 V8 引擎發布,終結微軟 IE 時代。
2009 年 AngularJS 誕生、Node誕生。
2011 年 ReactJS 誕生。
2014 年 VueJS 誕生。
其中,V8 和 Node.JS 的出現,使前端開發人員可以用熟悉的語法糖編寫后臺系統,為前端提供了使用同一語言的實現全棧開發的機會(JavaScript不再是一個被嘲笑只能寫寫頁面交互的腳本語言)。React、Angular、Vue 等 MVVM 前端框架的出現,使前端實現了項目真正的應用化(SPA單頁面應用),不再依賴后臺開發人員處理頁面路由 Controller,實現頁面跳轉的自我管理。同時也推動了前后端的徹底分離(前端項目獨立部署,不再依賴類似的 template 文件目錄)。
至于為啥 MVVM 框架能提升前端的渲染性能,這里簡單的說一下原理,因為大量的 DOM 操作是性能瓶頸的罪魁禍首,那通過一定的分析比較算法,實現同等效果下的最小 DOM 開銷是可行的。React、Vue 這類框架大都是通過這類思想實現的,具體實現可以去看一下相關資料。前后端分離也導致前端的分工發生了一些變化。
而后端開發更加關注數據服務,前端則負責展示和交互。當然相應的學習成本也越來越大,Node.JS的出現也使得前端前后端一起開發成為可能,好多大公司在 2015 年前后就進行了嘗試,用 Node.JS 作為中間數據轉接層,讓后端更加專注于數據服務和治理。
自 2009 年 5 月 Node.js 發布以來,前端能干的事情越來越多。短短 10 來年的時間,前端便從刀耕火種的年代走向了模塊化、工程化的時代。各種前端框架百家爭鳴,前端贏來了真正屬于自己的時代。
時間回到 2009年,記得那時候還沒有流行前后端分離,很多項目還是混在一起,而那時候的前端開發人員大多數也都是“切圖仔”。前端完成靜態頁面,由服務端同事完成數據的嵌入,也就是所謂的套頁面操作,每當有類似的功能,都會回到之前的頁面去復制粘貼,由于處于不同的頁面,類名需要更換,但是換湯不換藥。
久而久之,重復代碼越來越多,但凡改動一個小的地方,都需要改動很多代碼,顯得極不方便,也不利于大規模的進行工程化開發。雖然市面上也慢慢出現了 Angular、 Avalon 等優秀的前端框架,但是考慮到 SEO 和維護人員并不好招,很多公司還是選擇求穩,用套頁面的形式制作網頁,這對前端的工程化、模塊化是一個不小的阻礙。
不過,隨著 Node 被大力推崇,市面上涌現出大量的構建工具,如 Npm Scripts、Grunt、Gulp、FIS、Webpack、Rollup、Parcel等等。構建工具解放了我們的雙手,幫我們處理一些重復的機械勞動。
舉個簡單的例子:我們用 ES6 寫了一段代碼,需要在瀏覽器執行。但是由于瀏覽器廠商對瀏覽器的更新非常保守,使得很多 ES6 的代碼并不能直接在瀏覽器上運行。這個時候我們總不能手動將 ES6 代碼改成 ES5 的代碼。于是乎就有了下面的轉換。
//編譯前 [1,2,3].map(item => console.log(item)) //編譯后 [1, 2, 3].map(function (item) { return console.log(item); }); //代碼壓縮后 [1,2,3].map(function(a){return console.log(a)});
就是做了上述的操作,才能使得我們在寫前端代碼的時候,使用最新的 ECMAScript 語法,并且盡可能的壓縮代碼的體積,使得瀏覽器加載靜態腳本時能更加快速。
隨著 Ajax 的流行,前端工程師能做的事情就不只是“切圖” 這么簡單,現在前端工程師能做的越來越多,開始出現了明確的分工,并且能夠與服務端工程師進行數據聯調。這里說的傳統模塊化還不是后現代的模塊化,早期的模塊化是不借助任何工具的,純屬由 JavaScript 完成代碼的結構化。在傳統的模塊化中我們主要是將一些能夠復用的代碼抽成公共方法,以便統一維護和管理,比如下面代碼。
function show(id) { document.getElementById(id).setAttribute('style', "display: block") } function hide(id) { document.getElementById(id).setAttribute('style', "display: none") }
然后,我們將這些工具函數封裝到一個 JS 腳本文件里,在需要使用它們的地方進行引入。
但是,這種做法會衍生出兩個很大的問題,一個是全局變量的污染,另一個是人工維護模塊之間的依賴關系會造成代碼的混亂。
<script scr="./utils.js"></script>
例如,當我們的項目有十幾個甚至幾十個人維護的時候,難免會有人在公用組件中添加新的方法,比如 show 這個方法一旦被覆蓋了,使用它的人會得到和預期不同的結果,這樣就造成的全局變量的污染。另一個問題,因為真實項目中的公用腳本之間的依賴關系是比較復雜的,比如 c 腳本依賴 b 腳本,a 腳本依賴 b 腳本,那么我們在引入的時候就要注意必須要這樣引入。
<script scr="c.js"></script> <script scr="b.js"></script> <script scr="a.js"></script>
要這樣引入才能保證 a 腳本的正常運行,否則就會報錯。對于這類問題,我們該如何解決這樣的問題呢?
解決這個問題有兩種,先說說治標不治本的方法,我們通過團隊規范開發文檔,比如說我有個方法,是在購物車模塊中使用的,可以如下書寫。
var shop.cart.utils = { show: function(id) { document.getElementById(id).setAttribute('style', "display: block") }, hide: function(id) { document.getElementById(id).setAttribute('style', "display: none") } }
這樣就能比較有效的避開全局變量的污染,把方法寫到對象里,再通過對象去調用。專業術語上這叫命名空間的規范,但是這樣模塊多了變量名會比較累贅,一寫就是一長串,所以我叫它治標不治本。
還有一種比較專業的方法技術通過立即執行函數完成閉包封裝,為了解決封裝內變量的問題,立即執行函數是個很好的辦法,這也是早期很多開發正在使用的方式,如下所示。
(function() { var Cart = Cart || {}; function show (id) { document.getElementById(id).setAttribute('style', "display: block") } function hide (id) { document.getElementById(id).setAttribute('style', "display: none") } Cart.Util = { show: show, hide: hide } })();
上述代碼,通過一個立即執行函數,給予了模塊的獨立作用域,同時通過全局變量配置了我們的模塊,達到了模塊化的目的。
先來說說 CommonJS 規范,在 Node.JS 發布之后,CommonJS 模塊化規范就被用在了項目開發中,它有幾個概念給大家解釋一下。
每個文件都是一個模塊,它都有屬于自己的作用域,內部定義的變量、函數都是私有的,對外是不可見的;
每個模塊內部的 module 變量代表當前模塊,這個變量是一個對象;
module 的 exports 屬性是對外的接口,加載某個模塊其實就是在加載模塊的 module.exports 屬性;
使用 require 關鍵字加載對應的模塊,require 的基本功能就是讀入并執行一個 JavaScript 文件,然后返回改模塊的 exports 對象,如果沒有的話會報錯的;
下面來看一下示例,我們就將上面提到過的代碼通過 CommonJS 模塊化。
module.exports = { show: function (id) { document.getElementById(id).setAttribute('style', "display: block") }, hide: function (id) { document.getElementById(id).setAttribute('style', "display: none") } } // 也可以輸出單個方法 module.exports.show = function (id) { document.getElementById(id).setAttribute('style', "display: block") } // 引入的方式 var utils = require('./utils') // 使用它 utils.show("body")
除了 CommonJS 規范外,還有幾個現在只能在老項目里才能看到的模塊化模式,比如以 require.js 為代表的 AMD(Asynchronous Module Definition) 規范 和 玉伯團隊寫的 sea.js 為代表的 CMD(Common Module Definition) 規范。
AMD 的特點:是一步加載模塊,但是前提是一開始就要將所有的依賴項加載完全。CMD 的特點是:依賴延遲,在需要的時候才去加載。
首先,我們來看一下如何通過 AMD 規范的 require.js 書寫上述模塊化代碼。
define(['home'], function(){ function show(id) { document.getElementById(id).setAttribute('style', "display: block") } function hide(id) { document.getElementById(id).setAttribute('style', "display: none") } return { show: show, hide: hide }; }); // 加載模塊 require(['utils'], function (cart){ cart.show('body'); });
require.js 定義了一個函數 define,它是全局變量,用來定義模塊,它的語法規范如下:
define(id, dependencies, factory)
id:它是可選參數,用于標識模塊;
dependencies:當前模塊所依賴的模塊名稱數組,如上述模塊依賴 home 模塊,這就解決了之前說的模塊之間依賴關系換亂的問題,通過這個參數可以將前置依賴模塊加載進來;
factory:模塊初始化要執行的函數或對象。
require([dependencies], function(){})
然后,在其他文件中使用 require 進行引入,第一個參數為需要依賴的模塊數組,第二個參數為一個回調函數,當前面的依賴模塊被加載成功之后,回調函數會被執行,加載進來的模塊將會以參數的形式傳入函數內,以便進行其他操作。
感謝各位的閱讀,以上就是“軟件架構之如何理解前后端分離與前端模塊化”的內容了,經過本文的學習后,相信大家對軟件架構之如何理解前后端分離與前端模塊化這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。