移動端開發者眼中的前端開發流程變遷與先後端分離

每日一篇優秀博文 2017年10月9日 週一

移動端開發者眼中的前端開發流程變遷與先後端分離

寫在最開始

這是一篇面向移動端開發者的科普性文章,從前端開發的最初流程開始,結合示範代碼,討論開發流程的演變過程,但願能覆蓋一部分前端開發技術棧,從而對前端開發的相關概念造成初步的認識。javascript

本文會提供一些示範代碼,然而他們沒法運行,也不須要徹底看懂,更多的是方便讀者對相關概念和方案有更加具體形象的感覺和更清晰的理解。html

在寫做過程當中,我閱讀學習了淘寶在先後端分離和前端開發技術演變方面的博客,受益不淺,相關文章都羅列在文末的參考資料中。同時因爲自身能力有限,對不少概念的理解比較淺顯,甚至有誤,歡迎交流指正。前端

移動端與前端的區別

在開發 App 的過程當中,咱們不會刻意思考開發流程,由於一切看上去都很是天然。能夠本地肯定的內容就直接寫死,不然異步發起網絡請求並動態的修改,最後把源代碼編譯成可執行的二進制文件供客戶安裝。java

前端開發和移動端開發就有本質的不一樣了。一個網頁的最終展示樣式受到 HTML + CSS 的影響,而 JavaScript 腳本負責用戶交互。一個頁面不會被編譯成可執行文件,它僅僅由幾個文本文件組成,由服務端明文下發給瀏覽器並繪製到屏幕上。node

下文中可能會反覆提到「渲染」的概念,除非特別說明,它不表示解析 HTML 文檔(DOM)並繪製到屏幕上這個過程,由於這一步由瀏覽器內核實現,普通狀況下不須要作過多瞭解和干預。react

網頁能夠分爲靜態、動態兩種,前者就是一個 HTML 文件,後者可能只是一份模板,在請求時動態的計算出數據,最後拼接成 HTML 格式的字符串,這個過程就被稱爲渲染。git

前端與移動端開發另外一個顯著差別是: 雖然能夠在本地調試 HTML,但實際上這些 HTML 的內容須要部署在服務端,這樣才能在用戶發起 HTTP 請求時返回 HTML 格式的文本。github

前端開發的混沌時代

一開始,咱們沒有任何工具,只能靠蠻力。咱們知道 Servlet 是由 Java 編寫的服務端程序,能夠方便的處理 HTTP 請求和返回,所以能夠直接把 HTML 文本當作字符串返回,也就是上文所說的渲染:web

public class HelloWorldServlet extends HttpServlet {
    @Override
    public void doGet(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException {
        resp.setContentType("text/html");

        PrintWriter out = resp.getWriter();
        out.println("<html><head><title>Hello World Sample</title></head>");
        out.println("<body><h1>Hello World Title<h1><h2>" +new Date().toLocaleString() + "</h2></body></html>");
        out.flush();
    }
}

理論上來講,咱們已經能夠開始全部前端開發了,但在這混沌初開的年代,想寫出一份可維護的代碼恐怕得用上「洪荒之力」。把 UI 和業務邏輯寫在一塊兒是一種很是強的耦合,不利於項目往後的拓展。也沒法要求每一個人同時會寫 Java 和前端。ajax

後端 MVC

上述方案的問題之一在於邏輯混亂不清,移動開發者在入門階段大多也經歷過,解決方案比較簡單:使用 MVC,把業務邏輯抽離到 Controller 中,讓 View 層專一於顯示 UI。

MVC 方案實現

在前端開發領域,也有相似的技術,好比 JSP,它通過編譯後變成 Servlet。在寫 JSP 的時候,咱們更加關心頁面樣式,所以代碼看起來就像是 HTML,不過在 <% %> 代碼塊中能夠調用 Java 函數:

<HTML>
<HEAD>
<TITLE>JSP測試頁面---HelloWorld!</TITLE>
</HEAD>
<BODY>
<%
    out.println("<h1>Hello World!</h1>");
%>
</BODY>
</HTML>

JSP 至關於 View 層,它從 Model 中獲取數據,說的再具體一點,是使用後端的語言(好比 Java)去訪問 Model 層提供的接口。

Controller 做爲直接和客戶端接觸的模塊,負責解析請求,數據校驗,路由分發,結果返回等等邏輯。路由分發是指根據請求路徑的不一樣,調用不一樣的 Model 和 View 提供服務。

MVC 的缺點與改進

使用了 MVC 架構(好比大名鼎鼎的 Struts)後,彷佛職責變清晰了,前端開發者負責寫 JSP,後端開發者負責寫 Controller 和 Model,然而在實際開發時仍是有諸多問題。

首先業務邏輯依然沒有嚴格區分,若是沒有良好的編碼規範,JSP 中就會混入大量業務邏輯。而一個框架存在的做用應該是讓沒有接受不少培訓的新人也能寫出合格的代碼。此外,前端開發者還須要對後端邏輯有大體的瞭解,熟悉後端編程語言,所以也存在不少溝通、學習成本。

前端只寫 Demo

一種解決方案是前端開發者只寫 Demo,也就是提供靜態的 HTML 效果給後端開發者,由後端開發者去完成視圖層(好比 JSP)的開發。這樣前端開發者就徹底不須要了解後端知識了。

惋惜這種想法很好,可是一旦付諸實現就會遇到很多問題。首前後端開發者依賴於前端的 Demo,只有看到 HTML 文件他們才能夠開始實現 View 層。而前端又依賴於後端開發者完成總體的開發,才能經過網絡訪問來檢查最終的效果,不然他們沒法獲取真實的數據。

更糟糕的是,一旦需求發生變更,上述流程還須要從新走一遍,先後端的交流依舊沒法避免。概況來講就是先後端對接成本過高。

舉個例子,在開發 App 的時候,你的同事給你發來一段代碼,其中是一個本地寫死的視圖,而後告訴你:「這個按鈕的文字要從數據庫,那個圖片的內容要經過網絡請求獲取,你把代碼改造一下吧。」。因而你花了半天時間改好了代碼,PM 跑來告訴你按鈕文字寫死就好,可是背景顏色要從數據庫讀取,另外,再加一個按鈕吧。WTF?

顯然這種開發流程效率極低,難以接受。

HTML 模板

其實必定程度上來講,JSP 能夠看作 HTML 模板的雛形。HTML 大致上肩負了兩個任務: 頁面框架和內容描述。所謂的 HTML 模板是指利用一種結構化的語法,表示出 HTML 的框架,同時把具體數據抽離出來。

好比 <p>111</p> 表示一個段落,其中內容爲 「111」。若是用模板來表示,能夠寫做 <p>Content</p> 或者 p Content 等等。總之,不要糾結於具體語法,咱們只要知道:

數據 + 模板 = HTML 源碼

好比在 Controller 層,能夠這樣調用:

// 這裏沒有指定模板的名稱,是由於使用了依賴倒置的思想,在配置文件中綁定了 Controller 對應的 View
return res.view({title: "111"});

模板中的代碼以下:

<h1><%= Content%></h1>

熟悉前端開發的讀者可能會發現,這其實採用了 Sails.js + EJS 開發。前者是基於 JavaScript 的服務端應用程序,後者是基於 HTML 語法的模板,另外一種風格的模板是 Jade,不過本文的目的並非重點介紹這些工具如何使用,就再也不贅述了。

回到模板的概念上來,它相對於 JSP 的優點在於,利用工具強行禁止前端開發者在視圖層寫業務邏輯。前端開發者只須要關心 UI 實現並肯定 HTML 中的變量。然後端開發者只要傳入參數便可獲取 HTML 格式的字符串。

模板開發的另外一個好處是先後端能夠同步開發。雙方約定一個數據格式,前端就能夠模擬出假數據並用來自測,後端也能夠用生成的數據與假數據對比進行測試。同時,這個約定的數據格式扮演了契約和文檔的做用,規範了雙方的數據交流形式,從而節省交流的時間成本。關於更多 Mock Server 的話題,請參考 這個鏈接

後端 MVC 架構總結

使用後端 MVC 架構加上模板開發是當前比較主流的一種開發模型,但它也不是完美的。因爲模板由前端開發者完成,因此要求前端開發者對後端環境(注意這裏不是實現細節)有所瞭解。

舉個簡單例子,大型應用的後端要分不少文件夾,這就要求前端對代碼組織結構有所瞭解,上傳文件時須要掌握 ssh、vim 而且依賴於服務端環境。

總的來講,採用服務端 MVC 方案時,HTML 在後端渲染,總體開發流程也所有基於後端環境。所以前端工程師不可避免的須要依賴於後端(雖然使用模板後狀況已經大大改善)。

AJAX 與前端 MVC

AJAX 的全稱是 Asynchronous Javascript And XML,即 「異步 JavaScript 和 XML」。它並不是一個框架,而是一種編程思想。它利用 JavaScript 異步發起請求,結果以 XML 格式返回,隨後 JavaScript 能夠根據返回結果局部操做 DOM。

AJAX 最大的優勢是不用從新加載所有數據,而是隻要獲取改動的內容便可。這在移動端編程中看上去是天經地義的,而前端開發則須要專門使用 AJAX 來實現,默認狀況下網頁的任何一處微小改動都須要從新加載整個網頁。

類比移動應用就會發現,AJAX 適合作單頁面更新,可是不擅長頁面跳轉,就像你的 app 頁面跳轉都是新建一個 UIViewController/Activity 而不是直接把當前頁面的內容所有換掉。

得益於 AJAX 的提出,HTML 在前端渲染變成了可能。咱們能夠下載一個空殼 HTML 文件和一個 JavaScript 腳本,而後在 JavaScript 腳本中獲取數據,爲 DOM 添加節點。

這時候就出現了不少前端的 MVC 框架,好比 Backbone.js,AngularJS(姑且認爲MVVM 是 MVC 的變種) 等一堆名詞,你能夠從 這裏 找到他們各自的 Demo。以我相對熟悉的 React 爲例:

<!DOCTYPE html>
<html>

<head>
  <script src="../build/react.js"></script>
  <script src="../build/react-dom.js"></script>
  <script src="../build/browser.min.js"></script>
</head>

<body>
  <div id="example"></div>
  <script type="text/babel">
    var LikeButton = React.createClass({ 
      getInitialState: function() { 
        return {liked: false}; 
      }, 
      handleClick: function(event) { 
        this.setState({liked: !this.state.liked}); 
      }, 
      render: function() { 
        var text = this.state.liked ? 'like' : 'haven\'t liked'; 
        return (
          <p onClick={this.handleClick}>
            You {text} this. Click to toggle.
          </p>
        ); 
      } 
    }); 
    ReactDOM.render(
      <LikeButton />, 
      document.getElementById('example') 
    );
  </script>
</body>

</html>

這段代碼不用徹底讀懂,你只要意識到,引入 React.js 這個框架後,咱們就能夠脫離 HTML 編程了。全部的邏輯都寫在 <script> 標籤塊中的 JavaScript 代碼中。

咱們還建立了一個 LikeButton 組件,它能夠擁有本身的方法,管理本身的狀態等等。這裏舉 React 的例子可能略有不妥,由於它其實只是一個 View 層的實現方案,還須要配合 Flux 或 Redux 架構。不過也足以感覺一下純 JavaScript 開發的感受了。

這種開發模式和移動端開發很是相似,使用 JavaScript 調用 DOM 接口來繪製視圖,使用 JavaScript 來實現業務邏輯,處理數據,發起網絡請求等。你徹底能夠理解爲單純用 JavaScript 在瀏覽器上開發移動應用,只不過用戶下載的是 JavaScript 腳本而非傳統靜態語言編譯後的二進制文件。

使用了前端 MVC 框架後,對於單頁應用(Single Page Application)來講,先後端各司其職,惟一的聯繫就變成了 API 調用,前端開發者再也不依賴後端環境,HTML 和 JavaScript 均可以放在 CDN 服務器上。這就是咱們常說的 先後端分離 的基本概念,即前端負責展示,後端負責數據輸出。

先後端分離的缺點

然而在我看來,上述先後端分離方案遠遠遜色於後端 MVC + 模板的開發流程,這是由於在實際開發中,純 SPA 的場景並很少見,即便移動端也不是隻有一個視圖,而是有不少頁面跳轉邏輯,更況且處處都是超連接的網頁呢?

咱們來看看在 SPA 和多頁面跳轉並存的狀況下,採用前端 MVC 框架進行先後端分離存在哪些不足。

雙端 MVC 不統一

前端的 MVC 主要處理單個頁面內的邏輯,然後端 MVC 框架處理的是整個 Web 服務器的邏輯。借用 jsconf 大會 上 赫門 的圖片來表示:


先後端 MVC 架構示意圖

因爲先後端 MVC 框架關注的重點不一樣,它們的地位天然也不一樣。前端的 MVC 框架負責頁面展現,所以它只是後端 MVC 框架的 View 層(或許只是一部分 View)。

這就會致使以下問題:

  1. 前端的 Model 和後端的 Model 結構上高度相似,好比前端用到一個 name 屬性,後端也得在 JavaBean 中定義一個 name。有時候爲了不前端對數據作太多邏輯處理從而致使性能降低,後端可能已經作了一些預處理。這就比如咱們寫 App 時,拿到的 feed 流多是篩選、排序過的,而咱們在移動端還要在 ViewModel 中作一些轉化才能給 View 使用。所以,先後端邏輯上的耦合仍是沒法徹底避免。
  2. 前端的 Controller 負責頁面調度,好比控件的狀態管理和樣式改變等。然後端的 Controller 負責調用服務,用戶鑑權等。二者徹底不等價。
  3. 前端也有路由模塊,它主要負責頁面內控件之間的跳轉,好比 React-Router,然後端路由則是把不一樣的網絡請求分發給指定的 Controller。二者邏輯也沒法統一。

SEO

SEO(搜索引擎優化)是一個移動開發者歷來不考慮,但前端開發者視做生命的問題。搜索引擎的工做原理是訪問每一個網頁,而後分析 HTML 中的標籤和關鍵字並作記錄。

一個純異步的網頁,HTML 幾乎是空殼子,而恰恰關鍵的數據都是動態下發的,這就影響了搜索引擎爬蟲的工做過程,他們會認爲該網頁什麼都沒有,即便記錄下來的也是非關鍵數據。

早些年穀歌推出了 Hash-bang 協議 來彌補 AJAX 對 SEO 形成的負面影響,它的本質是爲爬蟲提供後端渲染的降級處理機制。目前谷歌的爬蟲必定程度上能夠閱讀 JavaScript 代碼並爬取相關數據,但 AJAX 在對爬蟲的支持上終究不如 HTML 文本直接傳輸。

性能不夠

從上文中 React 的示範代碼能夠看出,HTML 文件很是小,很快就被打開。可是頁面的渲染邏輯直到 JavaScript 文件被下載後才能開始執行,這就會致使一段時間的白屏。

在移動網絡上,前端渲染 HTML 的性能固然不如後端渲染,頻繁發送 HTTP 請求也會影響加載體驗,所以依賴於前端渲染的頁面,在性能方面還有很大的提升空間。

集中 Or 分離?

不少年前,JavaScript 和 CSS 並不用單獨寫在外部文件中,而是直接內嵌在 HTML 代碼裏:

<p style="background-color:green"
    onclick="javascript:myFunction()">
    This is a paragraph.
</p>

爲了便於管理和修改,咱們把 CSS 和 JavaScript 分離出來。然而到了 React 中,好像走了回頭路,全部邏輯都放在 JavaScript 中。

個人理解是,這種作法適合組件化,咱們很容易定義出一個組件而且重用。這種思想對於複雜的 SPA 來講或許適用,但對於並不複雜但有多個頁面的網頁來講,就顯得過重了。引入了 React 這樣的框架,以及 MVC 的結構,反而會顯得過分設計,增長代碼量和複雜度。

考慮到以前所說的先後端邏輯不能複用的問題,這就更容易致使性能問題。

Node.js

先後端分離的哲學

至此,咱們已經嘗試事後端 MVC 架構,HTML 模板,前端 MVC 架構等多種方案,但結果老是難以使人滿意,是時候總結緣由了。

咱們在進行上述實踐的過程當中,過分強調物理層上的先後端分離,可是忽略了二者自然就存在必定的耦合。實際上,前端開發者不只關注 View 的實現,還應該負責一部分 Controller 中的邏輯,後端開發者則應該關心數據獲取與處理,以及一些跨終端的業務邏輯。

若是頁面渲染在後端實現,會致使前端開發者依賴後端實現和開發環境,後端開發者被迫熟悉前端邏輯(此時不是調用 API 而是直接生成數據並套用模板,這就要求把獲取的數據轉換成模板須要的數據)。

若是頁面渲染所有放在前端,業務邏輯就會太靠前,從而致使不能複用。這種作法彷佛有些矯枉過正了。此外,上文中也介紹了很多 AJAX 的缺點,就不贅述了。

咱們彷佛陷入了兩難的境地,頁面渲染無論是放在前端仍是後端都不合適。其實這很好理解,頁面渲染涉及數據邏輯和 UI,他們理應分別由先後端開發者分別負責,單獨交給任何一方都顯得不合適。

但若是前端工程師能夠寫後端代碼,問題不就迎刃而解了麼?實際上數據的處理能夠分爲兩個步驟:從數據庫或第三方服務獲取數據,把數據轉化爲 View 可用的形式。前者每每和 C++/Java 服務器相關,後者則和前端模板相關,它的做用更像是 MVVM 架構中的 ViewModel。

Node.js 分層

我在上一篇文章中初步介紹了 Node.js 的定位:「一個用 JavaScript 進行開發的後端應用程序框架」。所以它剛好能夠完美的解決前端不瞭解後端邏輯和代碼的問題。

Node.js 做爲一箇中間層,調用上游 Java 服務器提供的服務,獲取數據。自身負責處理業務邏輯,路由請求,cookie 讀寫,頁面渲染等。前端則負責應用 CSS 樣式和 JavaScript 交互,相似於最先期原始的模型。

借用 玉伯的 Web研發模式演變 中的圖片來講明:


node.js 中間層

這裏 還有一個解釋的很是詳細的表格以供參考。

實戰應用

這不是一篇介紹 Node.js 的博客,我也不熟悉相關框架的應用,舉這個例子是爲了演示 Node.js 是如何作先後端分離的。

我選擇了 Sails.js 框架,項目的代碼在 Github: sails-react-example,這裏簡單的分析一下。

視圖都放在 views 目錄下,採用 EJS 爲模板,由 Node.js 負責在服務端渲染:

<div class="container">
<h1><%= __('Comment') %>s for SAILS<small>js</small> + REACT<small>js</small></h1>
</div>
<script src="/js/commentMain.js"></script>
Controllers 負責頁面轉發與模板渲染,具體的服務轉交給 Services 去完成:

module.exports = {
  app : function(req, res) {
    // 若是有必要,在這裏調用 Services 獲取數據
    return res.view({});
  },
};

這個 Demo 中沒有實現 Services,一般它用於和真正的後端進行交互,能夠視狀況選擇 HTTP 或 SOAP,並對返回結果作處理。此外還有 policies/responses 模塊分別對 HTTP 請求和返回內容作處理。

前端的相關代碼都封裝在模板層,可以與 Node.js 無縫接合。

風險控制

雖然咱們用增長 Node.js 處理層的方式解決了先後端分離中的一些痛點,但在實際應用中仍是須要考慮得更加周全。

新增一層後,勢必會致使性能損耗。然而分層本就是一個在衡量得失後作出的權衡,能夠經過各類優化把性能損耗降到最低。何況,在 Node.js 這一層還可使用 BigPipe 來處理多個異步請求。

傳統網頁在加載頁面時,首先獲取 HTML,而後獲取其中引用的 CSS 和 JavaScript。在服務端準備數據和網絡傳輸過程當中,瀏覽器須要一直等待,而 BigPipe 將頁面分紅若干小塊,雖然每一個塊的加載邏輯不變,但塊與塊之間能夠造成流水線做業,避免瀏覽器無心義的等待。

使用 BigPipe 技術在必定場景下能夠代替 Ajax 的多個異步請求。具體介紹能夠參考 BigPipe學習研究

使用 Node.js 後,對前端開發者的技術要求提升了,編碼工做量也會相應的增長。然而這都是工程化的必經之路,編碼量增長的背後實際上是溝通、維護效率的提升。

總結

爲了處理先後端複雜的邏輯,咱們嘗試了使用了後端 MVC 框架來分離業務,用 HTML 模板來分離數據和 UI 樣式。使用了 Ajax 技術的網頁更適合單頁應用,雖然作到了物理層的分離,但在處理多頁面時仍是有很多問題。

實際上頁面渲染本就是先後端共同關心的話題,咱們更應該根據業務邏輯進行先後端分離。最終選擇了 Node.js,藉助它使用 JavaScript 的特性,由前端工程師負責獲取數據後的處理和模板的使用,分擔了一部分本來邏輯上屬於前端,但技術上偏向後端的任務。這種開發模式看上去像是一種倒退,實際上是螺旋式的上升與返璞歸真。

Node.js 基於事件和異步 I/O 的特性,以及優秀的處理高併發的能力很是適合先後端分離的場景,使用 BigPipe 等技術也足以將分層帶來的損耗降到最低。選擇 Node.js 作先後端分離並不必定最佳實踐,但在目前看來有不錯的應用,同時也須要一邊探索一邊前進。

相關文章
相關標籤/搜索