運(yùn)行環(huán)境與網(wǎng)頁的區(qū)別
網(wǎng)頁開發(fā)渲染線程和JS線程是互斥的,渲染線程和JS線程都可以操作DOM,容易引起混亂,當(dāng)執(zhí)行 JS 引擎線程時(shí),GUI渲染線程會(huì)被掛起,當(dāng)前任務(wù)隊(duì)列為空時(shí),JS引擎才會(huì)去執(zhí)行 GUI 渲染。這也是為什么長時(shí)間的腳本運(yùn)行可能會(huì)導(dǎo)致頁面失去響應(yīng)的原因。
而對于小程序,它的邏輯層和渲染層是分開的,小程序采用 AppService
和 WebView
的雙線程模型,基于 WebView
和原生控件混合渲染的方式,小程序的兩個(gè)線程沒有互斥的關(guān)系,js腳本線程和GUI渲染線程在 Native 客戶端的協(xié)調(diào)下可以有條不紊的同時(shí)執(zhí)行。
雙線程模型運(yùn)行機(jī)制
小程序的渲染層和邏輯層分別由2個(gè)線程管理:渲染層的界面使用了WebView 進(jìn)行渲染;邏輯層則使用Javascript引擎解析和執(zhí)行JS邏輯代碼。一個(gè)小程序存在多個(gè)頁面,所以渲染層存在多個(gè) WebView 線程。
渲染層多線程的原因,便于維護(hù)多頁面的狀態(tài):
例如:一款新聞信息流小程序,它有兩個(gè)頁面,A頁面是新聞信息流,B頁面是新聞詳情頁。那么當(dāng)用戶往下滑了很久后發(fā)現(xiàn)一個(gè)感興趣的新聞,點(diǎn)擊跳轉(zhuǎn)到詳情頁,看完之后想回到A頁面剛才的位置繼續(xù)瀏覽。單頁應(yīng)用由于把A頁面給擦掉了,所以這種場景下當(dāng)從B回到A時(shí),會(huì)發(fā)現(xiàn)A又重新刷新了一遍,體驗(yàn)非常糟糕。
TIPS:
官方說渲染層和邏輯層分別是兩個(gè)線程,個(gè)人覺得容易引起歧義,更嚴(yán)謹(jǐn)一點(diǎn)說是兩個(gè)進(jìn)程中的兩個(gè)線程,即WebView
進(jìn)程和App
進(jìn)程,官方文檔中有這樣一句話:
當(dāng)小程序基于 WebView 環(huán)境下時(shí),WebView 的 JS 邏輯、DOM 樹創(chuàng)建、CSS 解析、樣式計(jì)算、Layout、Paint (Composite) 都發(fā)生在同一線程
,在 WebView 上執(zhí)行過多的 JS 邏輯可能阻塞渲染,導(dǎo)致界面卡頓。以此為前提,小程序同時(shí)考慮了性能與安全,采用了目前稱為「雙線程模型」的架構(gòu)。
上面說的 “同一線程” 其實(shí)指的是webView
的渲染進(jìn)程(渲染進(jìn)程中包含渲染線程和JS主線程等),網(wǎng)頁開發(fā)中提到的渲染線程與JS主線程互斥和阻塞的問題在 WebView
環(huán)境下也不例外,所以小程序則將 JS 邏輯部分拆到App級(App進(jìn)程中不同于WebView環(huán)境,并沒有window
document
等對象)的JS線程中運(yùn)行,即所謂的AppService
,WebView
與App
是獨(dú)立的運(yùn)行環(huán)境,雖然不會(huì)發(fā)生阻塞,但是也無法共享數(shù)據(jù),所以要依靠 Native
來通訊。
所以理論上是可以在wxs中訪問到window對象的,因?yàn)閣xs代碼運(yùn)行在 webview 中,但是微信對wxs功能做了閹割限制,尤其不能讓用戶操作DOM。

通訊過程如下:

兩個(gè)線程的通信是基于微信客戶端提供的WeixinJsBridge來實(shí)現(xiàn)的,像一個(gè)橋梁一樣將小程序的運(yùn)行環(huán)境和微信客戶端(native連接了起來),同時(shí)也負(fù)責(zé)在渲染層和邏輯層之間傳遞數(shù)據(jù)和事件的工作。
數(shù)據(jù)傳輸通過邏輯層和視圖層兩邊提供的evaluateJavascript
實(shí)現(xiàn)的,用戶傳輸?shù)臄?shù)據(jù),需要將其轉(zhuǎn)換為字符串形式傳遞,同時(shí)把轉(zhuǎn)換后的數(shù)據(jù)內(nèi)容拼接成一份 JS 腳本,再通過執(zhí)行 JS 腳本的形式傳遞到兩邊的獨(dú)立環(huán)境。
請求也是通過WeixinJsBridge由微信客戶端代為發(fā)起。
以上這樣的運(yùn)行機(jī)制,優(yōu)缺點(diǎn)都很明顯:
雙線程模型的優(yōu)缺點(diǎn)
優(yōu)點(diǎn):
將邏輯層和渲染層隔離開,用戶無法直接操作DOM,提供了相對封閉和安全的運(yùn)行環(huán)境。
JS不會(huì)阻塞渲染,但是大部分情況下視覺都要依賴JS中處理的數(shù)據(jù),JS阻塞時(shí)也不會(huì)通知視圖去更新,所以這條優(yōu)點(diǎn)其實(shí)意義不是很大
所有的頁面和組件的邏輯都在一個(gè)線程(AppService
)里,使用同一個(gè)上下文環(huán)境,比較好做狀態(tài)共享或跨頁面通訊。
缺點(diǎn):
缺點(diǎn)在于每一次數(shù)據(jù)傳遞都要進(jìn)行一次線程之間的通信,業(yè)務(wù)邏輯跟渲染層天然隔離,造成通信開銷大、延遲高等問題,通信越頻繁、數(shù)據(jù)量越大,則性能瓶頸越嚴(yán)重。
每個(gè)頁面都創(chuàng)建一個(gè)WebView
線程處理,有更多的內(nèi)存、時(shí)間開銷。
渲染層和邏輯層狀態(tài)要維護(hù)兩份,進(jìn)一步加重內(nèi)存、時(shí)間開銷,并且沒有辦法完全保證兩份數(shù)據(jù)狀態(tài)實(shí)時(shí)保持一致,例如僅使用 this.data
更新數(shù)據(jù)而不是通過setData
時(shí),那么實(shí)際渲染的值與邏輯層的值就不一致,某些場景下會(huì)造成非預(yù)期的問題。
尤其當(dāng) this.data.obj 這個(gè)obj指向一個(gè)引用地址的時(shí)候,帶來的問題更加不可預(yù)期。
this.viewModal = {obj: {a: 3}};
this.setData({obj: this.viewModal.obj}); // 此時(shí)邏輯層的data.obj已經(jīng)指向viewModal.obj對象的地址
this.viewModal.obj.a = 666;
this.data.obj.a === 666; // true
// 之后無論你在任何地方不小心更改了this.viewModal.obj,那么this.data.obj也變了,而此時(shí)在渲染層的數(shù)據(jù)還是舊數(shù)據(jù),你本意是想通過this.data獲取當(dāng)前被渲染的值,但是這時(shí)候可能值已經(jīng)并不符合你的預(yù)期了。
小程序并非所有組件都是通過 webview
來渲染的。比如像 video, canvas, map 等稱為 native compoennt 的組件是直接用客戶端的原生組件渲染的,這意味著這些組件的性能更好。
總體上從開發(fā)者的角度來看,我認(rèn)為這個(gè)架構(gòu)并不是一個(gè)很好的架構(gòu),邏輯層與渲染層隔離,帶來的問題遠(yuǎn)遠(yuǎn)比它解決的問題更多。
一個(gè)應(yīng)用里面的多個(gè)頁面,你認(rèn)為是共享的狀態(tài)多,還是獨(dú)立的狀態(tài)多?用戶在操作一個(gè)頁面時(shí),更關(guān)心跨頁面通信實(shí)時(shí)性,還是更關(guān)心當(dāng)前頁面的渲染實(shí)時(shí)性?
我們當(dāng)然更關(guān)心性能,但是從微信的角度來說,他們想既能享受 web
生態(tài)的好處的同時(shí)也能限制 web
的開放性,增強(qiáng)自己對平臺(tái)內(nèi)容的管控程度,從禁用eval
和new Function()
上就能看出一二。
當(dāng)然微信也知道架構(gòu)所帶來的性能問題,所以發(fā)明了 WXS
,讓一部分 js
代碼能在渲染層跑,部分解決通信消耗和延遲的問題,還提供了一系列的性能優(yōu)化指南。
而近期,又有重磅更新放出,新渲染引擎 Skyline
橫空出世。
Skyline 新渲染引擎
官方聲稱新引擎能夠解決我們上面的吐槽:
在 Skyline 環(huán)境下,我們嘗試改變這一情況:Skyline 創(chuàng)建了一條渲染線程來負(fù)責(zé) Layout, Composite 和 Paint 等渲染任務(wù),并在 AppService 中劃出一個(gè)獨(dú)立的上下文,來運(yùn)行之前 WebView 承擔(dān)的 JS 邏輯、DOM 樹創(chuàng)建等邏輯。這種新的架構(gòu)相比原有的 WebView 架構(gòu),有以下特點(diǎn):
界面更不容易被邏輯阻塞,進(jìn)一步減少卡頓
無需為每個(gè)頁面新建一個(gè) JS 引擎實(shí)例(WebView),減少了內(nèi)存、時(shí)間開銷
框架可以在頁面之間共享更多的資源,進(jìn)一步減少運(yùn)行時(shí)內(nèi)存、時(shí)間開銷
框架的代碼之間無需再通過 JSBridge 進(jìn)行數(shù)據(jù)交換,減少了大量通信時(shí)間開銷
而與此同時(shí),這個(gè)新的架構(gòu)能很好地保持和原有架構(gòu)的兼容性,基于 WebView 環(huán)境的小程序代碼基本上無需任何改動(dòng)即可直接在新的架構(gòu)下運(yùn)行。
牛蛙~ 牛蛙~,由于我也是剛得知消息,新引擎體驗(yàn)報(bào)告待我后續(xù)奉上🐶。
其實(shí)本篇文章就是看到了新渲染引擎的消息后想寫的,各位先當(dāng)做個(gè)鋪墊,有助于我們更好地去理解和使用新引擎。
最后,說了半天Webview
,那 Webview
到底是個(gè)啥,這里再給大家解釋一下。
WebView
WebView是術(shù)語,是指網(wǎng)頁視圖。可以內(nèi)嵌在移動(dòng)端,實(shí)現(xiàn)前端的混合式開發(fā),大多數(shù)混合式開發(fā)框架都是基于WebView模式進(jìn)行二次開發(fā)的。比如:APIcloud、uni-app等等的框架。
WebView
是一個(gè)基于webkit
引擎、展現(xiàn)web頁面的控件,用于在手機(jī)系統(tǒng)中展示網(wǎng)頁,可以簡單的理解為一個(gè)可以嵌套到界面上的一個(gè)瀏覽器控件。
webkit
是一個(gè)瀏覽器引擎,也就是瀏覽器內(nèi)核,處理CSS、DOM、渲染等。
不同運(yùn)行環(huán)境中,使用的WebView
不同。

原文鏈接
該文章在 2023/7/29 10:05:52 編輯過