您好,登錄后才能下訂單哦!
本篇文章為大家展示了鴻蒙 JavaScript GUI 技術棧解析,內容簡明扼要并且容易理解,絕對能使你眼前一亮,通過這篇文章的詳細介紹希望你能有所收獲。
JS 框架層
從最頂層的視角出發,要想用「鴻蒙 2.0」渲染出一段動態的文本,你只需要編寫如下的 HML(類 XML)格式代碼:
<!-- hello.hml --> <text onclick="boil">{{hello}}</text>
然后在同級目錄編寫這樣的 JavaScript:
// hello.js export default { data: { hello: 'PPT' }, boil() { this.hello = '核武器'; } }
這樣只要點擊文本,就會調用 boil
方法,讓 PPT 變成 核武器。
這背后發生了什么呢?熟悉 Vue 2.0 的同學應該會立刻聯想到下面這幾件事:
onclick
事件時能執行相應回調。 this.hello
賦值時能執行相應回調。這幾件事分別是怎么實現的呢?簡單說來是這樣的:
onclick
屬性轉換為 JS 對象的屬性字段。 onclick
屬性會在 C++ 中被檢查和注冊,相當于全部組件均為原生。Object.defineProperty
的(幾百行量級的)ViewModel。document.createElement
式的標準化 API。由于大量常見 JS 框架中的能力都直接做進了 C++,所以整套 GUI 技術棧里用純 JavaScript 所實現的東西(主要見 ace_lite_jsfwk
倉庫下的 core/index.js
、observer.js
和 subject.js
),相當于有且只有這么一個功能:
一個可以 watch 的 ViewModel。
至于純 JS 框架部分的實現復雜度和質量,客觀地說如果是個人業余作品,可以當作校招面試中不錯的加分項。
JS 引擎與運行時層
理解了 JS 框架層之后,我們既可以認為「鴻蒙 2.0」選擇把高度簡化后的 Vue 深度定制進了 C++ 里,也可以認為它緊密圍繞著高度簡化(且私有)的 DOM 實現了配套的前端框架。因此要想繼續探索這套 GUI 的原理,我們就必須進入其 C++ 部分,了解其 JS 引擎與運行時層的實現。
JS 引擎和運行時之間,有什么區別與聯系呢?JS 引擎一般只需符合 ECMA-262 規范,其中沒有對任何帶「副作用」的平臺 API 的定義。從 setTimeout
到 document.getElementById
到 console.log
再到 fs.readFile
,這些能執行實際 IO 操作的功能,都需要由「將引擎 API 和平臺 API 膠合到一起」的運行時提供。運行時本身的原理并不復雜,譬如在個人的文章《從 JS 引擎到 JS 運行時》中,你就可以看到如何借助現成的QuickJS 引擎,自己搭建一個運行時。
那么在「鴻蒙 2.0」中,JS 運行時是如何搭建出來的呢?有這么幾條重點:
<text>
和 <div>
的 XML 標簽組件,都對應一個綁定到 JerryScript 上的 C++ Component 類,如 TextComponent
和 DivComponent
等。 @system
為前綴的 built-in 模塊,它們提供了 JS 中可用的 Router / Audio / File 等平臺能力(參見 ohos_module_config.h
)。這里特別值得一提的是 Router。它和 vue-router 等常見 Web 平臺路由的實現原理有很大區別,是專門在運行時內深度定制的(參見 router_module.cpp
、js_router.cpp
和 js_page_state_machine.cpp
)。簡單說來這個「路由」是這樣實現的:
router.replace
原生方法,走進 C++。pages/detail
)加載新頁面 JS,新建頁面狀態機實例,將其切換至 Init 狀態。所以我們可以發現,這里所謂的「切換路由」,其實更接近 Web 瀏覽器的「刷新頁面」。那么我們可以認為這個 JS 運行時的能力,已經可以對標 WebKit 級的瀏覽器內核了嗎?
當然還差得很遠。與 WebKit 相比,它并未支持對 HTML 和 CSS 的解析(二者都會在開發階段被解析轉換成同等執行效果的 JS),也沒有瀏覽器中持續動態加載、解析與執行資源的挑戰(小程序不外乎是幾個本地的靜態 JS 文件)。至于排版布局和渲染方面自然也有很大差距,這點會在最后一節提及。
另外,相信很多同學都會對 JerryScript 引擎感到好奇。本部分最后分享一些個人對此所掌握的消息。
JerryScript 引擎是一款專為嵌入式硬件實現的 JS 解釋器,只支持到 ES5.1 標準。在 QuickJS Benchmark 中,可以查看到它們的性能對比結果:
可以看到論性能,JerryScript 在無 JIT 的引擎中大幅弱于 QuickJS 和 Hermes。如果和開啟了 JIT 的 V8 相比,甚至會慢出兩個數量級。因此這是非常特定于低端設備的引擎,如果需要支持 React 和 Vue 這類中大型前端項目中標配的基礎庫(甚至其相應全家桶),仍然可能需要使用更強大的引擎。
對于 JerryScript 的使用,有同場景重度應用經驗的當屬 RT-Thread 創始人 @午夜熊,他們和某國內一線廠商合作研發的智能手表就用 JerryScript 實現了 UI,目前產品馬上就要上市了。他們團隊對 JerryScript 的一些使用反饋也吻合上述評價,概括說來是這樣的:
那么師出名門的 QuickJS 和 Facebook 的 Hermes,是否就是無 JIT 式 JS 引擎的下一代標桿了嗎?倒也未必如此。這方面可以參考個人的知乎回答:隨著 TypeScript 繼續普及,會不會出現直接跑 TypeScript 的運行時?這里提到的微軟為教育項目 MakeCode 研發的 Static TypeScript,就相當有潛力成為下一代的高性能 JS 系語言環境。通過限定 TypeScript 的靜態強類型子集并為其搭建工具鏈,STS 可以做到無需 JIT 也能接近 V8 的性能水平,同時內存占用比 V8 少兩個數量級。這使得 STS 不光能用于開發普通 app 這類 IO 密集的應用,還能順利在嵌入式硬件上開發小游戲這類更偏計算密集(需逐幀更新渲染)的應用,在工程能力上是一項很大的突破。
所以說,當「鴻蒙 2.0」還需要熟練開發者勉強搭建出環境跑通 Hello World 時,微軟已經讓上百萬小朋友都能用 TypeScript 在網頁里給教學用的掌上游戲機寫小游戲入門編程了。這里沒什么唱反調的意思,只希望提醒一下我們在為國產「里程碑」歡呼時,也要清醒地看到業界前沿的動向,僅此而已。
圖形繪制層
理解 JS 運行時之后,還剩最后一個問題,即 JS 運行時中的各種 Component 對象,是如何被繪制為手表等設備上的像素的呢?
這就涉及「鴻蒙 2.0」中的另一個 graphic_lite 倉庫了。可以認為,這里才是真正執行實際繪制的 GUI。像之前的 TextComponent 等原生組件,都會對應到這里的某種圖形庫 View。它以一種相當經典的方式,在 C++ 層實現并提供了「Canvas 風格的立即模式 GUI」和「DOM 風格的保留模式 GUI」兩套 API 體系(對于立即模式和保留模式 GUI 的區別與聯系,可參見個人這篇IMGUI 科普回答)。概括說來,這個圖形子系統的要點大致如下:
UIView
這個 C++ 控件基類,其中有一系列形如 OnClick
/ OnLongPress
/ OnDrag
的虛函數。基本每種 JS 中可用的原生 Component 類,都對應于一種 UIView 的子類。 DrawLine
/ DrawCurve
/ DrawText
等命令式的繪制方法。FillArea
矩形單色填充能力。在基礎 UI 控件方面,不難找到一些值得一提的自研模塊特性:
至于 2D UI 渲染中的幾項關鍵能力,則基本可分為路徑、位圖和文字三類。這個圖形庫在這幾個方面都有涉及,最后簡單介紹一下。
首先對于位圖,這個圖形庫依賴了 libpng
和 libjpeg
做圖像解碼,然后即可使用內存中的 bitmap 圖像做繪制。
然后對于路徑,這個圖形庫自己實現了各種 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; } }
基于高中的數學知識,我們不難明白這種曲線是如何繪制出來的:取足夠多的點(也就是那個默認 1000 的 INTERPOLATION_RANGE
)作為插值輸入,逐點計算出曲線表達式的 XY 坐標,然后直接修改像素位置所在的 framebuffer 內存即可。這種教科書式的實現是最經典的,不過如果要拿它對標 Skia 里的黑魔法,還是不要勉為其難了吧。
最后對于文字的繪制,會涉及一些字體解析、定位、RTL和折行等方面的處理。這部分實際上也是組合使用了一些業界通用的開源基礎庫來實現的。比如對于「牢」這個字,就可以找到圖形庫的這么幾個開源依賴,它們各自扮演不同的角色:
harfbuzz
- 用來告訴調用者,應該把「牢」的 glyph 字形放在哪里。freetype
- 從宋體、黑體等字體文件中解碼出「牢」的 glyph 字形,將其光柵化為像素。icu
- 處理 Unicode 中許多奇葩的特殊情況,這塊個人不了解,略過。到這里,我們就可以理出一個非常概括性的渲染流程了:
this.hello = 'PPT'
之類的代碼,觸發依賴追蹤。這就是個人對「鴻蒙 2.0」這套 GUI 技術棧的解讀了。時間有限并未進一步深挖,歡迎(文明的)批評指正。
總結
特別聲明:本部分主觀評論僅針對「鴻蒙 2.0」當前的 GUI 框架部分,請勿隨意曲解。
對于「鴻蒙 2.0」在 GUI 部分的亮點,個人能想到這些:
而至于明顯(不只是某幾行代碼寫得丑)的缺失或問題,目前看來則有這么一些:
JS 框架層
JS 引擎與運行時層
圖形渲染層
看起來槽點很多,但是你會指責汽車沒有噴氣式發動機嗎?對于不同復雜度的場景,自然存在著不同的最優架構設計。目前看來,這套設計確實很適合嵌入式硬件和簡易「小程序」的場景。但如果按照所謂「分布式全場景跨平臺」的要求來審視,那么不管比起現代的 Web 瀏覽器還是 iOS 和安卓的 GUI,這套架構的復雜度都是完全無法相提并論的。
上述內容就是鴻蒙 JavaScript GUI 技術棧解析,你們學到知識或技能了嗎?如果還想學到更多技能或者豐富自己的知識儲備,歡迎關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。