目錄
一园欣、說(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ù)處理性置。
當(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
:
看標(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é)可以看下這張圖:
我們寫(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í)行后的樣子是這樣的:
這就是一個(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)
單從這個(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í),先看圖:
參考:《獨(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ì)它們呢?