本文會對目前流行的基於 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 跨端融合方案眾多,除了上述提到的三種,還有各個公司的更多方案,但是一般來說跨端融合的技術架構都比較相近,我們可以通過下面這一個圖來簡單概括:

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

  • 業務代碼:即我們寫的 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 的優化策略:

圖片來自 IMWeb 2018

通過 hippybuffer 的方式減少通信的數據量,並且防止重複的編解碼操作,可以有效提高性能。

減少通信次數

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

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

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

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

降低首屏時間

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

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

圖片來自 IMWeb 2018

客戶端級別的其他優化

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

框架選型

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

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

以上。


推薦閱讀:
相关文章