為了讓大家更好的理解小程式的一些限制和做一些優化,下面從小程式的基礎架構講起,如有不對的地方,望指正,請輕噴 ?
一. 頁面棧限制最多10層
首先,我們看看下圖,小程式的架構如下:
我們可以看到,一個頁面使用一個 WebView 執行緒進行渲染。 如果於頁面棧有 10 層,則會開啟 10 個 WebView 執行緒,佔多一點記憶體,所以對頁面棧進行了限制。那如果在10層頁面棧的限制內,由於頁面的內容過於複雜,記憶體爆了怎麼辦? 小程式內部有一個回收機制,如果記憶體緊張時,會回收掉一部分 WebView 。
很多人可能會覺得, 10 層頁面棧基本已經夠用了,無須關注這方面的限制了。
但是如果出現迴圈引用的話,使用者反覆點選,則很容易出現爆棧的情況。 如下圖:
所以,在開發之前,提前梳理好頁面之間的跳轉,合理使用 navigator ,redirectTo, navigateBack …… 是非常重要的。當然,作為一個程式設計師,我並不想在跳轉的時候去時時刻刻的關注我有沒有正確引用,有沒有超出10層頁面棧。 完全可以對小程式的跳轉做一個封裝。
因為只有 wx.navigateTo 才會使頁面棧 + 1 ,那我們只要對這個方法做一層兜底處理即可。 如下程式碼:
const PAGE_LIMIT = 10
const pages = getCurrentPages()
if(pages.length >= PAGE_LIMIT) {
// 使用 wx.redirectTo 方法
}
複製程式碼
這樣就可以隨心所欲的跳來跳去了。
二. JsCore
小程式的邏輯層是在 JsCore 中執行的。限制如下:
- 沒有 Window、Document 物件,也就無法使用 基於 DOM 的一些庫. 暫無解決方法。
還是從一張圖說起:
不同於頁面的渲染,所有的指令碼邏輯都是跑在同一個 JsCode 執行緒裡面,類似於路由中改變 Hash 值。 因此也會引起下面一些常見的坑- 需要自己清理頁面的 setTimeout 或者 setInterval 定時器
- 如果要求頁面每次出現都是最新的資料,則要把拉取資料的邏輯放在 onShow 中執行,別放在 onload 中。
- 若追求“極致化體驗”,在使用者切到下一個頁面的時候,可以在 onHide 中 abort 掉沒有請求成功的介面,把即將處理的 setData 去掉,可以減輕最新頁面的邏輯,讓最新頁面最快渲染展示。
三. 原生元件層級最高
像 canvas , video ,input ,map ,picker …… 元件,官方直接使用原生元件,渲染方式如下圖:
從上圖很容易就可以看出,Navtive 元件的層級是最高的,那麼僅僅去改變 z-index 也無法讓其他元件覆蓋原生元件。還好,官方給出了一個解決方案:
- 使用 cover-view cover-image 將希望層級變高的元件也包裹成“原生元件”
四. 獨特的網路請求,本地儲存
- 獨特的 Storage 儲存
- 以使用者緯度隔離,同一個裝置,A 無法訪問 B 使用者的資料。
- 持久快取,只有在使用者關掉小程式才會刪除,如果空間不足,會進行 LRU ,也就是不經常使用的小程式的資料快取區域會被全部清空。
- 在體驗版、開發版、和線上版都共用一套,並不會隔離。
- 沒有 Cookie
- Request uploadFile downloadFile 併發最多10個請求
- 只支援 HTTPS WSS
如果以往在移動端, PC 的介面會使用 Cookie 進行一些處理,那在小程式中使用該介面就比較尷尬了。
因此,我們可以在小程式中也模擬出 Cookie ,如下圖:
在 Storage 中隔離一個欄位,用來做 Cookie ,下面用了一個小技巧,把 Cookie 的內容存放於記憶體中,而非每次都從 storage 中讀取。
let cookie = (function(){
return wx.getStorageSync('cookies');
}())
const Cooke = {
getCookie(){}, //從記憶體中獲取cookie
setCookie(){}, // 設定cookie
setCookieInHeader(){}, //根據response的Header設定cookie
removeCookie() {}, //刪除cookie
isExpired() {} //判斷是否過期
}
複製程式碼
然後,我們在每次 Request 成功後,解析 Header 中 SET-COOKIE 屬性設定 Cookie ,在每一次請求的時候,手動在 Header 中設定 Cookie 。這樣就完美地模擬瀏覽器的 Cookie 概念了。
- 併發請求處理
設定一個佇列,如果請求處理完畢,則移出佇列,如果當前陣列超過10個請求,則進入等待狀態。大概程式碼如下:
class Request {
constructor() {
this.maxLimit = 10;
this.requestQueue = []; // 請求佇列
this.requestIng = 0; //當前併發數
}
request () {
// 判斷是否超出併發數
if(this.requestIng >= this.maxLimit) {
// 推入請求佇列
}else {
this.requestIng ++;
// 執行成功後 - 1 ,從 requestQueue 中取出重新請求
}
}
}
複製程式碼
五. setData
好吧,還是從一張圖說起,顯然,我們可以看出,每一次 setData 都是一次執行緒通訊。
執行緒通訊成本很高,非常耗時間,因此官方明確的給出了建議:- 將多次 setData 合併成一次進行呼叫
- 只 setData 物件改變的某個值
data: {
array: {
changeData: '我改變了',
noChangeData: '我沒有改變'
},
},
this.setData({
'array.changeData':'changed data'
})
複製程式碼
- 與介面渲染無關的欄位不要放在 data 中
data: {
view: '與介面相關的資料'
},
noRelaView: '與介面無關'
複製程式碼
@Author:beidan