前端技術演進(七):前端跨棧技術

姜小抖發表於2019-02-16
這個來自之前做的培訓,刪減了一些業務相關的,參考了很多資料(參考資料列表),謝謝前輩們,麼麼噠 ?

隨著網際網路架構的不斷演進,前端技術框架從後臺輸出頁面到後臺MVC,再到前端MVC、MVP、MVVM,以及到Virtual DOM和MNV*的實現,已經發生了巨大的變化。整體上來看,前端也正在朝著模組化、元件化和高效能Web開發模式化的方向快速發展。除了傳統桌面瀏覽器端Web上的應用,前端技術棧在服務端或移動端上的嘗試和發展也從來沒有停止過,而且形成了一系列成熟的解決方案。前端的技術棧能解決的不只是頁面上的問題,前端工程師的追求也絕不只是頁面上的技術。

跨後端技術

這幾年全棧工程師已成為一個很熱門的關鍵詞,從最早的MEAN技術棧到後端直出,再到現在的前後端同構,前端通過與Node結合的開發模式越來越被開發者認同並在越來越多的專案中得到實踐。前端開發者都熱衷於在Node上開發有以下幾個原因:

  • Node是一個基於事件驅動和無阻塞的伺服器,非常適合處理併發請求,因此構建在Node上的應用服務相比其他技術實現的服務效能表現要好。
  • Node端執行的是JavaScript,對於前端開發者來說學習成本較低,要關注的問題相對來說比前端更純粹些。
  • 作為一名前端工程師確實需要掌握一門後臺語言來輔助自己的技術學習。
  • Node端處理資料渲染的方式能夠解決前端無法解決的問題,這在大型Web應用場景下的優勢就體現出來了,這也是目前Node後端直出或同構的實現方式被開發者廣泛使用的一個重要原因。

Node後端開發

Node.js 是一個基於 Chrome V8 引擎 的 JavaScript 執行時。

有個叫Ryan Dahl的歪果仁,他的工作是用C/C寫高效能Web服務。對於高效能,非同步IO、事件驅動是基本原則,但是用C/C寫就太痛苦了。於是這位仁兄開始設想用高階語言開發Web服務。他評估了很多種高階語言,發現很多語言雖然同時提供了同步IO和非同步IO,但是開發人員一旦用了同步IO,他們就再也懶得寫非同步IO了,所以,最終,Ryan瞄向了JavaScript。在2009年,Ryan正式推出了基於JavaScript語言和V8引擎的開源Web伺服器專案,命名為Node.js。

Node第一次把JavaScript帶入到後端伺服器開發,加上世界上已經有無數的JavaScript開發人員,所以Node一下子就火了起來。Node最大的優勢是藉助JavaScript天生的事件驅動機制加V8高效能引擎,使編寫高效能Web服務輕而易舉。

阻塞和非阻塞

阻塞 是說 Node.js 中其它的 JavaScript 命令必須等到一個非 JavaScript 操作完成之後才可以執行。這是因為當 阻塞 發生時,事件機制無法繼續執行JavaScript。

在 Node.js 中,JavaScript由於 CPU 密集操作而表現不佳。而不是等待非 JavaScript操作 (例如I/O)。這被稱為 阻塞

阻塞 方法執行起來是 同步地 ,但是 非阻塞 方法執行起來是 非同步地 。 使用檔案系統模組讀取一個檔案,同步方法看上去如下:

const fs = require('fs');
const data = fs.readFileSync('/file.md'); // 這裡會阻塞複製程式碼

與之功能等同的 非同步 版本:

const fs = require('fs');
fs.readFile('/file.md', (err, data) => {
  if (err) throw err;
});複製程式碼

在第二個例子中, fs.readFile() 因為是 非阻塞 的,所以 JavaScript 會繼續執行,不會發生阻塞, 這對於高效吞吐來說是絕佳的設計。

在 Node.js 中 JavaScript 的執行是單執行緒的,所以並行與事件輪詢能力(即在完成其它任務之後處理 JavaScript 回撥函式的能力)有關。任何一個企圖以並行的方式執行的程式碼必須讓事件輪詢機制以非 JavaScript 操作來執行,像 I/O 操作。

比如 每個對伺服器的請求消耗 50 毫秒完成,其中的 45 毫秒又是可以通過非同步操作而完成的資料庫操作。選擇 非阻塞 操作可以釋放那 45 毫秒用以處理其它的請求操作。這是在選擇 阻塞非阻塞 方法上的重大區別。

Node.js 中的事件輪詢機制和其它語言相比而言有區別,其它語言一般需要建立執行緒來處理並行任務。

MEAN

Node出現的早期還不像現在一樣擁有很複雜的概念,相關技術和語言的標準還不成熟,Node開發一般用的比較多的方案就是使用Express作為Web框架進行小型的Web站點建設,與之結合的主流技術則以M(Mysql)、E(Express)、 A(Angular)、 N(Node)最為典型,甚至到了今天MEAN技術組合的方式仍在沿用。

image.png | center | 712x530

前端一般使用Angular來管理實現頁面應用,服務端Web框架以Express為主,同時使用免費開源的MongoDB資料庫,這樣就可以很快地構建一個Web應用了。

今天可能不一定再去選擇使用它,因為可以代替實現的成熟方案已經很多了,各類其他前後端框架都可以用來靈活組合作為MEAN的替代選型方案,比如 Vue、React可以替代 Angular,Koa 可以替代 Express,資料庫的選擇也有很多。

Node後端資料渲染

對於前端開發者來說,在大型Web應用開發中,很多時候並不需要完全重新設計整個應用後臺的架構,更多的情況下需要結合Node的能力幫助我們解決前後端分離開發模式下無法解決的問題。我們先來看下通常前後端分離的開發模式下有哪些問題,利用Node 端的服務又是如何幫助我們解決這些問題的:

SPA場景下SEO的問題

通常情況下,SPA應用或前後端分離的開發模式下頁面載入的基本流程是:

  1. 瀏覽器端先載入一個空頁面和JavaScript 指令碼。
  2. 然後非同步請求介面獲取資料。
  3. 渲染頁面資料內容後展示給使用者。

那麼問題來了,搜尋引擎抓取頁面解析該頁面HTML中關鍵字、描述或其他內容時,JavaScript尚未呼叫執行,搜尋引擎獲取到的僅僅是一個空頁面,所以無法獲取頁面上<body>中的具體內容,這就比較影響搜尋引擎收錄頁面的內容排行了。儘管我們會在空頁面的<meta>裡面新增keyword和description的內容,但這肯定是不夠的,因為頁面關鍵性的正文內容描述並沒有被搜尋引擎獲取到。

如果使用Node後端資料渲染(有人稱之為直出或服務端渲染 SSR),在頁面請求時將內容渲染到頁面上輸出,那麼搜尋引擎獲取到的HTML就已經包含頁面完整的內容,頁面也就更容易被檢索到了。

前端頁面渲染展示緩慢的問題

除了SEO問題,在前後端分離的開發模式下頁面在JavaScript執行渲染之前是空白的(或提示使用者載入中)。使用者在看到資料時已經花費的網路等待時間包括:

DOM下載時間 + DOM解析時間 + JavaScript 檔案請求時間 + JavaScript部分執行時間 + 介面請求時間 + DOM渲染時間。

這時使用者看到頁面資料時已經是三次序列網路資源請求之後的事情了。如果使用後端直出來進行資料渲染,首先SEO的問題不復存在,使用者瀏覽器載入完DOM的內容解析後即可立即展示,網路載入的問題也得到解決。其他的邏輯操作(如事件繫結和滾動載入的內容)則可按需、按非同步載入,從而大幅度減少展示頁面內容花費的時間。

一般後臺頁面資料直出的通用架構設計如下:

image.png | center | 600x446

直出層接受前端的路由請求,並在Node端的Controller層非同步請求服務接入層介面,獲得Model資料並進行組裝拼接,然後提取相對應的Node端View模板渲染出HTML輸出給使用者瀏覽器,而不用通過前端JavaScript請求動態資料後渲染。

不僅如此,直出層根據不同的瀏覽器userAgent,也可以提取不同的模板渲染頁面返回給不同的使用者瀏覽器,所以這種實現方式不僅非常適合大型應用服務的實現場景,而且可以方便地實現網站的響應式內容直出。

前後端同構

在前後端分離的開發模式上加入直出層,解決了SEO和資料載入顯示緩慢的問題。可是有兩個新的問題:

  • 前端的開發實現向直出層偏移,不得不在原來的開發模式上做出修改來 適應直出層內容的開發,例如修改後端模板來適應現有的開發模式,結果我們不得不維護兩套不同的前後臺模板或技術實現——前端渲染實現邏輯和後端直出實現邏輯,儘管可能都是用JavaScript寫的。
  • 如果是在移動端Hybrid應用上,離線包機制實現可能就會出現問題。因為每次都是從後端直出HTML結構給前端,這樣就難做到將HTML檔案進行離線快取,而只能進行其他靜態檔案的快取。在Hybrid App的應用場景下,其實我們更希望做到的是移動端首次開啟頁面時使用後端直出內容來解決載入慢和SEO問題,而在有離線快取的情況下則使用客戶端本地快取的靜態檔案拉取資料返回渲染的方式來實現,或者未來在高版本的瀏覽器支援HTTP2的條件下使用前端渲染,低端瀏覽器不支援HTTP2的情況下則使用直出的方式實現。

所以需要一套完善的開發方式,和原有開發方式保持一致,且能夠同時用於前後端分離的開發模式和後端資料渲染模板開發方式中。這種開發模式就是我們所說的前後端同構。

實現同構的核心

前後端同構的宗旨是,只開發一套專案程式碼,既可以用來實現前端的JavaScript 載入渲染,也可以用於後臺的直出渲染。

為什麼可以這樣做呢?和前端渲染資料內容的方式相同,頁面直出層內容也是通過資料加上模板編譯的方式生成的,前端渲染和後臺直出的模式生成DOM結構的區別只在於 資料和模板的渲染髮生在什麼時候。如果使用一套能在前端和後端都編譯資料的模板系統,就可以做到使用同一套開發程式碼在前後端分別進行資料渲染解析。因此前後端同構的核心問題是實現前後臺資料渲染的統一性。

同構的優勢

除了解決前後端開發方式的問題,前後端同構的網站具有一些明顯的優勢:

  • 可以根據使用者的需求方便地選擇使用前端渲染資料還是後臺直出頁面資料;
  • 開發者只需維護一套前端程式碼,而且可以沿用前端原有的專案元件化管理、打包構建方式,根據不同的構建指令生成類似的前後端資料模板或元件在前後端執行解析,所以這對於DOM結構層上的開發方式應該是一致的。

前後端同構的實現原理

基於資料模板的前後端同構方案

早在前端MVC開發的時代,前端模板的使用就非常廣泛,例如Mustache、Handlebar 等,基本原理是將模板描述語法與資料進行拼接生成HTML程式碼字串插入到頁面特定的元素中來完成資料的渲染。同理,後端直出層也可以通過該方法來實現資料的渲染產生HTML字串輸出到頁面上。

如果前後端使用同一個模板解析引擎,那麼我們只需要編寫同一段模板描述語法結構就可以在前端和後端分開進行渲染了。比如同樣的模板:

<div class="entry">
  <h1>{{title}}</h1>
  <div class="body">
    {{body}}
  </div>
</div>複製程式碼

前端,後端拿到資料後解析保持一致:

{
  "title": "Hello",
  "body": "World"
}複製程式碼
<div class="entry">
  <h1>Hello</h1>
  <div class="body">
    World
  </div>
</div>複製程式碼

對於前端開發的同一段模板語法結構,我們既可以選擇在瀏覽器端渲染生成HTML字串輸出,也可以選擇在後端渲染生成HTML字串輸出。如果選擇在前端渲染,則可以將模板進行打包編譯,在資料請求成功後進行DOM渲染;如果選擇後端渲染,就可以將模板資料直接傳送到直出層的View檢視進行渲染,實現同一個模板語法結構在前後端渲染出相同的內容。這裡的前提是要保證前後端使用的模板渲染引擎或者模板解析的語法是一致的。

基於MVVM的前後端同構

MVVM框架頁面上的JavaScript邏輯主要是通過Directive(不只是Directive,還有filter、 表示式等,以Directive為主)來實現的,一般前端頁面載入完成後會開始掃描DOM結構中的Directive指令並進行DOM操作渲染或事件繫結,所以資料的顯示仍然需要頁面執行Directive後才能完成。那麼如果將Directive的操作在直出層實現,瀏覽器直接輸出的頁面就是渲染後的內容資料了。

<div class="entry">
  <h1 x-html="title"></h1>
  <div class="body" x-html="body"></div>
</div>複製程式碼

前端編寫的同一段MVVM的語法結構,通過前端MVVM框架解析或後端Directive 執行解析最終都可以生成相同的HTML結構,不同的是前端執行解析後生成的是ViewModel物件並通過瀏覽器體現,後端渲染則生成HTML標籤的文字字串輸出給瀏覽器。這裡同樣需要做一件事,即在後臺實現一個與前端解析Directive相同的模組,甚至還包括filter、語法表示式等的實現。這樣就可以在前後端完成同一段語法結構的解析了。

基於VirtualDOM的前後端同構

之前說過,VirtualDOM作為一種新的程式設計概念被廣泛應用在實際專案開發中,其核心是使用JavaScript 物件來描述DOM結構。那麼既然Virtual DOM是一個JavaScript物件,就表示其可以同時存在於前後端,通過不同的處理方式來實現同構。

在前端開發的元件中宣告某段VirtualDOM描述語法,然後通過VirtualDOM框架解析生成VirtualDOM,這裡的VirtualDOM既可以用於在瀏覽器端生成前端的DOM結構,也可以在直出層直接轉換成HTML標記的文字字串輸出,後面這種情況就可以在服務端上實現Virtual DOM到HTML文字字串的轉換。這樣,通過對Virtual DOM的不同操作處理,就可以統一前後端渲染機制,實現元件的前後端對同一段描述語法進行渲染。

這裡VirtualDOM上的邏輯實現仍然需要在瀏覽器端進行事件繫結來完成,最好能讓同構框架幫助我們自動完成,根據HTML的結構進行特定的事件繫結處理,保證最後展示給使用者的頁面是完整且帶有互動邏輯的

無論哪一種方式,核心都體現在HTML的結構形式變化上,頁面內容的描述方式有很多,而且可以通過特定的處理過程實現轉化,這樣就提供了更多的可能性。

image.png | center | 390x370

Egg.js

image.png | center | 89x28

Node雖然生態比較火熱,但是至今還沒有一款公認的成熟的企業級框架,主要是因為使用Node來開發大型後端應用的企業還很少。現在主要有兩款:Sails 和 Egg.js。

設計原則

一個外掛只做一件事:Egg 沒有內建很多額外的功能,而是通過外掛的方式來實現,Egg 通過框架聚合這些外掛,並根據自己的業務場景定製配置,這樣應用的開發成本就變得很低。

約定優於配置:按照一套統一的約定進行應用開發,團隊內部採用這種方式可以減少開發人員的學習成本,這也是很多框架的思路。

特點

  • 提供基於 Egg 定製上層框架的能力:可以基於 Egg 去封裝適合團隊的上層框架。
  • 高度可擴充套件的外掛機制:可以促進業務邏輯的複用,生態圈的形成。
  • 內建多程式管理。
  • 基於 Koa 開發,效能優異:支援所有的Koa中介軟體。
  • 框架穩定,測試覆蓋率高。
  • 漸進式開發:可以流暢的實現編碼 --> 編碼抽象成功能 --> 功能抽象成外掛 --> 外掛封裝到框架 的漸進過程。

eggjs.org/zh-cn

現在我們部門的Node專案基本上是 Koa,Egg流。其他部門也有 Express 流。

跨終端技術

移動端

移動網際網路興起後,智慧移動裝置出現,大量應用市場的Native應用也開始湧現。隨著第一波移動端網際網路開發浪潮漸漸平靜,各類Native應用開始進入有序更新迭代的階段。人們對移動網際網路需求急劇增長,Native 應用快速迭代開發的需求也越來越多,但是現有Native應用的開發迭代速度依然無法滿足市場快速變化的需要。隨之而來的是HTML5的出現,它允許開發者在移動裝置上快速開發網頁端應用,並讓移動網際網路應用開發很快進入了Native應用、Web應用、Hybrid 應用並存的時代。

image.png | center | 600x305

在發展過程中,最大限度的利用原生能力成為了一大趨勢。出現了 React Native、Weex 等框架,可以直接使用 Javascript 來編寫原生應用。比如React Native,產出的並不是“網頁應用”, 或者說“HTML5應用”,又或者“混合應用”。 最終產品是一個真正的移動應用,從使用感受上和用Objective-C或Java編寫的應用相比幾乎是無法區分的。

import React, { Component } from 'react';
import { Text, View } from 'react-native';

class WhyReactNativeIsSoGreat extends Component {
  render() {
    return (
      <View>
        <Text>
          如果你喜歡在Web上使用React,那你也肯定會喜歡React Native.
        </Text>
        <Text>
          基本上就是用原生元件比如'View''Text'
          來代替web元件'div''span'。
        </Text>
      </View>
    );
  }
}複製程式碼

image.png | center | 618x544

也就是說即使不懂原生應用的開發,也可以用 Javascript 來編寫原生應用了。

桌面端

現在,也可以使用 JavaScript, HTML 和 CSS 構建跨平臺的桌面應用。通過 Electron 之類的應用,可以直接把 Web 專案打包成桌面應用,執行在各個作業系統中。

著名的 Atom IDE 就是通過 Electron 構建的,其他的包括 VS Code、Skype、Github Desktop 之類的 App 也都是通過 Electron 構建的。

image.png | center | 718x902


相關文章