從客戶(hù)端角度窺探小程序架構(gòu)

目錄

一园欣、說(shuō)在前面

二锭汛、從微信小程序的發(fā)展史說(shuō)起

三昵慌、微信小程序原理分析

  • 快速加載和原生的體驗(yàn)
  • 渲染層
  • 預(yù)加載
  • 基礎(chǔ)庫(kù)內(nèi)部?jī)?yōu)化
  • 注入小程序WXML結(jié)構(gòu)和WXSS樣式
  • 邏輯層

四、看看JavaScriptCore是怎么執(zhí)行JS腳本的

五涧衙、再說(shuō)說(shuō)支付寶小程序

  • 運(yùn)行時(shí)架構(gòu)
  • 小程序SDK

六哪工、最后

一、說(shuō)在前面:

小程序自誕生以來(lái)绍撞。就以一種百家爭(zhēng)鳴的姿態(tài)展現(xiàn)在開(kāi)發(fā)者的面前正勒。繼2017年1月9日微信小程序誕生后得院,小程序市場(chǎng)又陸續(xù)出現(xiàn)了支付寶小程序傻铣、頭條小程序、百度智能小程序等等祥绞。各家都在微信小程序的基礎(chǔ)上非洲,面向自己的業(yè)務(wù),對(duì)架構(gòu)進(jìn)行逐步優(yōu)化調(diào)整蜕径,但是萬(wàn)變不離其宗两踏,微信小程序終歸為小程序鼻祖,也是得益于微信小程序的思想兜喻,才造就了如今這百花齊放的業(yè)態(tài)梦染。說(shuō)起微信小程序,在體驗(yàn)上的優(yōu)化朴皆,讓我很長(zhǎng)一段時(shí)間認(rèn)為帕识,這是 Native 層渲染。事實(shí)并不完全是遂铡,至今不敢相信肮疗,webView 的渲染竟能帶來(lái)如此體驗(yàn)。本篇主要以一個(gè)客戶(hù)端開(kāi)發(fā)者的角度扒接,來(lái)對(duì)微信小程序伪货、支付寶小程序一探究竟。本篇旨在原理分析钾怔,我并未有真實(shí)的小程序架構(gòu)設(shè)計(jì)經(jīng)驗(yàn)碱呼。

說(shuō)到小程序,不得不需要指出另外一個(gè)問(wèn)題宗侦,蘋(píng)果爸爸對(duì)于 HTML5 app 的更新的審核問(wèn)題愚臀,目前會(huì)有開(kāi)發(fā)者存在這樣的疑問(wèn),Hybrid 和 H5 是不是要被蘋(píng)果拒審了呢凝垛?其實(shí)從更新描述來(lái)看懊悯,不難發(fā)現(xiàn)蘋(píng)果的主要目的是針對(duì)“核心功能未在二進(jìn)制文件內(nèi)”的 App 蜓谋,實(shí)際上小程序無(wú)論是在設(shè)計(jì)理念上,還是核心技術(shù)上炭分,都不存在這樣的問(wèn)題桃焕,小程序并非App,小程序是以 App 為載體捧毛,盡可能的對(duì) web 頁(yè)面進(jìn)行優(yōu)化而生成的產(chǎn)物观堂。還有一點(diǎn)是馬甲包日益猖獗,馬甲包最后基本都轉(zhuǎn)化成為了條款內(nèi)描述的“現(xiàn)金B(yǎng)ocai呀忧、彩票抽獎(jiǎng)和慈善捐款”類(lèi)型师痕,所以蘋(píng)果想要盡可能的禁止它。而且從微信小程序開(kāi)發(fā)文檔來(lái)看而账,微信小程序是典型的技術(shù)推動(dòng)產(chǎn)品的結(jié)果胰坟。關(guān)于RN類(lèi)技術(shù),更不存在這樣的問(wèn)題了泞辐,RN本質(zhì)為 JS 通過(guò) JSCore 調(diào)用 Native 組件笔横。實(shí)際上它的核心仍然在 Native 端,當(dāng)然對(duì) code push 我還尚存疑問(wèn)咐吼。關(guān)于 RN 的動(dòng)態(tài)更新上吹缔,從bang's的描述也不難發(fā)現(xiàn)蘋(píng)果爸爸的態(tài)度,只要不是為了繞過(guò)審核去做動(dòng)態(tài)更新就可以接受锯茄。

二厢塘、從微信小程序的發(fā)展史說(shuō)起

微信小程序是什么,微信把小程序定義為是一種全新的連接用戶(hù)與服務(wù)的方式肌幽,它可以在微信內(nèi)被便捷地獲取和傳播晚碾,同時(shí)具有出色的使用體驗(yàn)。便捷和出色有何而來(lái)牍颈?小程序技術(shù)最初來(lái)源于 H5 和 Native 間的簡(jiǎn)單調(diào)用迄薄,微信構(gòu)建了一個(gè) WeixinJSBridge 來(lái)為H5提供一些 Native 的功能,例如地圖煮岁、播放器讥蔽、定位、拍照画机、預(yù)覽等功能冶伞。關(guān)于 Bridge 的具體實(shí)現(xiàn)可以參考《寫(xiě)一個(gè)易于維護(hù)使用方便性能可靠的Hybrid框架》。但是微信逐漸的又遇到了另外一個(gè)問(wèn)題步氏,那就是 H5 頁(yè)面的體驗(yàn)問(wèn)題响禽,微信團(tuán)隊(duì)為了解決 H5 頁(yè)面的白屏問(wèn)題,他們引入了最近很火的離線(xiàn)包概念,當(dāng)然微信稱(chēng)之為微信 Web 資源離線(xiàn)存儲(chǔ)芋类,實(shí)際上是一個(gè)東西隆嗅。Web 開(kāi)發(fā)者可借助微信提供的資源存儲(chǔ)能力,直接從微信本地加載 Web 資源而不需要再?gòu)姆?wù)端拉取侯繁,從而減少網(wǎng)頁(yè)的加載時(shí)間胖喳。關(guān)于離線(xiàn)包的概念,不了解的話(huà)可以參考《web離線(xiàn)技術(shù)原理》
贮竟。但是當(dāng)頁(yè)面加載大量 CSS 和 JS 時(shí)丽焊,依然會(huì)有白屏問(wèn)題,包括 H5 頁(yè)面點(diǎn)擊事件的遲鈍感和頁(yè)面跳轉(zhuǎn)的體驗(yàn)問(wèn)題咕别。那么基于此問(wèn)題技健,應(yīng)運(yùn)而生的,小程序技術(shù)就誕生了惰拱。

從微信小程序的發(fā)展史雌贱,不難看出,小程序?qū)嶋H上是近幾年開(kāi)發(fā)者對(duì) H5 體驗(yàn)優(yōu)化而來(lái)的弓颈,這也切合了前面所說(shuō)的帽芽,小程序?qū)嶋H上是典型的技術(shù)推動(dòng)產(chǎn)品的結(jié)果删掀。

三翔冀、微信小程序原理分析

微信小程序自稱(chēng)能夠解決以下問(wèn)題:

  • 快速的加載。
  • 更強(qiáng)大的能力披泪。
  • 原生的體驗(yàn)纤子。
  • 易用且安全的微信數(shù)據(jù)開(kāi)發(fā)。
  • 高效和簡(jiǎn)單的開(kāi)發(fā)款票。

快速加載和原生的體驗(yàn)控硼,這其實(shí)都是在體驗(yàn)上的升級(jí),更強(qiáng)大能力實(shí)際上源于微信小程序?yàn)殚_(kāi)發(fā)者提供了大量的組件艾少,這些組件有基于web技術(shù)卡乾,也有基于Native技術(shù),在我看來(lái)這和 RN 技術(shù)不謀而合缚够。后面我會(huì)舉一個(gè)模仿 RN 實(shí)現(xiàn)的小例子來(lái)闡述一下它的原理幔妨。

高效和簡(jiǎn)單的開(kāi)發(fā)是因?yàn)槲⑿判〕绦蜷_(kāi)發(fā)語(yǔ)言實(shí)質(zhì)上還是基于 web 開(kāi)發(fā)規(guī)范,這使得開(kāi)發(fā)前端的人來(lái)開(kāi)發(fā)小程序顯得更容易谍椅。

還有一點(diǎn)更重要的就是安全误堡,為什么說(shuō)小程序是安全的?后面會(huì)逐步展開(kāi)雏吭,揭開(kāi)小程序的神秘面紗锁施。

快速加載和原生的體驗(yàn)

小程序的架構(gòu)設(shè)計(jì)與 web 技術(shù)還是有一定的差別,吸取了 web 技術(shù)的一些優(yōu)勢(shì),也摒棄了 web 技術(shù)中體驗(yàn)不好的地方悉抵。最主要的特點(diǎn)就是小程序采用雙線(xiàn)程機(jī)制肩狂,即視圖渲染和業(yè)務(wù)邏輯分別運(yùn)行在不同的線(xiàn)程中。在傳統(tǒng)的 web 開(kāi)發(fā)中姥饰,網(wǎng)頁(yè)開(kāi)發(fā)渲染線(xiàn)程和腳本線(xiàn)程是互斥的婚温,所以 H5 頁(yè)面中長(zhǎng)時(shí)間的腳本運(yùn)行可能會(huì)導(dǎo)致頁(yè)面失去響應(yīng)或者白屏,體驗(yàn)糟糕媳否。

為了更好的體驗(yàn)栅螟,將頁(yè)面渲染線(xiàn)程和腳本線(xiàn)程分開(kāi)執(zhí)行:

  • 渲染層:界面渲染相關(guān)的邏輯全部 在webView 線(xiàn)程內(nèi)執(zhí)行,一個(gè)小程序存在多個(gè)頁(yè)面篱竭,一個(gè)頁(yè)面對(duì)應(yīng)一個(gè) webView力图,微信小程序限制開(kāi)發(fā)者最多只能創(chuàng)建五個(gè)頁(yè)面。
  • 邏輯層:Android采用 JSCore 掺逼,iOS采用的 JavaScriptCore 框架運(yùn)行 JS 腳本吃媒。怎么在 JavaScriptCore 運(yùn)行腳本文件后面會(huì)講。

雙線(xiàn)程模型是小程序框架與大多數(shù)前端 web 框架的不同之處吕喘,基于這個(gè)模型可以更好的管控以及提供更安全的環(huán)境赘那。因?yàn)檫壿媽舆\(yùn)行在 JSCore 中,并沒(méi)有一個(gè)完整瀏覽器對(duì)象氯质,因而缺少相關(guān)的DOM API和BOM API募舟。客戶(hù)端的開(kāi)發(fā)者可能對(duì) DOM 有些陌生闻察,了解編譯過(guò)程的同學(xué)應(yīng)該知道在編譯器編譯代碼的時(shí)候拱礁,會(huì)有一個(gè)語(yǔ)法分析的過(guò)程,生成抽象語(yǔ)法樹(shù) AST辕漂,編譯器會(huì)根據(jù)語(yǔ)法樹(shù)去檢查表達(dá)式是否合法呢灶、括號(hào)是否匹配等。實(shí)際上DOM也是一種樹(shù)結(jié)構(gòu)钉嘹,經(jīng)過(guò)瀏覽器的解析鸯乃,最終呈現(xiàn)在用戶(hù)面前。通過(guò) JavaScript 操縱 DOM 可以隨意改變?cè)氐奈恢冒匣粒@對(duì)于小程序來(lái)說(shuō)是極為不安全的缨睡。所以說(shuō)邏輯層為小程序帶來(lái)的另一個(gè)特點(diǎn),易于管控和安全仆潮。線(xiàn)程通信基于前面提到的 WeixinJSBridge :邏輯層把數(shù)據(jù)變化通知到視圖層宏蛉,觸發(fā)視圖層頁(yè)面的更新,視圖層把觸發(fā)的事件通知到邏輯層進(jìn)行業(yè)務(wù)處理性置。


image

當(dāng)我們對(duì)渲染層進(jìn)行事件操作后拾并,會(huì)通過(guò) WeixinJSBridge 將數(shù)據(jù)傳遞到 Native 系統(tǒng)層。Native 系統(tǒng)層決定是否要用 Native 處理,然后丟給 邏輯層進(jìn)行用戶(hù)的邏輯代碼處理嗅义。邏輯層處理完畢后會(huì)將數(shù)據(jù)通過(guò) WeixinJSBridge 返給 View 層屏歹,View 渲染更新視圖。

渲染層

根據(jù)《微信小程序開(kāi)發(fā)者文檔》描述之碗,在視圖層內(nèi)蝙眶,小程序的每一個(gè)頁(yè)面都獨(dú)立運(yùn)行在一個(gè)頁(yè)面層級(jí)上。小程序啟動(dòng)時(shí)僅有一個(gè)頁(yè)面層級(jí)褪那,每次調(diào)用wx.navigateTo幽纷,都會(huì)創(chuàng)建一個(gè)新的頁(yè)面層級(jí);相對(duì)地博敬,wx.navigateBack會(huì)銷(xiāo)毀一個(gè)頁(yè)面層級(jí)友浸。大概可以理解為,每個(gè) web 頁(yè)面都是運(yùn)行在單獨(dú)的 webView 里面偏窝,這樣的好處就是讓每個(gè) webView 單純的處理當(dāng)前頁(yè)面的渲染邏輯收恢,不需要加載其他頁(yè)面的邏輯代碼,減輕負(fù)擔(dān)能夠加速頁(yè)面渲染祭往,使其能夠盡可能的接近原生伦意,這與小程序跳轉(zhuǎn)頁(yè)面的體驗(yàn)上也是一致的。

實(shí)際上在小程序源碼內(nèi)有一個(gè) index.html 文件的存在硼补,這是小程序啟動(dòng)后的入口文件驮肉。初次加載的時(shí)候,主入口會(huì)加載相應(yīng)的 webView 括勺,這其中就會(huì)包括前面所提到的缆八,視圖層和邏輯層。邏輯層雖然也提供了 webView 疾捍,但是并不提供瀏覽器相關(guān)接口,而是單純的為了獲取當(dāng)前的 JSCore 栏妖,執(zhí)行相關(guān)的 JS 腳本文件乱豆,這也是開(kāi)發(fā)小程序是沒(méi)辦法直接操作 DOM 的根本原因。

當(dāng)我們每打開(kāi)一個(gè)新頁(yè)面的時(shí)候吊趾,調(diào)用 navigateTo 都相當(dāng)于打開(kāi)了一個(gè)新的 webView 宛裕,這樣一直打開(kāi),內(nèi)存也會(huì)變得吃緊论泛,這也是為什么小程序?qū)?yè)面打開(kāi)數(shù)量有限制的原因了揩尸。

預(yù)加載

根據(jù)小程序開(kāi)發(fā)文檔描述:對(duì)于每一個(gè)新的頁(yè)面層級(jí),視圖層都需要進(jìn)行一些額外的準(zhǔn)備工作屁奏。在小程序啟動(dòng)前岩榆,微信會(huì)提前準(zhǔn)備好一個(gè)頁(yè)面層級(jí)用于展示小程序的首頁(yè)。除此以外,每當(dāng)一個(gè)頁(yè)面層級(jí)被用于渲染頁(yè)面勇边,微信都會(huì)提前開(kāi)始準(zhǔn)備一個(gè)新的頁(yè)面層級(jí)犹撒,使得每次調(diào)用wx.navigateTo都能夠盡快展示一個(gè)新的頁(yè)面。這在客戶(hù)端的角度來(lái)看粒褒,相當(dāng)于打開(kāi)新頁(yè)面之后识颊,對(duì)下一個(gè)頁(yè)面的 webView 提前做了預(yù)加載,這個(gè)思路與當(dāng)前比較流行的 webView 緩存池的思路不謀而合奕坟,原因是在 iOS 和 Android 系統(tǒng)上祥款,操作系統(tǒng)啟動(dòng) webView 都需要一小段時(shí)間,預(yù)加載會(huì)提升頁(yè)面打開(kāi)速度月杉,優(yōu)化白屏問(wèn)題镰踏。

基礎(chǔ)庫(kù)內(nèi)部?jī)?yōu)化

再往深層次來(lái)看,通過(guò)小程序開(kāi)發(fā)工具的源碼沙合,能找到一個(gè) pageframe.html 的模版文件笑陈,具體位置在package.nw/html/pageframe.html

image

看標(biāo)題就應(yīng)該很清楚了,這是渲染層的核心模塊打掘,它的作用就是為小程序準(zhǔn)備一個(gè)新的頁(yè)面垂睬,小程序每個(gè)視圖層頁(yè)面內(nèi)容都是通過(guò) pageframe.html 模板來(lái)生成的,包括小程序啟動(dòng)的首頁(yè)究履。通過(guò)查看源碼滤否,里面定義了一個(gè)屬性var __webviewId__,我猜想這是每個(gè) webView 頁(yè)面的頁(yè)面 ID 最仑,邏輯層處理多個(gè)視圖層間的業(yè)務(wù)邏輯可能就是通過(guò)這個(gè)ID來(lái)做的映射關(guān)系藐俺。在首次啟動(dòng)時(shí),后臺(tái)會(huì)緩存生成的 pageframe.html 模版泥彤,在后面的頁(yè)面打開(kāi)時(shí)欲芹,直接加載緩存的 pageframe.html 模版,頁(yè)面引入的資源文件也可以直接在緩存中加載吟吝,包括小程序基礎(chǔ)庫(kù)視圖層底層菱父、頁(yè)面的模版信息、配置信息以及樣式等內(nèi)容剑逃,這樣避免重復(fù)生成浙宜,快速打開(kāi)頁(yè)面,提升頁(yè)面渲染性能蛹磺。

注入小程序WXML結(jié)構(gòu)和WXSS樣式

關(guān)于 pageframe.html 最后是怎么生成相應(yīng)頁(yè)面的歸功于一個(gè)叫 nw.js 的框架粟瞬,具體實(shí)現(xiàn)這里就不講了(主要是我不會(huì))。

邏輯層

上面了解了渲染層都做了什么之后萤捆,下面在窺探一下裙品,小程序的邏輯層都做了什么俗批。參考http://eux.baidu.com/blog/fe/微信小程序架構(gòu)原理不難發(fā)現(xiàn),sevice 層的代碼是由 WAService.js 實(shí)現(xiàn)的清酥,邏輯層實(shí)際上主要提供了 Page扶镀, App,GetApp 接口和更為豐富 wx 接口模塊焰轻,包括數(shù)據(jù)綁定臭觉、事件分發(fā)、生命周期管理辱志、路由管理等等蝠筑。關(guān)于視圖層和邏輯層間的具體交互細(xì)節(jié)可以看下這張圖:

image

我們寫(xiě)的頁(yè)面邏輯最后都被引入到了一個(gè)叫 appservice.html 的頁(yè)面中,并且分別從 app.js 開(kāi)始一一執(zhí)行揩懒;小程序代碼調(diào)用 Page 構(gòu)造器的時(shí)候什乙,小程序基礎(chǔ)庫(kù)會(huì)記錄頁(yè)面的基礎(chǔ)信息,如初始數(shù)據(jù)(data)已球、方法等臣镣。需要注意的是,如果一個(gè)頁(yè)面被多次創(chuàng)建智亮,并不會(huì)使得這個(gè)頁(yè)面所在的JS文件被執(zhí)行多次忆某,而僅僅是根據(jù)初始數(shù)據(jù)多生成了一個(gè)頁(yè)面實(shí)例(this),在頁(yè)面 JS 文件中直接定義的變量阔蛉,在所有這個(gè)頁(yè)面的實(shí)例間是共享的弃舒。對(duì)于邏輯層,從客戶(hù)端的角度看状原,我們應(yīng)該更關(guān)注于邏輯層的JS是怎么注入到JSCore中的聋呢。

四、看看JavaScriptCore是怎么執(zhí)行JS腳本的

說(shuō)到JavaScriptCore颠区,我們先來(lái)討論下Hybrid App 的構(gòu)建思路削锰,Hybird App是指混合模式移動(dòng)應(yīng)用,即其中既包含原生的結(jié)構(gòu)又有內(nèi)嵌有 Web 的組件瓦呼。這種 App 不僅性能和用戶(hù)體驗(yàn)可以達(dá)到和原生所差無(wú)幾的程度喂窟,更大的優(yōu)勢(shì)在于 bug 修復(fù)快,版本迭代無(wú)需發(fā)版央串。Hybird App的實(shí)質(zhì)并沒(méi)有修改原生 Native 的行為,而是將下發(fā)的資源進(jìn)行加載和界面渲染碗啄,類(lèi)似 WebView质和。下面通過(guò)一個(gè)例子來(lái)模擬一下 JavaScriptCore 執(zhí)行 JS 腳本來(lái)讓 Native 和 JS 之間的通信。關(guān)于 JavaScriptCore 的具體使用可以參考下戴銘的《深入剖析 JavaScriptCore》稚字。

我們打算實(shí)現(xiàn)這樣的功能:通過(guò)下發(fā)JS腳本創(chuàng)建原生的 UILabel 和 UIButton 控件并響應(yīng)事件饲宿,首先編寫(xiě) JS 代碼如下:

(function(){
 console.log("ProgectInit");
 //JS腳本加載完成后 自動(dòng)render界面
 return render();
 })();

//JS標(biāo)簽類(lèi)
function Label(rect,text,color){
    this.rect = rect;
    this.text = text;
    this.color = color;
    this.typeName = "Label";
}
//JS按鈕類(lèi)
function Button(rect,text,callFunc){
    this.rect = rect;
    this.text = text;
    this.callFunc = callFunc;
    this.typeName = "Button";
}
//JS Rect類(lèi)
function Rect(x,y,width,height){
    this.x = x;
    this.y = y;
    this.width = width;
    this.height = height;
}
//JS顏色類(lèi)
function Color(r,g,b,a){
    this.r = r;
    this.g = g;
    this.b = b;
    this.a = a;
}
//渲染方法 界面的渲染寫(xiě)在這里面
function render(){
    var rect = new Rect(20,100,280,30);
    var color = new Color(1,0,0,1);
    var label = new Label(rect,"這是一個(gè)原生的Label",color);

    var rect4 = new Rect(20,150,280,30);
    var button = new Button(rect4,"這是一個(gè)原生的Button",function(){
                            var randColor = new Color(Math.random(),Math.random(),Math.random(),1);
                            TestBridge.changeBackgroundColor(randColor);
                            });
    //將控件以數(shù)組形式返回
    return [label,button];
}

創(chuàng)建一個(gè) OC 類(lèi) TestBridge 綁定到 JavaScriptCore 全局對(duì)象上:

@protocol TestBridgeProtocol <JSExport>
- (void)changeBackgroundColor:(JSValue *)value;
@end

@interface TestBridge : NSObject<TestBridgeProtocol>

@property(nonatomic, weak) UIViewController *ownerController;

@end
#import "TestBridge.h"

@implementation TestBridge

- (void)changeBackgroundColor:(JSValue *)value{
    self.ownerController.view.backgroundColor = [UIColor colorWithRed:value[@"r"].toDouble green:value[@"g"].toDouble blue:value[@"b"].toDouble alpha:value[@"a"].toDouble];
}

@end

在 ViewController 中實(shí)現(xiàn)一個(gè)界面渲染的 render 解釋方法:

#import "ViewController.h"
#import <JavaScriptCore/JavaScriptCore.h>
#import "TestBridge.h"

@interface ViewController ()

@property(nonatomic, strong)JSContext *jsContext;
@property(nonatomic, strong)NSMutableArray *actionArray;
@property(nonatomic, strong)TestBridge *bridge;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    //創(chuàng)建JS運(yùn)行環(huán)境
    self.jsContext = [JSContext new];
    //綁定橋接器
    self.bridge =  [TestBridge new];
    self.bridge.ownerController = self;
    self.jsContext[@"TestBridge"] = self.bridge;
    self.actionArray = [NSMutableArray array];
    [self render];
}

-(void)render{
    NSString * path = [[NSBundle mainBundle] pathForResource:@"main" ofType:@"js"];
    NSData * jsData = [[NSData alloc]initWithContentsOfFile:path];
    NSString * jsCode = [[NSString alloc]initWithData:jsData encoding:NSUTF8StringEncoding];
    JSValue * jsVlaue = [self.jsContext evaluateScript:jsCode];
    for (int i=0; i<jsVlaue.toArray.count; i++) {
        JSValue * subValue = [jsVlaue objectAtIndexedSubscript:i];
        if ([[subValue objectForKeyedSubscript:@"typeName"].toString isEqualToString:@"Label"]) {
            UILabel * label = [UILabel new];
            label.frame = CGRectMake(subValue[@"rect"][@"x"].toDouble, subValue[@"rect"][@"y"].toDouble, subValue[@"rect"][@"width"].toDouble, subValue[@"rect"][@"height"].toDouble);
            label.text = subValue[@"text"].toString;
            label.textColor = [UIColor colorWithRed:subValue[@"color"][@"r"].toDouble green:subValue[@"color"][@"g"].toDouble blue:subValue[@"color"][@"b"].toDouble alpha:subValue[@"color"][@"a"].toDouble];
            label.textAlignment = NSTextAlignmentCenter;
            [self.view addSubview:label];
        }else if ([[subValue objectForKeyedSubscript:@"typeName"].toString isEqualToString:@"Button"]){
            UIButton * button = [UIButton buttonWithType:UIButtonTypeSystem];
            button.frame = CGRectMake(subValue[@"rect"][@"x"].toDouble, subValue[@"rect"][@"y"].toDouble, subValue[@"rect"][@"width"].toDouble, subValue[@"rect"][@"height"].toDouble);
            [button setTitle:subValue[@"text"].toString forState:UIControlStateNormal];
            button.tag = self.actionArray.count;
            [button addTarget:self action:@selector(buttonAction:) forControlEvents:UIControlEventTouchUpInside];
            [self.actionArray addObject:subValue[@"callFunc"]];
            [self.view addSubview:button];
            
        }
    }
}

-(void)buttonAction:(UIButton *)btn{
    JSValue * action  = self.actionArray[btn.tag];
    [action callWithArguments:nil];
}

@end

這樣就完成了一個(gè)簡(jiǎn)單的 JS 腳本注入厦酬,實(shí)際上執(zhí)行后的樣子是這樣的:

image

這就是一個(gè)簡(jiǎn)單的執(zhí)行 JS 腳本的邏輯,實(shí)際上 ReactNative 的原理也是基于此瘫想,小程序邏輯層與微信客戶(hù)端的交互邏輯也是基于此仗阅。

到這里,關(guān)于微信小程序渲染層與邏輯層做了什么国夜、怎么做的减噪、優(yōu)化了什么以及為什么要采用這樣的架構(gòu)來(lái)設(shè)計(jì),基本都梳理完畢了车吹。小程序這樣的分層設(shè)計(jì)顯然是有意為之的筹裕,它的中間層完全控制了程序?qū)τ诮缑孢M(jìn)行的操作, 同時(shí)對(duì)于傳遞的數(shù)據(jù)和響應(yīng)時(shí)間也做到的監(jiān)控窄驹。一方面程序的行為受到了極大限制朝卒, 另一方面微信可以確保他們對(duì)于小程序內(nèi)容和體驗(yàn)有絕對(duì)的控制。我們?cè)谛〕绦虻?JS 代碼里面是不能直接使用瀏覽器提供的 DOM 和 BOM 接口的乐埠,這一方面是因?yàn)?JS 代碼外層使用了局部變量進(jìn)行屏蔽抗斤,另一方面即便我們可以操作 DOM 和 BOM 接口,它們對(duì)應(yīng)的也是邏輯層模塊丈咐,并不會(huì)對(duì)頁(yè)面產(chǎn)生影響瑞眼。這樣的結(jié)構(gòu)也說(shuō)明了小程序的動(dòng)畫(huà)和繪圖 API 被設(shè)計(jì)成生成一個(gè)最終對(duì)象而不是一步一步執(zhí)行的樣子, 原因就是 json 格式的數(shù)據(jù)傳遞和解析相比與原生 API 都是損耗不菲的扯罐,如果頻繁調(diào)用很可能損耗 過(guò)多性能负拟,進(jìn)而影響用戶(hù)體驗(yàn)。

總結(jié)一句話(huà)就是webView渲染歹河,JSCore處理邏輯掩浙,JSBridge做線(xiàn)程通信。后面再簡(jiǎn)要的分析下支付寶小程序秸歧,支付寶小程序?qū)儆诤笃鹬愠ΓЦ秾毿〕绦蛟谖⑿判〕绦虻幕A(chǔ)上,做了一些優(yōu)化键菱,單從技術(shù)角度來(lái)看谬墙,有點(diǎn)后來(lái)者居上的意思。目前支付寶技術(shù)通過(guò)官方的媒體賬號(hào)對(duì)外暴漏的一些實(shí)現(xiàn)細(xì)節(jié)也在逐步增多经备。

六拭抬、再說(shuō)說(shuō)支付寶小程序

前端框架下面是小程序 native 引擎,包括了小程序容器侵蒙、渲染引擎和 JavaScript 引擎造虎,這塊主要是把客戶(hù)端 native 的能力和前端框架結(jié)合起來(lái),給開(kāi)發(fā)者提供系統(tǒng)底層能力的接口纷闺。在渲染引擎上面算凿,支付寶小程序不僅提供 JavaScript+Webview 的方式份蝴,還提供 JavaScript+Native 的方式,在對(duì)性能要求較高的場(chǎng)景氓轰,可以選擇 Native 的渲染模式婚夫,給用戶(hù)更好的體驗(yàn)。

這段文字來(lái)源于支付寶對(duì)外開(kāi)放的技術(shù)博客的描述署鸡,從這段描述中案糙,我們能夠發(fā)現(xiàn)支付寶小程序在架構(gòu)設(shè)計(jì)上同樣采用的渲染引擎加 JavaScript 引擎兩部分,包括頁(yè)面間的切換實(shí)際上和微信小程序邏輯基本一致储玫。下面這張是支付寶小程序應(yīng)用框架的架構(gòu)圖:

運(yùn)行時(shí)架構(gòu)

image

單從這個(gè)運(yùn)行時(shí)架構(gòu)來(lái)看侍筛,它與微信小程序不同的地方是,webView 頁(yè)面也就是渲染層通過(guò)消息服務(wù)直接與邏輯層進(jìn)行通訊撒穷,而不需要像微信的 JSBridge 那樣作為中間層匣椰,消息服務(wù)具體實(shí)現(xiàn)細(xì)節(jié)目前尚不得知。支付寶的JSBridge只會(huì)與邏輯層進(jìn)行通訊端礼,來(lái)給小程序提供一些 Native 能力禽笑。支付寶的這種架構(gòu)主要目的是解決渲染層與邏輯層交互的對(duì)象較復(fù)雜、數(shù)據(jù)量較大時(shí)蛤奥,交互的性能比較差的問(wèn)題佳镜。支付寶小程序的設(shè)計(jì)思路比較值得借鑒,微信小程序線(xiàn)程間的通訊是通過(guò) JSBridge 凡桥,序列化 json 進(jìn)行傳遞的蟀伸。支付寶小程序重新設(shè)計(jì)了V8虛擬機(jī),讓邏輯和渲染都有自己的 Local Runtime缅刽,存放私有的模塊和數(shù)據(jù)啊掏。在渲染層和邏輯層交互時(shí),setData 的 對(duì)象會(huì)直接創(chuàng)建在 Shared Heap 里面衰猛,因此渲染層的 Local Runtime 可以直接讀到該對(duì)象迟蜜,并且用于 render 層的渲染,保證了邏輯和渲染的隔離啡省,又減少了序列化和傳輸成本娜睛。當(dāng)然支付寶還有些其他的優(yōu)化,包括首頁(yè)離線(xiàn)緩存卦睹,緩存時(shí)機(jī)的處理以及閃屏處理等等問(wèn)題畦戒,這里就不再延伸討論了(因?yàn)楹芏嗉?xì)節(jié)我也不知道??)。

小程序SDK

根據(jù)支付寶小程序?qū)ν忾_(kāi)放的技術(shù)文章來(lái)看结序,架構(gòu)設(shè)計(jì)還是非常巧妙的兢交,也很值得我們學(xué)習(xí),先看圖:

image

參考:《獨(dú)家笼痹!支付寶小程序技術(shù)架構(gòu)全解析》

小程序SDK在架構(gòu)設(shè)計(jì)上把它分為了兩部分配喳,一部分是核心庫(kù)基礎(chǔ)引擎,一部分是基于基礎(chǔ)庫(kù)開(kāi)發(fā)的插件功能凳干。從上往下看:

  • 第一層小程序?qū)忧绻@是小程序開(kāi)發(fā)者使用小程序 DSL 及各種組件開(kāi)發(fā)的代碼層。
  • 第二層和第三層架應(yīng)該是小程序內(nèi)部封裝的一些組件和對(duì)外提供的相關(guān)API等救赐。
  • 第四層和第五層是基于 React 框架涧团,構(gòu)建的小程序運(yùn)行基礎(chǔ)框架,這是小程序的核心層经磅,主要包含小程序的邏輯處理引擎及渲染層泌绣。支付寶基于 ReactNative 增加了 Native 引擎,可以用原生來(lái)渲染 UI 预厌。根據(jù)支付寶 mPaaS 的介紹來(lái)看阿迈,目前支付寶的小程序使用的是 React 版,螞蟻內(nèi)部的其他 App 有在使用 React Native 版的小程序轧叽。
  • 基礎(chǔ)組件部分和擴(kuò)展能力部分更像是基于 Bridge 調(diào)用的原生能力苗沧。擴(kuò)展能力應(yīng)該是支付寶內(nèi)部的一些基礎(chǔ)組件,一樣通過(guò)JSBridge給小程序進(jìn)行賦能炭晒。

支付寶小程序架構(gòu)設(shè)計(jì)上采用分層的設(shè)計(jì)待逞,邏輯非常清晰。在管控上网严,和微信小程序基本一致识樱,使用自己的一套 DSL 來(lái)保證它的管控能力,編寫(xiě)小程序只能使用框架提供的自定義的模板樣式震束,既保證了安全性怜庸,又解決了H5開(kāi)發(fā)質(zhì)量參差不齊的問(wèn)題。

六驴一、最后

差不多半年多沒(méi)有寫(xiě)文章了休雌,這一年幾乎把所有的精力都撲在了公司的業(yè)務(wù)上,趁著公司年會(huì)時(shí)間稍顯充裕肝断,對(duì)當(dāng)前的小程序架構(gòu)進(jìn)行了下分析和總結(jié)杈曲,順便參加下掘金的征文活動(dòng)。當(dāng)然胸懈,真正的小程序應(yīng)該比這還要復(fù)雜的多担扑,小程序?qū)嶋H上是多年來(lái)大前端融合的一個(gè)結(jié)果,是一套非常成體系的技術(shù)方案趣钱,是技術(shù)推動(dòng)產(chǎn)品而產(chǎn)生的概念涌献。看了這么多我想你對(duì)小程序也有了初步認(rèn)識(shí)首有,小程序的核心實(shí)際上還是渲染層邏輯層的構(gòu)建燕垃,那么如果讓你開(kāi)發(fā)一套小程序SDK枢劝,你會(huì)怎樣設(shè)計(jì)它們呢?

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末卜壕,一起剝皮案震驚了整個(gè)濱河市您旁,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌轴捎,老刑警劉巖鹤盒,帶你破解...
    沈念sama閱讀 206,482評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異侦副,居然都是意外死亡侦锯,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)秦驯,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)尺碰,“玉大人,你說(shuō)我怎么就攤上這事汇竭〈谢龋” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,762評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵细燎,是天一觀(guān)的道長(zhǎng)两曼。 經(jīng)常有香客問(wèn)我,道長(zhǎng)玻驻,這世上最難降的妖魔是什么悼凑? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,273評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮璧瞬,結(jié)果婚禮上户辫,老公的妹妹穿的比我還像新娘。我一直安慰自己嗤锉,他們只是感情好渔欢,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,289評(píng)論 5 373
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著瘟忱,像睡著了一般奥额。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上访诱,一...
    開(kāi)封第一講書(shū)人閱讀 49,046評(píng)論 1 285
  • 那天垫挨,我揣著相機(jī)與錄音,去河邊找鬼触菜。 笑死九榔,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播哲泊,決...
    沈念sama閱讀 38,351評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼剩蟀,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了攻旦?” 一聲冷哼從身側(cè)響起喻旷,我...
    開(kāi)封第一講書(shū)人閱讀 36,988評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎牢屋,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體槽袄,經(jīng)...
    沈念sama閱讀 43,476評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡烙无,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,948評(píng)論 2 324
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了遍尺。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片截酷。...
    茶點(diǎn)故事閱讀 38,064評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖乾戏,靈堂內(nèi)的尸體忽然破棺而出迂苛,到底是詐尸還是另有隱情,我是刑警寧澤鼓择,帶...
    沈念sama閱讀 33,712評(píng)論 4 323
  • 正文 年R本政府宣布三幻,位于F島的核電站,受9級(jí)特大地震影響呐能,放射性物質(zhì)發(fā)生泄漏念搬。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,261評(píng)論 3 307
  • 文/蒙蒙 一摆出、第九天 我趴在偏房一處隱蔽的房頂上張望朗徊。 院中可真熱鬧,春花似錦偎漫、人聲如沸爷恳。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,264評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)温亲。三九已至,卻和暖如春通危,著一層夾襖步出監(jiān)牢的瞬間铸豁,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,486評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工菊碟, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留节芥,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,511評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像头镊,于是被迫代替她去往敵國(guó)和親蚣驼。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,802評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容