本文會對目前流行的基於 JavaScript 的 web 跨端融合方案進行總結和分析,目標人群為 web 方向的從業者但是對跨端融合方案瞭解不多的人。

web 跨端融合簡介

在 2015 年 React Native 釋出之前,web 在移動端 APP 上主要透過 WebView 進行承載,其有許多優點,可以快速迭代釋出,不特別受 APP 版本的影響,因此,一些快速發展的業務(包括前期的手機QQ、手機淘寶)大量採用了 WebView 內嵌 H5 頁面的形式來推動業務。

但是這種方式缺點也比較明顯,主要體現在以下兩點:

載入時間較長,包括 WebView 初始化的時間、網路請求的時間。

HTML 頁面在效能上天然不如 Native 頁面,無論怎麼進行效能最佳化。

在 2015 年,Facebook 推出了 React Native,從而打開了 web 跨端融合的大門,後續在此架構基礎上又出現了阿里巴巴的 Weex(2016)、騰訊的小程式(小程式實際上更偏 web 一點,和其他幾類稍有不同,本文不作介紹)、 Hippy(2018)、Taro(Taro 其實更偏向解釋翻譯,和其他幾類定位不同)等跨端融合解決方案,並且漸漸被用到越來越多的專案中,目前,跨端融合開發已經是一種比較主流的 web 開發模式,在阿里系應用、騰訊的微信、QQ瀏覽器、手機QQ均已經進行了大規模應用。

基本架構

雖然 web 跨端融合方案眾多,除了上述提到的三種,還有各個公司的更多方案,但是一般來說跨端融合的技術架構都比較相近,我們可以透過下面這一個圖來簡單概括:

web跨端融合方案淺析

接下來,我們逐個進行簡析:

業務程式碼:

即我們寫的 React Native 程式碼、Weex 程式碼,一般來說,我們的業務程式碼需要經過框架工具或者打包工具(例如 webpack 配合 loader)進行打包,從而相容一些 ES Next 的寫法以及一些框架本身不支援的 Web 寫法。

Javascript FrameWork:

這部分主要是針對 Weex、Hippy 來講的,Weex 聲稱支援 Vue、Rax 語法,而 Hippy 聲稱支援 React、Vue 寫法,實際上,對於這些庫而言,並不是直接將 React、Vue 引入到專案中,而是會對其原始碼進行修改(Vue 有針對 Weex 平臺的版本),而 Hippy 也是對 React 原始碼進行了修改,例如,你寫的一個

createElement

的操作,在 Web 平臺中實際呼叫的是

document。createElement(tagName)

這個介面;而在 Weex 平臺中實際執行的是

new renderer。Element(tagName)

(renderer 由 Javascript Runtime 提供,並且最終和 Native 通訊渲染上屏)。

Javascript Runtime:

Runtime 的部分,主要是對外暴露了一些統一的介面,比如說節點的增刪改查、網路請求的介面等,而這些藉口,實際上是其“代理”的客戶端的能力,透過客戶端 JSAPI 的方式進行呼叫。另外,把 Runtime 和 FrameWork 進行抽離,也可以便於一個跨端方案適配多個框架,只需要將不同的 FrameWork 和瀏覽器互動的部分程式碼轉換成 Runtime 提供的標準介面,就可以實現對不同框架的支援。

Core:

這部分主要是對 Javascript 的解釋執行,在 iOS 上一般是 JSCore(系統自帶,給客戶端提供了執行 JavaScript 程式的能力),而安卓上則可以採用 V8、X5 等。

最下層則是分 Android 和 iOS 端去進行渲染。

發展現狀

實際上,React Native 最初提出這種解決方案的時候,市面上並沒有同類的產品,但是由於 React Native 的一些問題和其他原因,各個大公司基本都在實現自己的跨端融合方案,這裡 React Native 的問題主要體現在:

最主要的是協議風險。

React Native 打包出來的 JSBundle 較大,並且預設沒有靈活的分包機制,需要自行解決相關問題。

在部分元件比如 List 元件中,效能較差(據非官方說法,效能並不是 React Native 團隊首要考察因素,但是國內團隊一般都比較重視效能)。

部分事件傳送頻繁導致效能損失、例如列表滾動事件、手勢事件等。

雙端 API 大量沒有對齊(這也和其 slogan 是‘learn once, write everywhere’ 而不是 ‘write once, run everywhere’ 相對應)。

而對於國內的 Weex 和 Hippy 框架,其都做了大量的效能最佳化解決了上述問題,並且規避了協議風險(Weex 採用了 Apache 2。0 協議,而 Hippy 即將開源)。

另外值得一提的是,Weex 和 Hippy 都可以在 web 端進行執行,一般可以作為降級方案使用,從而真正做到了“一份程式碼”,三端執行。

效能最佳化

實際上,採用目前的跨端融合方案的體驗已經比採用 WebView 的方案強太多了,但是效能最佳化是沒有止境的,隨著頁面複雜度的提高以及使用者體驗的要求,實際上目前這類跨端融合方案採用了以下幾個方向的效能和使用者體驗最佳化:

減少網路請求

在我們上述提供的架構圖中,一般而言對於一個這類頁面,業務程式碼是透過網路請求載入的,這個時候在載入上主要省去的是 WebView 的初始化時間,這其實是不夠的,所以我們也可以採用將業務程式碼提前下發並存在使用者本地,開啟的時候只需要從本地拉取並執行程式碼,這樣可以減少相關的網路請求阻塞,最佳化載入時間。

另外,減少網路請求還體現在對資源的快取上,對一個頁面中所採用的圖片等資原始檔進行 LRU 策略的快取,從而防止重複的請求(在傳統的 WebView 的方案上,也可以採用對 WebView 增加 Hook 的方式實現)。

當然,以上兩點在 WebView 的方案上也可以採用。

降低通訊成本

我們從上文的架構圖中可以看出,這裡的層級實際上比較多,如果不同層級的通訊資料較多,並且有比較頻繁甚至重複的編解碼操作,肯定會有很大的開銷,從而影響效能,所以,在不同層級之間做好資料的傳遞,並且防止重複的編解碼操作是比較重要的。

這裡可以最佳化的細節其實比較多,我們舉一個 Hippy 的例子:

在 Hippy 架構中,jsRuntime 會生成一個 jsObject 物件樹(即需要渲染的 DOM 資訊),其在經過 JSBridge 時需要透過

JSON。stringify

進行序列化,而在 Java(andriod) 接收端,則需要先將其變成一個 JsonObject,最終轉化成 HippyMap,這裡實際上是有重複的編解碼操作的,我們看看 Hippy 的最佳化策略:

web跨端融合方案淺析

圖片來自 IMWeb 2018

透過 hippybuffer 的方式減少通訊的資料量,並且防止重複的編解碼操作,可以有效提高效能。

減少通訊次數

為了減少在通訊方面的消耗,我們除了降低通訊的成本,還可以做的就是減少通訊次數,當然,前提是不影響使用者體驗。

這方面可以減少的通訊消耗,其中一個方面是頻繁的事件通訊,我們知道,事件的觸發是在 native 端的,但是事件處理的邏輯程式碼實際上是在 js 層來完成的,在這方面的通訊,React Native 就因為頻繁的通訊從而影響了效能。

我們可以最佳化的地方在於,首先減少沒有繫結回撥函式的事件通訊,一般而言這部分通訊是不必要的,其次是多次通訊可以進行合併,比如說 list 滾動回撥函式、以及動畫通訊,我們可以透過配置驅動代替資料驅動的方式(即一次向客戶端傳遞整個配置,後續相同事件可以直接在客戶端進行處理),來減少通訊次數。

這方面 Hippy 和 Weex 都有大量細碎的實踐,在此便不具體介紹了。

降低首屏時間

在原來的 WebView 頁面中,我們為了增強使用者體驗,防止使用者進來之後看到白屏,可以採用服務端渲染的方式,將渲染好的頁面返回給客戶端,同時優化了首屏請求,也防止了客戶端裝置較差造成JS執行時間較長的情況。

在跨端融合方案中我們仍然有類似的解決方案,在不考慮離線包的情況下(即只考慮業務程式碼從遠端載入的情況),我們也可以由服務端渲染好再返回,Weex 便採用了類似的方案,不過其做的更加徹底,在服務端將程式碼結果編譯成 AST 樹並轉化成位元組碼(OPcode),在客戶端解析後直接生成虛擬 DOM:

web跨端融合方案淺析

圖片來自 IMWeb 2018

客戶端級別的其他最佳化

客戶端的最佳化有一部分是本來客戶端開發就會面臨的內容,也有一部分是和混合方案有關的最佳化,比如 Flex Render 的最佳化,不過這方面的內容一般而言和前端關係不是非常密切,筆者作為初級前端工程師,對這方面的內容還並不熟悉。

框架選型

本文的最後一部分,介紹框架選型。

對於各類跨端融合的方案,其相對於 WebView 都有非常大的效能提升,因此在前期,無論選擇什麼框架都能夠看到成效,這裡也並不進行特定的框架選型推薦,但是一般認為,如果是從 Vue 的專案切換,Weex 會更合適一點,而如果從 React 專案切換,在確保沒有證書風險的情況下可以採用 React Native,否則可以嘗試原生支援 React 的 Hippy。

以上。