首頁 > web前端 > js教程 > 一起看 鴻蒙 JavaScript GUI 技術棧

一起看 鴻蒙 JavaScript GUI 技術棧

coldplay.xixi
發布: 2020-09-14 13:28:55
轉載
2797 人瀏覽過
<p>一起看 鴻蒙 JavaScript GUI 技術棧

<p>相關學習推薦:javascript影片教學

<p>#眾所周知,剛剛開源的「鴻蒙2.0」以JavaScript 作為IoT應用開發的框架語言。這標誌著繼 SpaceX 上天之後,JavaScript 再一次蹭到了新聞聯播級的熱點。這麼好的機會,只拿來陰陽怪氣實在太可惜了。身為科普,這篇文章不會拿著放大鏡找出程式碼中的槽點來吹毛求疵,而是希望通俗地講清楚它所支援的 GUI 到底是怎麼一回事。只要對計算機基礎有個大概的了解,應該就不會對本文有閱讀上的障礙。

<p>我們已經知道在「鴻蒙 2.0」上,開發者只需編寫形如 Vue 元件式的 JavaScript 業務邏輯,即可將其渲染為智慧手錶等嵌入式硬體上的 UI 介面。這個過程中需要涉及哪些核心的模組呢?這些模組中又有哪些屬於自研,哪些使用了現成的開源專案呢?這裡將其分為自上而下的三個抽象層來介紹:

  • JS 框架層,可理解為一個大幅簡化的Vue 式JavaScript 框架
  • JS 引擎與運行時層,可理解為一個大幅簡化的WebKit 式運行時
  • 圖形渲染層,可理解為一個大幅簡化的Skia 式圖形繪圖函式庫
<p>這三個抽象層,整體構成了一套針對嵌入式硬體的GUI 技術堆疊。有別於許多高喊「不明覺厲/ 深不可測」的輿論,個人認為至少對於GUI 部分,國內凡是接觸過目前主流Hybrid 式跨端方案或JS 運行時研發的一線開發者,都很容易從源碼出發來理解它。下面逐層對其做一些解讀和分析。

JS 框架層

<p>從最頂層的視角出發,要想用「鴻蒙2.0」渲染出一段動態的文本,你只需要寫如下的HML(類XML)格式程式碼:

<!-- hello.hml --><text onclick="boil">{{hello}}</text>复制代码
登入後複製
<p>然後在同級目錄寫這樣的JavaScript:

// hello.jsexport default {  data: {    hello: 'PPT'
  },
  boil() {    this.hello = '核武器';
  }
}复制代码
登入後複製
<p>這樣只要點擊文本,就會呼叫boil 方法,讓PPT 變成核武

<p>這背後發生了什麼事?熟悉 Vue 2.0 的同學應該會立刻聯想到下面這幾件事:

  • 需要對 XML 的預處理機制,將其轉換為 JS 中的巢狀函數結構。這樣只要在執行時做一次簡單 eval ,即可用 JS 產生符合 XML 結構的 UI。
  • 需要事件機制,使得觸發 onclick 事件時能執行對應回呼。
  • 需要資料劫持機制,使得對 this.hello 賦值時能執行對應回呼。
  • 需要能在回呼中更新 UI 物件控制項。
<p>這幾件事分別是怎麼實現的呢?簡單說來是這樣的:

  • XML 預處理依賴現成的NPM 開源包,從而把XML 中的onclick 屬性轉換為JS 物件的屬性字段。
  • 事件的註冊和觸發都直接由 C 實作。如上一步所獲得的 JS 物件 onclick 屬性會在 C 中被檢查和註冊,相當於全部元件均為原生。
  • 資料劫持機制用 JS 實現,是基於 Object.defineProperty 的(幾百行量級的)ViewModel。
  • UI 控制項的更新,會在 ViewModel 自動執行的 JS 回呼中,呼叫 C 的原生方法實作。這部分完全隱式完成,並未開放 document.createElement 式的標準化 API。
<p>由於大量常見JS 框架中的能力都直接做進了C ,所以整套GUI 技術堆疊裡用純JavaScript 所實現的東西(主要見ace_lite_jsfwk 倉庫下的core/index.jsobserver.jssubject.js),相當於有且只有這麼一個功能:

<p>一個可以watch 的ViewModel。

<p>至於純 JS 框架部分的實現複雜度和質量,客觀地說如果是個人業餘作品,可以當作校招面試中不錯的加分項。

JS 引擎與運行時層

<p>理解了JS 框架層之後,我們既可以認為“鴻蒙2.0”選擇把高度簡化後的Vue 深度定制進了C 裡,也可以認為它緊密圍繞著高度簡化(且私有)的DOM 實現了配套的前端框架。因此要繼續探索這套 GUI 的原理,我們必須進入其 C 部分,了解其 JS 引擎與運行時層的實現。

<p>JS 引擎和運行時之間,有什麼區別與連結呢? JS 引擎一般只需符合 ECMA-262 規範,其中沒有任何帶有「副作用」的平台 API 的定義。從setTimeoutdocument.getElementByIdconsole.log 再到fs.readFile,這些能執行實際IO 操作的功能,都需要由「將引擎API 和平台API 膠合到一起」的運行時提供。運行時本身的原理並不複雜,譬如在個人的文章《從 JS 引擎到 JS 運行時》中,你就可以看到如何借助現成的 QuickJS 引擎,自己搭建一個運行時。

<p>那麼在「鴻蒙 2.0」中,JS 運行時是如何搭建出來的呢?有這麼幾個重點:

  • JS 引擎選擇了 JerryScript,這是一款由三星開發的嵌入式 JS 引擎。
  • 每種形如<text><p> 的XML 標籤元件,都對應一個綁定到JerryScript 上的C Component 類,如TextComponentpComponent 等。
  • 除UI 原生物件外,還有一系列在JS 中以@system 為前綴的built-in 模組,它們提供了JS 中可用的Router / Audio / File 等平台能力(參見ohos_module_config.h)。
<p>這裡特別值得一提的是 Router。它和vue-router 等常見Web 平台路由的實作原理有很大區別,是專門在運行時內深度定制的(參見router_module.cppjs_router.cppjs_page_state_machine.cpp)。簡單說來這個「路由」是這樣實現的:

  • 在 JS 中呼叫切換頁面的 router.replace 原生方法,走進 C 。
  • C 中根據新頁面 URI 路徑(如 pages/detail)載入新頁面 JS,新頁面狀態機實例,將其切換至 Init 狀態。
  • 在新狀態機的 Init 過程中,呼叫 JS 引擎到 eval 新頁面的 JS 程式碼,取得新頁面的 ViewModel。
  • 將路由參數附加到 ViewModel 上,銷毀舊狀態機及其上的 JS 物件。
<p>所以我們可以發現,這裡所謂的「切換路由」,其實更接近 Web 瀏覽器的「刷新頁面」。那我們可以認為這個 JS 運行時的能力,已經可以對標 WebKit 等級的瀏覽器核心了嗎?

<p>當然還差得很遠。與WebKit 相比,它並未支援對HTML 和CSS 的解析(二者都會在開發階段被解析轉換成同等執行效果的JS),也沒有瀏覽器中持續動態載入、解析與執行資源的挑戰(小程式不外乎是幾個本地的靜態JS 檔案)。至於排版佈局和渲染方面自然也有很大差距,這點會在最後一節提及。

<p>另外,相信很多同學都會對 JerryScript 引擎感到好奇。本部分最後分享一些個人對此所掌握的消息。

<p>JerryScript 引擎是一款專為嵌入式硬體實現的 JS 解譯器,只支援到 ES5.1 標準。在QuickJS Benchmark 中,可以查看到它們的效能比較結果:

一起看 鴻蒙 JavaScript GUI 技術棧
<p>可以看到論效能,JerryScript 在無JIT 的引擎中大幅弱於QuickJS 和Hermes。如果和開啟了 JIT 的 V8 相比,甚至會慢出兩個數量級。因此這是非常特定於低階設備的引擎,如果需要支援 React 和 Vue 這類中大型前端專案中標配的基礎庫(甚至是其對應全家桶),仍然可能需要使用更強大的引擎。

<p>對於JerryScript 的使用,有同場景重度應用經驗的當屬RT-Thread 創始人@午夜熊,他們和某國內一線廠商合作研發的智能手錶就用JerryScript 實現了UI,目前產品馬上就要上市了。他們團隊對 JerryScript 的一些使用回饋也吻合上述評價,概括說來是這樣的:

  • JerryScript 在体积和内存占用上,相比 QuickJS 有更好的表现。
  • JerryScript 的稳定性弱于 QuickJS,有一些难以绕过的问题。
  • JerryScript 面对稍大(1M 以上)的 JS 代码库,就有些力不从心了。
<p>那么师出名门的 QuickJS 和 Facebook 的 Hermes,是否就是无 JIT 式 JS 引擎的下一代标杆了吗?倒也未必如此。这方面可以参考个人的知乎回答:随着 TypeScript 继续普及,会不会出现直接跑 TypeScript 的运行时?这里提到的微软为教育项目 MakeCode 研发的 Static TypeScript,就相当有潜力成为下一代的高性能 JS 系语言环境。通过限定 TypeScript 的静态强类型子集并为其搭建工具链,STS 可以做到无需 JIT 也能接近 V8 的性能水平,同时内存占用比 V8 少两个数量级。这使得 STS 不光能用于开发普通 app 这类 IO 密集的应用,还能顺利在嵌入式硬件上开发小游戏这类更偏计算密集(需逐帧更新渲染)的应用,在工程能力上是一项很大的突破。

<p>所以说,当「鸿蒙 2.0」还需要熟练开发者勉强搭建出环境跑通 Hello World 时,微软已经让上百万小朋友都能用 TypeScript 在网页里给教学用的掌上游戏机写小游戏入门编程了。这里没什么唱反调的意思,只希望提醒一下我们在为国产「里程碑」欢呼时,也要清醒地看到业界前沿的动向,仅此而已。

图形绘制层

<p>理解 JS 运行时之后,还剩最后一个问题,即 JS 运行时中的各种 Component 对象,是如何被绘制为手表等设备上的像素的呢?

<p>这就涉及「鸿蒙 2.0」中的另一个 graphic_lite 仓库了。可以认为,这里才是真正执行实际绘制的 GUI。像之前的 TextComponent 等原生组件,都会对应到这里的某种图形库 View。它以一种相当经典的方式,在 C++ 层实现并提供了「Canvas 风格的立即模式 GUI」和「DOM 风格的保留模式 GUI」两套 API 体系(对于立即模式和保留模式 GUI 的区别与联系,可参见个人这篇 IMGUI 科普回答)。概括说来,这个图形子系统的要点大致如下:

  • 图形库提供了 UIView 这个 C++ 控件基类,其中有一系列形如 OnClick / OnLongPress / OnDrag 的虚函数。基本每种 JS 中可用的原生 Component 类,都对应于一种 UIView 的子类。
  • 除了各种定制化 View 之外,它还开放了一系列形如 DrawLine / DrawCurve / DrawText 等命令式的绘制方法。
  • 这个图形库具备名为 GFX 的 GPU 加速模块,但它目前似乎只有象征性的 FillArea 矩形单色填充能力。
<p>在基础 UI 控件方面,不难找到一些值得一提的自研模块特性:

  • 支持了简易的 RecycleView 长列表。
  • 支持了简易的 Flex 布局。
  • 支持了内部的 Invalidate 脏标记更新机制。
<p>至于 2D UI 渲染中的几项关键能力,则基本可分为路径、位图和文字三类。这个图形库在这几个方面都有涉及,最后简单介绍一下。

<p>首先对于位图,这个图形库依赖了 libpnglibjpeg 做图像解码,然后即可使用内存中的 bitmap 图像做绘制。

<p>然后对于路径,这个图形库自己实现了各种 CPU 中的像素绘制方法,典型的例子就是这个贝塞尔曲线的绘制源码:

void DrawCurve::DrawCubicBezier(const Point& start, const Point& control1, const Point& control2, const Point& end,    const Rect& mask, int16_t width, const ColorType& color, OpacityType opacity)
{    if (width == 0 || opacity == OPA_TRANSPARENT) {        return;
    }

    Point prePoint = start;    for (int16_t t = 1; t <= INTERPOLATION_RANGE; t++) {
        Point point;
        point.x = Interpolation::GetBezierInterpolation(t, start.x, control1.x, control2.x, end.x);
        point.y = Interpolation::GetBezierInterpolation(t, start.y, control1.y, control2.y, end.y);        if (prePoint.x == point.x && prePoint.y == point.y) {            continue;
        }

        DrawLine::Draw(prePoint, point, mask, width, color, opacity);
        prePoint = point;
    }
}复制代码
登入後複製
<p>基于高中的数学知识,我们不难明白这种曲线是如何绘制出来的:取足够多的点(也就是那个默认 1000 的 INTERPOLATION_RANGE)作为插值输入,逐点计算出曲线表达式的 XY 坐标,然后直接修改像素位置所在的 framebuffer 内存即可。这种教科书式的实现是最经典的,不过如果要拿它对标 Skia 里的黑魔法,还是不要勉为其难了吧。

<p>最后对于文字的绘制,会涉及一些字体解析、定位、RTL和折行等方面的处理。这部分实际上也是组合使用了一些业界通用的开源基础库来实现的。比如对于「牢」这个字,就可以找到图形库的这么几个开源依赖,它们各自扮演不同的角色:

  • harfbuzz - 用来告诉调用者,应该把「牢」的 glyph 字形放在哪里。
  • freetype - 从宋体、黑体等字体文件中解码出「牢」的 glyph 字形,将其光栅化为像素。
  • icu - 处理 Unicode 中许多奇葩的特殊情况,这块个人不了解,略过。
<p>到这里,我们就可以理出一个非常概括性的渲染流程了:

  • JS 中執行 this.hello = 'PPT' 之類的程式碼,觸發依賴追蹤。
  • JS 依賴追蹤回呼觸發原生函數,更新 C 的 Component 元件狀態。
  • Component 更新其綁定的 UIView 子類別狀態,觸發圖形庫更新。
  • 圖形庫更新記憶體中的像素狀態,完成繪製。
<p>這就是個人對「鴻蒙 2.0」這套 GUI 技術堆疊的解讀了。時間有限並未進一步深挖,歡迎(文明的)批評指正。

總結

<p>特別聲明:本部分主觀評論僅針對「鴻蒙 2.0」目前的 GUI 框架部分,請勿隨意曲解。

<p>對於「鴻蒙 2.0」在 GUI 部分的亮點,個人能想到這些:

  • 確實有務實(但和當年 PPT 介紹完全兩碼事)的程式碼。
  • 不是 WebView 套殼,佈局和繪製是自己做的。
  • 無需超過大學本科程度的電腦知識,也能順利閱讀理解。
<p>而至於明顯(不只是某幾行程式碼寫得醜)的缺失或問題,目前看來則有這麼一些:

  • JS 框架層
    • 沒有基本的元件間通訊(如props / emit 等)能力
    • 沒有基本的自訂元件能力
    • 沒有除基礎依賴追蹤以外的狀態管理能力
  • JS 引擎與執行時間層
    • 標準支援過低,無法運作Vue 3.0 這類需Proxy 的下一代前端框架
    • 效能水準弱,難以支援中大型JS 應用
    • #沒有開放DOM 式的物件模型API,不利於上層抹平差異
  • #圖形渲染層
    • 沒有實質可用的GPU 加速
    • 沒有SVG 和富文本等高階渲染能力
    • Canvas 完成度低,缺狀態堆疊和很多API
<p>看起來槽點很多,但是你會指責汽車沒有噴射引擎嗎?對於不同複雜度的場景,自然存在著不同的最優架構設計。目前看來,這套設計確實很適合嵌入式硬體和簡易「小程式」的場景。但如果按照所謂「分散式全場景跨平台」的要求來審視,那麼不管比起現代的 Web 瀏覽器還是 iOS 和安卓的 GUI,這套架構的複雜度都是完全無法相提並論的。 如果想在手機上實裝,幾乎必定還需要追加大量複雜模組,進行大幅的架構演化與重新設計

<p>當然,汽車廠商也不會說自己造的是飛機,對吧?

<p>總之這確實是一盤自己做的麻婆豆腐,但不是某些人口中的滿漢全席。

<p>最後是個人的主觀評論:

<p>首先,這套 GUI 技術堆疊達到了組裝和借鏡開源產品時所能獲得的主流水平。但論性能與表現力上限,其核心模組距離微軟 MakeCode 這類業界 cutting-edge 級的產學研結合前緣方案,仍有數量級的世代差距。

<p>其次,不必把它當作需要海量專家精密計算的Rocket Science——不是貶低自主研發,而是真心地希望大家能明白,「這件事我也可以實際參與進來!」作業系統和GUI 沒有那麼神秘,已有很多國產的成熟開源產品可供學習、使用與貢獻(這裡順便推薦極易體驗且同為國產的RT-Thread 作為嚐鮮入門之用)。畢竟只有真正搞懂了某個產品在技術上到底是怎麼一回事,才不容易被別有用心的人帶節奏,對吧?

<p>最後,對於所有熟悉 JavaScript 的前端開發者們,你們為什麼還要陰陽怪氣地嘲笑鴻蒙?鴻蒙就是 JavaScript 在中國的財富密碼啊! JavaScript 被鴻蒙這樣的「國之重器」採用,可以大大增強前端的道路自信、理論自信、文化自信和技術堆疊自信。只要以這種形式結合拼接與自研,就可以一舉在全國上下獲得崇高的聲望,這條路真是太讓人心馳神往了呀(小聲)

<p>我們要團結起來,大力弘揚和宣傳JavaScript 在大國競爭中的核威懾級地位,爭取上升到只要說自己會寫JavaScript,大家就會對你肅然起敬的高度——只要你是前端程式設計師,買票可以插隊,搭車可以讓座,開房可以白嫖……好時代,來臨了!

<p>想成為國之棟樑嗎?來寫 JavaScript 吧!

<p>不多說了,我要去實幹興邦啦!

<p>想了解更多程式設計學習,請關注php培訓欄位!

#

以上是一起看 鴻蒙 JavaScript GUI 技術棧的詳細內容。更多資訊請關注PHP中文網其他相關文章!

相關標籤:
來源:juejin.im
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
作者最新文章
最新問題
JavaScript鉤子函數是什麼?
來自於 1970-01-01 08:00:00
0
0
0
怎麼實作 JavaScript點與圓的位置關係
來自於 1970-01-01 08:00:00
0
0
0
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板