什麼是小程式
小程式頁面本質上是網頁,使用了hybrid技術進行原生與web的混合開發,hybrid技術使用webview做渲染,透過js bridge
作為web與native的資料互動橋樑
- 使用技術棧與網頁開發是一致的,都用到HTML、CSS、JS
區別:不支援瀏覽器API,比如window、document,只能使用微信提供的API
微信小程式的發展歷程
微信小程式形態
- 小程式更像是公眾號開發的升級
- 早期微信透過sdk方式增強了開發者開發公眾號網頁的能力,小程式的誕生是微信邁向平臺化超級app的業務行為,並且幫助使用者更好的實現了「輕量級APP」
開發標準
- 最初微信小程式自己定義了一套“標準”,最開始的框架沒有元件、npm、和web生態嚴重脫節
- 由於特殊的雙執行緒模型與四不像語法,小程式開放只是對三方業務的開放
商家湧入
- 小程式業務的開放性 -> 平臺型app
- 比如:支付寶小程式、百度、淘寶、360、快應用
- 小程式設計的目的:大多數選擇了和微信類似的架構,框架,更多不是從技術角度考慮,而是想盡可能蹭微信小程式的福利,讓開發者更快的投放到自己的平臺
微信小程式框架
目錄結構每個頁面資料夾都包含了wxml、wxss、js、json幾個字尾的檔案,分別是模板檔案,樣式檔案,js指令碼檔案,json頁面配置檔案。在目錄最外層還有全域性的app.js、app.json、app.wxss
小程式型別應用的技術選型
渲染頁面的技術方案
- 用純客戶端原生技術渲染
- 用純web技術渲染
用客戶端原生技術與web技術結合的混合開發(hybrid)渲染
方案對比
- 開發門檻:web門檻低,原生也有像RN這樣的框架支援
- 體驗:原生體驗比web好太多,hybrid在一定程度上比web接近原生體驗
- 版本更新:web支援線上更新,如果小程式用原生開發,則需要打包到微信一起稽核
管控與安全:web可跳轉或是改變頁面內容,存在一些不可控因素和安全風險
方案確定
- 小程式的宿主環境是微信等手機app,用純客戶端原生技術來編寫小程式,那麼小程式程式碼每次都需要與手機app程式碼一起發版。所以該方案不可取
web支援有一份副本資源包放在雲端,透過下載到本地,動態執行後即可渲染出介面,但純web技術在一些複雜的互動上可能有效能問題。所以該方案不可取
- 在web技術中,UI渲染跟指令碼執行都在一個單執行緒中執行,這就容易導致一些邏輯任務搶佔UI渲染的資源
兩者結合的hybrid技術來渲染小程式,用一種近似web的方式來開發,並且可以實現線上更新程式碼
- 擴充套件web的能力,比如輸入框元件(input、textarea)有更好的控制鍵盤的能力
- 體驗更好,同時減輕webview的渲染工作
- 用客戶端原生渲染內建一些複雜元件可以提供更好的效能
雙執行緒模型
小程式的渲染層和邏輯層分為兩個執行緒管理
檢視層(Web View) -> WebView進行渲染
- 介面渲染相關的任務都在WebView執行緒裡執行,透過邏輯層程式碼區控制渲染哪些介面。一個小程式存在多個介面,所以檢視層存在多個WebView執行緒
邏輯層(App Service) -> JsCore執行緒執行js指令碼
- 建立一個單獨的執行緒去執行js程式碼,在這裡執行的都是有關小程式業務邏輯的程式碼,負責邏輯處理、資料請求、介面呼叫等
- JSBridge:上層開發與Native(系統層)的橋樑,使小程式可透過API使用原生的功能,且部分元件為原生元件實線,從而有良好體驗
設計目的:為了管控和安全等問題,阻止開發者使用一些,例如瀏覽器的window物件,跳轉頁面,操作DOM、動態執行指令碼的開放性介面
使用沙箱環境提供純javascript的解釋執行環境
- 客戶端系統,在pc執行時用javascript引擎執行js程式碼
- ios:在ios系統時使用ios提供的Js core框架執行js程式碼
- 安卓:在安卓系統時使用騰訊的x5核心提供的JsCore執行js程式碼
資料驅動檢視變化
問:js邏輯程式碼放到單獨的執行緒中執行,在WebView執行緒無法直接操作DOM,開發者如何實現動態更改介面
DOM的更新透過簡單的資料通訊來實現,邏輯層和檢視層的通訊由native(微信客戶端)做中轉,邏輯層傳送網路請求也經由native轉發。
- 在渲染層把wxml轉化為對應的js物件
- 在邏輯層發生資料變化的時候,透過宿主環境提供的setData方法把資料從邏輯層傳遞到native,再轉發到渲染層
- 經過對比,更新真實dom內容
總結:這部分和mvvm框架類似,轉化了ast樹,透過native建立了虛擬dom,透過setData的方法更新資料,再透過native做了diff差異化更新真實dom樹
事件處理
微信做了特殊的處理,將所有的事件攔截後丟到邏輯層交給js處理
- 程式碼描述互動事件
- 檢視層使用者互動觸發事件
- native攔截丟給邏輯層
- 邏輯層收到回撥處理事件
事件的派發處理包括事件捕獲和冒泡兩種:
互動流程就如同上述的資料驅動檢視變化一樣,native攔截丟給邏輯層後,透過js響應事件,透過setData修改資料,虛擬dom發生變化,native diff更新真實dom
執行機制
- 冷啟動:使用者首次開啟或者小程式被微信主動銷燬後再次開啟的情況(當手機系統五秒內兩次以上記憶體告警,小程式會主動銷燬),此時小程式需要重新載入啟動
- 熱啟動:加入使用者已經開啟過某小程式,在一定時間內(目前是五分鐘)再次開啟該小程式(小程式前後臺切換),此時無需重新啟動
注意 - 小程式沒有重啟的概念
小程式框架對比
小程式原生語法
- 目前小程式已經能夠做到前端工程化,並且植入前端生態中已有的一些理念,比如狀態管理,CLI工程化等等
前端能力基本在小程式上可以複用,比如狀態管理,柯里化管理元件狀態,ts等
增強型框架
指小程式引入npm後,有了更開放的能力所帶來的收益
以微信小程式為例,騰訊開源了omi框架
- 整體保留了小程式現有語法,在此之上對它進行了擴充和增強
- 比如引入了computed hook,比如能直接透過
this.store.data.logs[0] = 'changed'
修改狀態,徹底將微信小程式vue化
轉換型框架
能夠一碼多端的框架,比如rax、taro、uniapp,讓開發者幾乎不用感受小程式原生語法,更大程度對接前端已有生態,只是最後構建產物為小程式程式碼
這些框架實際都是透過轉換產物1:1轉換成對應的小程式程式碼
編譯時
優勢
- 執行時效能損耗低
- 目的碼明確,開發者所寫即所得
- 執行時、編譯時最佳化:比如框架會給予開發者更多的語法支援以及預設的效能最佳化處理,比如避免多次setData,或者長連結串列最佳化等等
劣勢 語法限制高:需要完全命中開發者在模板部分所用到的所有語法,語法受限,由於是1:1編譯轉換,開發者在開發的時候還是不得不去遵循小程式的開發規範,比如一個檔案只能定義一個元件之類的
執行時
比起編譯時,最大優勢是可以幾乎沒有任何語法約束的去完成程式碼編寫
優勢:沒有語法限制
微信小程式
文件必讀
推薦將文件的指南、框架、元件、API、工具相關的通讀一遍
基本內容
示例程式碼github地址:https://github.com/wechat-miniprogram/miniprogram-demo
基礎
官方文件:https://developers.weixin.qq.com/miniprogram/dev/framework/
基礎核心
更新機制
啟動時同步更新
- 定期檢查小程式版本
- 長時間未使用小程式
啟動時非同步更新
- 開啟發現有新版本,非同步下載,下次冷啟動時載入新版本
開發者手動更新
程式碼注入
- 按需注入:“lazyCodeLoading”:“requiredComponents”配置路徑進行打包
- 用時注入:在開啟「按需注入」特性的前提下,指定一部分自定義元件不在小程式啟動時注入,而是在真正渲染的時候才進行注入,使用佔位元件在需要渲染但注入完成前展示
分包載入
原則
- 宣告subpackages後,將俺subpackages配置路徑進行打包,subpackages配置路徑外的目錄將被打爆到app(主包)中
- app(主包)也可以有自己的pages(即最外層的pages欄位)
- subpackage的根目錄不能是另外一個subpackage內的子目錄
- tabBar頁面必須在app(主包)內
獨立分包
- 當小程式從普通的分包頁面啟動時,需要受限下載主包
- 獨立分包執行時,App並不一定被註冊,因此getApp()也不一定可以獲得App物件。基礎庫2.2.4版本開始getApp支援
[allowDefault]
引數,在App未定義時返回一個預設實現,當主包載入,App被註冊時,預設實現中定義的屬性會被覆蓋合併到真正的App中
除錯小程式
- 使用vconsole
- 開啟sourceMap配置項
- 實時日誌:重寫log,使用wx.getRealtimeLogManager封裝,在運營後臺“開發->開發管理->運維中心->實時日誌"檢視
- errno:開發者自己丟擲異常,針對API的cb err進行狀態碼的判斷,針對業務場景語義化展示
如何相容版本
版本號比較
const version = wx.getSystemInfoSync().SKDVersion if (compareVersion(version, '1.1.0') >= 0) { wx.openBluetoothAdapter() } else { wx.showModal({ title: '提示', content: '當前微信版本過低,無法使用該功能,請升級到最新微信版本後重試' }) }
判斷API是否存在
if (wx.openBluetoothAdapter) { wx.openBluetoothAdater() } else { wx.showModal({ title: '提示', content: '當前微信版本過低,無法使用該功能,請升級到最新微信版本後重試' }) }
wx.canIUse
判斷當前環境是否能使用api,和jsdk的api許可權判斷類似
wx.showModal({
success: function(res) {
if (wx.canIUse('showModal.success.cancel')) {
console.log(res.cancel)
}
}
})
最低基礎庫版本
運營後臺設定最低基礎庫版本
框架
微信原生小程式框架官方文件
小程式配置
- 全域性配置
- 頁面配置
- sitemap配置
框架介面
App:必須在app.js中呼叫且只能呼叫一次
- 一些全域性的生命週期
- getApp:外部訪問App中資料的方式
頁面
Page
- data
- 一些頁面中的生命週期
- getCurrentPages:獲取當前頁面棧,可以獲取當前頁面資訊,比如路由,也可以透過該方法實現跨頁面賦值
// 跨頁面賦值 let pages = getCurrentPages() // 當前頁面棧 let prePage = pages[pages.length - 2] // 上一頁面 prevPage.setData({ // 直接給上一頁面複製 }) // 頁面跳轉後自動重新整理 wx.switchTab({ url: '../index/index', success: function (e) { const page = getCurrentPages().pop(); // 當前頁面 if (page == undefined || page == null) return; page.onLoad(); //或者其他操作 } })
Router
- 頁面路由器有
switchTab
reLaunch
redirectTo
navigateTo
navigateBack
五個方法 - 與 wx 物件向同名的五個方法
switchTab
reLaunch
redirectTo
navigateTo
navigateBack
功能相同;唯一的區別是,頁面路由器中的方法呼叫時,相對路徑永遠相對於this
指代的頁面或自定義元件。
Page({ wxNavAction: function () { wx.navigateTo({ url: './new-page' }) }, routerNavAction: function () { this.pageRouter.navigateTo({ url: './new-page' }) } })
自定義元件
- 頁面路由器有
- Component:在微信開發工具右鍵新建元件自動建立了自定義元件模板,整體包裹在一個Component物件下
- Behavior:behaviors是用於元件間程式碼共享的特性, 類似一些程式語言中的'mixin'或者'traits',它可以將behavior模組的程式碼混入元件程式碼中,一般是抽離出邏輯程式碼和部分屬性
// 官方示例
module.exports = Behavior({
behaviors: [],
properties: {
myBehaviorProperty: {
type: String
}
},
data: {
myBehaviorData: {}
},
attached: function(){},
methods: {
myBehaviorMethod: function(){}
}
})
基礎用法,先新建一個元件,再透過元件自帶的behaviors屬性混入behavior模組,這個屬性類似vue的mixins
// 新建個元件
<view>屬性: {{myBehaviorProperty}} --- {{myCompProperty}}</view>
<view>資料: {{myBehaviorData}} --- {{myCompData}}</view>
<view bind:tap="myBehaviorMethod">觸發behavior的自定義方法</view>
<view bind:tap="myCompMethod">觸發元件的自定義方法</view>
// 元件級js
import testBehavior from './testBehavior'
Component({
behaviors: [testBehavior],
properties: {
myCompProperty: {
type: String,
value: ''
}
},
data: {
myCompData: 'myCompData'
},
created: function (){
console.log('[my-component]- created')
},
attached: function (){
console.log('[my-component]- attached')
},
ready: function (){
console.log('[my-component]- ready')
},
methods: {
myCompMethod: function () {
console.log('[my-component]- method')
}
}
})
// behavior.js
export default Behavior({
behaviors: [],
properties: {
myBehaviorProperty: {
type: String,
value: 'myBehaviorProperty'
}
},
data: {
myBehaviorData: 'myBehaviorData'
},
created: function () {
console.log('[my-behavior]- created')
},
attached: function () {
console.log('[my-behavior]- attached')
},
ready: function () {
console.log('[my-behavior]- ready')
},
methods: {
myBehaviorMethod: function () {
console.log('[my-behavior]- method')
}
}
})
面試會問到的
為什麼要分包
目前小程式主包有限制大小不能超過2M,整體(包括分包)不能超過20M,對小程式分包可以最佳化首次啟動的下載時間,以及在多團隊共同開發時可以更好的解耦
頁面通訊 -- 元件高階用法
跨頁面賦值
- 類似vue中的ref,直接操控元件的屬性值
let pages = getCurrentPages() // 當前頁面棧 let prePage = pages[pages.length - 2] // 上一頁面 prevPage.setData({ // 直接給上一頁面複製 })
- 使用behavior進行資料共享
使用
wx.nativgateTo
開啟,這兩個頁面將建立一條資料通道,類似父子元件,可以透過emit
和on
互相傳送- 被開啟的頁面可以透過
this.getOpenerEventChannel()
方法來獲取一個EventChannel
物件 wx.navigateTo
的success
回撥中也包含一個EventChannel
物件- 這兩個
EventChannel
物件間可以使用emit
和on
方法互相傳送、監聽事件
seo最佳化
小程式如何seo最佳化官方文件
- 被開啟的頁面可以透過
- 配置小程式sitemap
- 小程式裡跳轉的頁面(url)可被直接開啟
- 頁面跳轉優先採用navigate元件
必要的時候才請求使用者進行授權、登入、繫結手機號登
- 開發中遇到過如果要透過企微群發公眾號頁面時,企微爬取不到頁面資訊,因為頁面使用了靜默授權,爬取頁面的時候直接跳轉到授權頁了,所以獲取不到頁面資訊
設定一個清晰的標題和頁面縮圖
效能相關
- 小程式啟動流程官方文件
- 小程式切換頁面流程官方文件
如何提升小程式效能
啟動時效能最佳化
執行時效能最佳化
- 合理使用setState
- 渲染效能最佳化:非必要不監聽滾動事件,儘量少用複雜動畫效果
- 頁面切換最佳化:避免在onHide/onUnload執行耗時操作,提前發起資料請求
- 資源載入最佳化:圖片資源控制
記憶體最佳化:分包、按需注入,記憶體分析,處理記憶體告警
- 記憶體洩漏:如果頁面示例被掛載到全域性或者其他地方還有使用到,那麼該頁面this得不到釋放,容易造成記憶體洩漏,當需要其他頁面用到該this時,在該頁面unload時需要處理手動釋放記憶體
如何進行埋點
- 手動設定自己的埋點系統(需要埋點量少的時候)
- 如果是vue、react等框架則可以在webpack中載入自己寫的babel外掛,在轉化ast這步自動插入埋點
- 小程式中可以用騰訊官方提供的埋點工具,也可以透過開發者工具自定義分析設定埋點上報