自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

探索小程序底層架構原理

開發(fā) 架構
在這之前,我們先來思考一個問題,小程序在架構上為什么會選擇雙線程?

雙線程架構

在這之前,我們先來思考一個問題,小程序在架構上為什么會選擇雙線程?

為什么是雙線程?

加載及渲染性能

小程序的設計之初就是要求快速,這里的快指的是加載以及渲染。

目前主流的渲染方式有以下3種:

  • Web技術渲染
  • Native技術渲染
  • Hybrid技術渲染(同時使用了webview和原生來渲染)

從小程序的定位來講,它就不可能用純原生技術來進行開發(fā),因為那樣它的編譯以及發(fā)版都得跟隨微信,所以需要像Web技術那樣,有一份隨時可更新的資源包放在遠程,通過下載到本地,動態(tài)執(zhí)行后即可渲染出界面。

但如果用純web技術來開發(fā)的話,會有一個很致命的缺點那就是在 Web 技術中,UI渲染跟 JavaScript 的腳本執(zhí)行都在一個單線程中執(zhí)行,這就容易導致一些邏輯任務搶占UI渲染的資源,這也就跟設計之初要求的快相違背了。

因此微信小程序選擇了Hybrid 技術,界面主要由成熟的 Web 技術渲染,輔之以大量的接口提供豐富的客戶端原生能力。同時,每個小程序頁面都是用不同的WebView去渲染,這樣可以提供更好的交互體驗,更貼近原生體驗,也避免了單個WebView的任務過于繁重。

微信小程序是以webview渲染為主,原生渲染為輔的混合渲染方式。

管控安全

由于web技術的靈活開放特點,如果基于純web技術來渲染小程序的話,勢必會存在一些不可控因素和安全風險。

為了解決安全管控的問題,小程序從設計上就阻止了開發(fā)者去使用一些瀏覽器提供的開放性api,比如說跳轉頁面、操作DOM等等。如果把這些東西一個一個地去加入到黑名單,那么勢必會陷入一個非常糟糕的循環(huán),因為瀏覽器的接口也非常豐富,那么就很容易遺漏一些危險的接口,而且就算是禁用掉了所有的接口,也防不住瀏覽器內(nèi)核的下次更新。

所以要徹底解決這個問題,必須提供一個沙箱環(huán)境來運行開發(fā)者的JavaScript 代碼。這個沙箱環(huán)境只提供純 JavaScript 的解釋執(zhí)行環(huán)境,沒有任何瀏覽器相關接口。那么像HTML5中的ServiceWorker、WebWorker特性就符合這樣的條件,這兩者都是啟用另一線程來執(zhí)行 javaScript。

這就是小程序雙線程模型的由來:

  • 渲染層:界面渲染相關的任務全都在 WebView 線程里執(zhí)行,通過邏輯層代碼去控制渲染哪些界面。一個小程序存在多個界面,所以渲染層存在多個 WebView。
  • 邏輯層:創(chuàng)建一個單獨的線程去執(zhí)行 JavaScript,在這個環(huán)境下執(zhí)行的都是有關小程序業(yè)務邏輯的代碼。

雙線程模型

小程序的架構模型有別與傳統(tǒng)web單線程架構,小程序為雙線程架構。

微信小程序的渲染層與邏輯層分別由兩個線程管理,渲染層的界面使用 webview 進行渲染;邏輯層采用 JSCore運行JavaScript代碼。

webview渲染線程

如何找到渲染層?

1.我們可以通過調(diào)試微信開發(fā)者工具:微信開發(fā)者工具 ->調(diào)試 ->調(diào)試微信開發(fā)者工具

圖片

2.然后我們會再看到一個調(diào)試界面,看起來跟我們平時用的瀏覽器調(diào)試界面幾乎一摸一樣

圖片

但這并不是小程序的渲染層,而是開發(fā)者工具的結構。但我們在里面可以發(fā)現(xiàn)有一些webview標簽,在第一個webview上的src屬性看著是不是有點眼熟,沒猜錯的話它就是我們當前小程序打開頁面的路徑。所以這個webview才是小程序真正的渲染層。這里你會發(fā)現(xiàn)它里面并不只有一個webview,其實里面包含著視圖層的webview,業(yè)務邏輯層webview,開發(fā)者工具的webview。

開發(fā)者工具的邏輯層跑在webview中主要是為了模擬真機上的雙線程

3.打開渲染層一探究竟

通過showdevTools方法來打開調(diào)試此webview界面的調(diào)試器

document.querySelectorAll('webview')[0].showDevTools(true)

這里我們看到的才真正是小程序的渲染層,也就是小程序代碼編譯后的樣子,我們會發(fā)現(xiàn)這里的標簽都與我們開發(fā)時寫的不一樣,都統(tǒng)一加了wx-前綴。了解過webComponent的同學相信一眼就能看出他們非常相似,但小程序并沒有直接使用webComponent,而是自行搭建了一套組件系統(tǒng)Exparser。

Exparser的組件模型與WebComponents標準中的Shadow DOM高度相似。Exparser會維護整個頁面的節(jié)點樹相關信息,包括節(jié)點的屬性、事件綁定等,相當于一個簡化版的Shadow DOM實現(xiàn)。

為什么不直接使用webComponent,而是選擇自行搭建一套組件系統(tǒng)?

點擊查看:

  • 管控與安全:web技術可以通過腳本獲取修改頁面敏感內(nèi)容或者隨意跳轉其它頁面
  • 能力有限:會限制小程序的表現(xiàn)形式
  • 標簽眾多:增加理解成本

JSCore邏輯線程

邏輯層我們直接在小程序開發(fā)者工具的調(diào)試器中輸入document就能看到。

小程序將所有業(yè)務代碼置于同一個線程中運行,在小程序開發(fā)者工具中邏輯線程同樣是跑在一個webview中;webview中的appservice.html除了引入業(yè)務代碼js之外,還有后臺服務內(nèi)嵌的一些基礎功能代碼。

編譯原理

了解完小程序的雙線程架構,我們再來看一下小程序的代碼是如何編譯運行的,微信開者工具模擬器運行的代碼是經(jīng)過本地預處理、本地編譯,而微信客戶端運行的代碼是額外經(jīng)過服務器編譯的。這里我們還是以微信開發(fā)者工具為例來探索一番。

在開發(fā)者工具輸入openVendor(),會幫我們打開微信開發(fā)者工具的WeappVendor文件夾

圖片

在這里我們我們會看到一些wxvpkg文件,這是小程序的各個版本的基礎庫文件,還有兩個值得我們注意的文件:wcc、wcsc,這兩個文件是小程序的編譯器,分別用來編譯wxml和wxss文件。

編譯wxml

這里我們可以將開發(fā)者工具中的wcc編譯器拷貝一份出來,嘗試去用它編譯一下wxml文件,看看最后的產(chǎn)物是什么?

圖片

我們在終端執(zhí)行一下以下命令

./wcc -b index.wxml >> wxml_output.js

然后它會在當前目錄下生成一個wxml_output.js文件,文件中有一個非常重要的方法$gwx,該方法會返回一個函數(shù)。該函數(shù)的具體作用我們可以嘗試執(zhí)行一下看看結果。

我們打開渲染層webview搜索一下該方法(為了方便查看,這里會用個小項目來演示)

圖片

從這里我們可以看到該方法會傳入一個小程序頁面的路徑,返回的依然是一個函數(shù)

var decodeName = decodeURI("./index/index.wxml")
var generateFunc = $gwx(decodeName)

我們嘗試按這里流程執(zhí)行一下$gwx返回的函數(shù),看看返回的內(nèi)容是什么?

<!--compiler-test/index.wxml-->
<view class="qd_container" >
<text name="title">wxml編譯</text>
<view >{{ name }}</view>
</view>
const func = $gwx(decodeURI('index.wxml'))
console.log(func())

圖片

沒錯,這個函數(shù)正是用來生成Virtual DOM。

編譯wxss

我們同樣可以用微信開發(fā)者工具中的wcsc來編譯一下wxss文件。

(大家認為這里應該是會生成css文件還是js文件呢?)

我們在終端執(zhí)行一下以下命令來編譯wxss文件

./wcsc -js index.wxss >> wxss_output.js

圖片

相比之前的wcc編譯wxml文件來說,這次的編譯相對來說比較簡單,它主要完成了以下內(nèi)容:

  • rpx單位的換算,轉換成px
  • 提供setCssToHead方法將轉換好的css添加到head中

rpx動態(tài)適配

小程序提供rpx單位來適配各種尺寸的設備。

圖片

比如:

/*index.wxss */
.qd_container {
width: 100rpx;
background: skyblue;
border: 1rpx solid salmon;
}
.qd_reader {
font-size: 20rpx;
color: #191919;
font-weight: 400;
}

經(jīng)過編譯之后會生成setCssToHead方法并執(zhí)行

setCssToHead([".",[1],"qd_container { width: ",[0,100],"; background: skyblue; border: ",[0,1]," solid salmon; }\n.",[1],"qd_reader { font-size: ",[0,20],"; color: #191919; font-weight: 400; }\n",])( typeof __wxAppSuffixCode__ == "undefined"? undefined : __wxAppSuffixCode__ );

里面會調(diào)用transformRPX方法將rpx轉成px

var transformRPX = window.__transformRpx__ || function(number, newDeviceWidth) {
if ( number === 0 ) return 0;
number = number / BASE_DEVICE_WIDTH * ( newDeviceWidth || deviceWidth );
number = Math.floor(number + eps);
if (number === 0) {
if (deviceDPR === 1 || !isIOS) {
return 1;
} else {
return 0.5;
}
}
return number;
}
// 主要公式
number = number / BASE_DEVICE_WIDTH * (newDeviceWidth || deviceWidth);
number = Math.floor(number + eps); //為了精確
// rpx值 / 基礎設備寬750 * 真實設備寬

渲染流程

上面了解完wxml與wxss的編譯過程,我們再來整體了解一下頁面的渲染流程。

先來了解渲染層模版

從上面的渲染層webview我們可以找到這兩個webview

圖片

第一個index/indexwebview我們上面說了它就是對應我們的小程序的渲染層,也就是真正的小程序頁面。

那么下面這個instanceframe.html是什么呢?

這個webview其實是小程序渲染模版,打開查看一番

圖片

它其實就是提前注入了一些頁面所需要的公共文件,以及紅框內(nèi)的一些頁面獨立的文件占位符,這些占位符會等小程序對應頁面文件編譯完成后注入進來。

如何保證代碼的注入是在渲染層webview的初始化之后執(zhí)行?

在剛剛渲染模版webview的下方有這樣一段腳本:

if (document.readyState === 'complete') {
alert("DOCUMENT_READY")
} else {
const fn = () => {
alert("DOCUMENT_READY")
window.removeEventListener('load', fn)
}
window.addEventListener('load', fn)
}

很明顯,這里在頁面初始化完成后,通過alert來進行通知。此時的native/nw.js會攔截這個alert,從而知道此時的webview已經(jīng)初始化完成。

整體渲染流程

了解了上面這個重要過程,我們就可以將整個流程串聯(lián)起來了。

1.打開小程序,創(chuàng)建視圖層頁的webview時,此時會初始化渲染層webview,并且會將該web view地址設置為instanceframe.html,也就是我們的渲染層模版。

2.然后進入頁面/index/index,等instanceframewebview初始化完成,會將頁面index/index編譯好的代碼注入進來并執(zhí)行。

// 將webview src路徑修改為頁面路徑
history.pushState('', '', 'http://127.0.0.1:26444/__pageframe__/index/index')

/*
...
這里還有一些 wx config及wxss編譯后的代碼
*/

// 這里是
var decodeName = decodeURI("./index/index.wxml")
var generateFunc = $gwx(decodeName)
if (decodeName === './__wx__/functional-page.wxml') {
generateFunc = function () {
return {
tag: 'wx-page',
children: [],
}
}
}
if (generateFunc) {
var CE = (typeof __global === 'object') ? (window.CustomEvent || __global.CustomEvent) : window.CustomEvent;
document.dispatchEvent(new CE("generateFuncReady", {
detail: {
generateFunc: generateFunc
}
}))
__global.timing.addPoint('PAGEFRAME_GENERATE_FUNC_READY', Date.now())
} else {
document.body.innerText = decodeName + " not found"
console.error(decodeName + " not found")
}

3.此時通過history.pushState方法修改webview的src但是webview并不會發(fā)送頁面請求,并且將調(diào)用$gwx為生成一個generateFun方法,前面我們了解到該方法是用來生成虛擬dom的。

4.然后會判斷該方法存在時,通過document.dispatchEvent 派發(fā)發(fā)自定義事件generateFuncReady 將generateFunc當作參數(shù)傳遞給底層渲染庫。

5.然后在底層渲染庫WAWebview.js中會監(jiān)聽自定義事件generateFuncReady ,然后通過 WeixinJSBridge 通知 JS 邏輯層視圖已經(jīng)準備好()。

圖片

6.最后 JS 邏輯層將數(shù)據(jù)給 Webview 渲染層,WAWebview.js在通過virtual dom生成真實dom過程中,它會掛載到頁面的document.body上,至此一個頁面的渲染流程就結束了。

數(shù)據(jù)更新

小程序的視圖層目前使用 WebView 作為渲染載體,而邏輯層是由獨立的 JavascriptCore 作為運行環(huán)境。

在架構上,WebView 和 JS Core 都是獨立的模塊,并不具備數(shù)據(jù)直接共享的通道。所以在更新數(shù)據(jù)時必須調(diào)用setData來通知渲染層做更新。

setData

  • 邏輯層虛擬 DOM 樹的遍歷和更新,觸發(fā)組件生命周期和 observer 等;
  • 將 data 從邏輯層傳輸?shù)揭晥D層;
  • 視圖層虛擬 DOM 樹的更新、真實 DOM 元素的更新并觸發(fā)頁面渲染更新。

這里第二步由于WebView 和 JS Core 都是獨立的模塊,數(shù)據(jù)傳輸是通過 evaluateJavascript 實現(xiàn)的,還會有額外 JS 腳本解析和執(zhí)行的耗時因此數(shù)據(jù)到達渲染層是異步的。

因此切記:

  • 不要頻繁的去setData
  • 不要每次 setData 都傳遞大量新數(shù)據(jù)(單次stringify后不超過256kb)
  • 不要對后臺態(tài)頁面進行setData,會搶占正在執(zhí)行的前臺頁面的資源

與Vue對比(再來看看Vue)

整體來講,小程序身上或多或少都有著vue的影子...(模版文件,data,指令,虛擬dom,生命周期等)

但在數(shù)據(jù)更新這里,小程序卻與Vue表現(xiàn)的截然不同。

1.頁面更新DOM是同步的還是異步的?

2.既然更新DOM是個同步的過程,為什么Vue中還會有nextTick鉤子?

mounted() {
this.name = '前端南玖'
console.log('sync',this.$refs.title.innerText) // 舊文案
// 新文案
Promise.resolve().then(() => {
console.log('微任務',this.$refs.title.innerText)
})
setTimeout(() => {
console.log('宏任務',this.$refs.title.innerText)
}, 0)
this.$nextTick(() => {
console.log('nextTick',this.$refs.title.innerText)
})
}

圖片

責任編輯:華軒 來源: 前端南玖
相關推薦

2023-06-09 14:01:00

架構程序APP

2021-10-25 09:41:04

架構運維技術

2019-10-16 16:33:41

Docker架構語言

2021-09-14 09:52:56

ToB小程序生態(tài)評估

2022-10-28 10:23:27

Java多線程底層

2019-12-16 10:01:54

Java開發(fā)Web

2020-11-05 11:14:29

Docker底層原理

2023-01-04 07:54:03

HashMap底層JDK

2024-01-05 09:00:00

SpringMVC軟件

2017-06-09 10:40:00

微信小程序架構分析

2017-06-09 10:06:54

微信小程序架構分析

2017-06-09 12:58:20

微信小程序架構分析

2021-06-10 10:51:27

程序基礎架構

2021-07-23 13:34:50

MySQL存儲InnoDB

2023-10-18 10:55:55

HashMap

2022-12-19 08:00:00

SpringBootWeb開發(fā)

2021-07-05 07:51:43

JVM底層Python

2010-02-04 10:43:05

Android DDM

2023-02-12 23:23:30

點贊
收藏

51CTO技術棧公眾號