【導讀】
本文是對官方【小程序開發(fā)指南】的精簡 + 解讀春缕,幫助你更好地理解小程序算墨。理解小程序為什么出現(xiàn)橱野,理解小程序的底層原理等等逐工。
【注】
本文僅做筆記式記錄洛退,如有侵權牙寞,煩請告知忿磅。
第一部分:初識小程序
一囊陡、微信為什么要推出小程序
最初,在微信中展現(xiàn) web 頁面蛾绎,是通過 webview昆箕。
web 調用原生的一些能力,通過微信團隊未對外開放的 WeixinJSBridge租冠。
后來鹏倘,WeixinJSBridge 演變?yōu)閷ν忾_放的 JS-SDK。
JS-SDK 是對之前的 WeixinJSBrige 的一個包裝顽爹,以及新能力的釋放纤泵。
【存在痛點】
移動網頁體驗不良。
【方案 v1】
推出 JS-SDK 增強版(未對外開放)镜粤。
增加“微信 web 資源離線存儲”功能捏题。
【方案 v1 的問題】
(1)復雜頁面依然會有白屏問題。如?CSS肉渴、JavaScript 較多公荧,文件執(zhí)行會占用大量 UI 線程,造成白屏同规。
(2)緩存方案處理較為繁雜循狰,對開發(fā)者的要求較高窟社。
【愿景】
設計一個比較好的系統(tǒng),使得所有開發(fā)者在微信中都能獲得比較好的體驗绪钥。
包括:
(1)快速的加載
(2)更強大的能力(比如使用原生能力)
(3)原生的體驗
(4)易用且安全的微信數(shù)據(jù)開放
(5)高效和簡單的開發(fā)
【解決方案】
推出小程序灿里。
二、小程序與普通網頁開發(fā)的區(qū)別
四種主要區(qū)別:
UI 渲染和腳本執(zhí)行是否使用同一線程昧识、是否可進行 DOM 操作钠四、運行環(huán)境、開發(fā)依賴
1跪楞、UI 渲染和腳本執(zhí)行方式
網頁:UI 渲染和腳本執(zhí)行用同一個線程缀去,腳本執(zhí)行會阻塞 UI 渲染。
小程序:UI 渲染和邏輯執(zhí)行使用不同的線程甸祭,腳本執(zhí)行不會阻塞 UI 渲染缕碎。
2、DOM 操作
網頁:可以使用 DOM池户、BOM API咏雌,執(zhí)行 DOM、BOM 操作
小程序:邏輯層運行在 JSCore 中校焦,并沒有一個完整的瀏覽器對象赊抖,也就沒有 DOM、BOM API寨典,無法執(zhí)行?DOM氛雪、BOM 操作。
【注】
JSCore 的環(huán)境不同于瀏覽器耸成,也不同于 NodeJS报亩。因此,除了無法使用 DOM井氢、BOM API弦追,一些 NPM 包在小程序中也無法運行。
3花竞、運行環(huán)境
網頁:各種瀏覽器劲件、各式 webview
小程序:iOS、安卓约急、小程序開發(fā)者工具
4寇仓、開發(fā)依賴
網頁:編輯器、瀏覽器
小程序:小程序帳號烤宙、開發(fā)者工具
第二部分:小程序代碼組成
一、JSON 配置
JSON 文件在小程序代碼中扮演靜態(tài)配置的作用俭嘁,在小程序運行之前就決定了小程序一些表現(xiàn)躺枕。
【注意】
小程序是無法在運行過程中去動態(tài)更新 JSON 配置文件從而發(fā)生對應的變化的。
二、WXML 模板
1拐云、屬性大小寫敏感:class 和 Class 是不同的屬性。
2、變量名大小寫敏感:{{name}} 和 {{Name}} 是不同的變量湖蜕。
3提完、wx: for = "array":默認變量名 item,默認下標名 index薇缅。
4危彩、wx:key 的兩種形式:
wx: key = "uniqueKey" 或 wx:key = "*this"(代表 item 本身,要求 item 是唯一的 string / number)
5泳桦、模板:
可以在模板中定義代碼片段汤徽,然后在不同的地方調用。使用 name 屬性灸撰,作為模板的名字谒府。然后在?<template/>?內定義代碼片段。
定義模板:
<template name="msgItem">
? <view>
? ? <text> {{index}}: {{msg}} </text>
? ? <text> Time: {{time}} </text>
? </view>
</template>
使用模板:
<!--
item: {
? index: 0,
? msg: 'this is a template',
? time: '2016-06-18'
}
-->
<template is="msgItem" data="{{...item}}"/>
動態(tài)使用模板:
<template name="odd">
? <view> odd </view>
</template>
<template name="even">
? <view> even </view>
</template>
<block wx:for="{{[1, 2, 3, 4, 5]}}">
? <template is="{{item % 2 == 0 ? 'even' : 'odd'}}"/>
</block>
6浮毯、引用:
WXML 提供兩種文件引用方式 import 和 include完疫。
(1)import:用于引入 template
在 index.wxml 中 import 其他 template 文件,就可以在 index.wxml 中使用相應 template债蓝。
eg:
模板定義:
<!-- item.wxml -->
<template name="item">
? <text>{{text}}</text>
</template>
模板引用與使用:
<import src="item.wxml"/>
<template is="item" data="{{text: 'forbar'}}"/>
【注】
import 有作用域的概念壳鹤。
即:C 引用 B,B 引用A惦蚊,在C中可以使用B定義的 template器虾,在B中可以使用A定義的 template ,但是C不能使用A定義的template 蹦锋。
(2)include:用于引入任意整塊代碼
include 可以將目標文件中?除了?<template/> <wxs/>?外?的整個代碼引入兆沙,相當于是拷貝到 include 位置。
<!-- header.wxml -->
<view> header </view>
<!-- footer.wxml -->
<view> footer </view>
<!-- index.wxml -->
<include src="header.wxml"/>
<view> body </view>
<include src="footer.wxml"/>
7莉掂、所有 wxml 標簽都支持的屬性:
三葛圃、WXSS 樣式
1、rpx 單位:對于 750px 寬的設計稿憎妙,1px = 2rpx
2库正、樣式引用:@import './test_0.wxss'
【注】
和 css 樣式引用的區(qū)別:
index.css 中?@import url('./test_0.css'),請求 index.css 的時候厘唾,會多一個 test_0.css 的請求褥符。
index.wxss 中?@import './test_0.wxss',請求 index.wxss 的時候不會多一個請求抚垃,test_0.wxss 會被編譯打包到?index.wxss 中喷楣。
3趟大、官方樣式庫:WeUI
四、JavaScript 腳本
1铣焊、概述
理解 JavaScript 與 ECMAScript:
JavaScript 是 ECMAScript 一種實現(xiàn)逊朽。小程序中的 JavaScript同瀏覽器中的 JavaScript 以及 NodeJS 中的 JavaScript 是不相同的。
【注】
1曲伊、相比瀏覽器環(huán)境:小程序無法操作 DOM叽讳、BOM
2、相比 NodeJS 環(huán)境:小程序中無法加載原生庫坟募,也無法直接使用大部分的 NPM 包
2岛蚤、小程序的執(zhí)行環(huán)境
三大執(zhí)行環(huán)境:iOS平臺、Android平臺婿屹、小程序IDE
【注】
iOS9 和 iOS10 會不支持一些 es6 語法灭美。
開發(fā)中,需要用開發(fā)者工具勾選“ES6 轉 ES5”昂利,才能確保代碼在所有的環(huán)境都能得到很好的執(zhí)行届腐。
3、模塊化
導出:
// moduleA.js
module.exports = function() {}
使用:
require('./moduleA')
4蜂奸、?腳本的執(zhí)行順序
入口:app.js
執(zhí)行完 app.js 及其 require 的腳本后犁苏,小程序會按照 app.json 中定義的 pages 的順序,逐一執(zhí)行扩所。
5围详、作用域
(1)在文件中聲明的變量和函數(shù),只在該文件中有效祖屏。
(2)全局變量的聲明:
在 app.js 中聲明:
// app.js
App({
? globalData: 1
})
使用:
var app = getApp()
console.log('app.globalData:', app.globalData)? // 1
也可以在其他文件中設置全局變量:
var app = getApp()
app.addGlobalData = 'Sherry'
其他文件可訪問到?addGlobalData:
var app = getApp()
console.log('app.addGlobalData:', app.addGlobalData) // 'Sherry'
第三部分:理解小程序宿主環(huán)境
一助赞、渲染層和邏輯層
wxml、wxss 工作在渲染層袁勺,js 腳本工作在邏輯層雹食。
1、通信模型
渲染層:使用 webview 線程渲染界面期丰。一個小程序存在多個頁面群叶,所以渲染層存在多個 webview 線程。
邏輯層:使用 JsCore 線程運行 JS 腳本钝荡。
webview 線程和 JsCore 線程通信街立,通過 Native,也就是微信客戶端埠通。
2赎离、數(shù)據(jù)驅動
數(shù)據(jù)變更,視圖自動更新端辱。即:數(shù)據(jù)驅動視圖梁剔。
數(shù)據(jù)更新,上圖 JS 對象對應的節(jié)點就會發(fā)生變化憾朴。
對比前后兩個JS對象得到變化的部分,然后把這個差異應用到原來的Dom樹上喷鸽,從而達到更新UI的目的众雷,這就是“數(shù)據(jù)驅動”的原理。
【個人解讀】
所謂數(shù)據(jù)驅動做祝,即不關心視圖砾省,僅操縱數(shù)據(jù)。
邏輯層的任務混槐,僅僅是執(zhí)行用戶的 setData 操作编兄,然后把更新后的數(shù)據(jù)傳給渲染層而已。
渲染層拿到最新的數(shù)據(jù)后声登,會根據(jù)當前的 wxml 結構以及最新數(shù)據(jù)狠鸳、生成新的 JS 對象,并與當前的 JS 對象進行對比悯嗓,得到改動的地方件舵,再把差異更新到原來的 Dom 樹(形如:document.getElementById() 獲取到需要變更的真實 Dom 節(jié)點,然后更改它的屬性值 or 子節(jié)點等)脯厨。
二铅祸、程序與頁面
1、初始化
1)微信客戶端初始化宿主環(huán)境合武,同時從網絡下載或者從本地緩存中拿到小程序的代碼包临梗,把它注入到宿主環(huán)境
2)初始化完成,微信客戶端就會給 App 實例派發(fā) onLaunch 事件稼跳,onLaunch 被調用
2盟庞、退出
點擊小程序右上角的關閉,或按手機設備的Home鍵離開小程序岂贩,小程序并沒有被直接銷毀茫经。
這時,只是進入后臺狀態(tài)萎津,App 構造器定義的 onHide 方法會被調用卸伞。
3、重新進入
再次回到微信或者再次打開小程序時锉屈,小程序進入前臺狀態(tài)荤傲,App 構造器定義的 onShow 方法會被調用。
4颈渊、全局數(shù)據(jù)
// app.js
App({
? globalData: 'I am global data' // 全局共享數(shù)據(jù)
})
由于 JS 邏輯統(tǒng)一運行在邏輯層遂黍,僅有一個線程终佛。因此,頁面切換雾家,切換 webview 時铃彰,邏輯層并沒有發(fā)生改變,JS 的數(shù)據(jù)依然可以被訪問到芯咧。
【注】
也因此牙捉,在頁面中寫的 setTimeout 或者 setInterval 定時器,離開頁面時敬飒,并沒有被刪除邪铲,需要開發(fā)者手動刪除。
5无拗、頁面生命周期
(1)onLoad =>?onShow =>?onReady
onLoad带到、onReady 在頁面銷毀之前,僅觸發(fā)一次英染。
onLoad:監(jiān)聽頁面加載揽惹。
onReady:監(jiān)聽頁面初次渲染完成。
onReady 觸發(fā)時税迷,表示頁面已經準備妥當永丝,在邏輯層就可以和視圖層進行交互了。
(2)setData
this.setData 把數(shù)據(jù)傳遞給渲染層箭养,從而達到更新界面的目的慕嚷。
setData 的第二個參數(shù)是一個 callback 回調,在這次 setData 對界面渲染完畢后觸發(fā)毕泌。
【注】
1)我們只要保持一個原則就可以提高小程序的渲染性能:每次只設置需要改變的最小單位數(shù)據(jù)喝检。
如:
// page.js
Page({
? data: {
? ? a: 1, b: 2, c: 3,
? ? d: [1, {text: 'Hello'}, 3, 4]
? }
? onLoad: function(){
? ? ? // a需要變化時,只需要setData設置a字段即可
? ? this.setData({a : 2})
? }
})
this.setData({"d[1].text": 'Goodbye'});
2)不要把 data 中的任意一項的 value 設為 undefined撼泛,否則可能會有引起一些不可預料的 bug挠说。
6、小程序頁面棧
小程序宿主環(huán)境限制了頁面棧的最大層級為 10 層愿题。
wx.navigateTo({ url: 'pageD' }):插入新頁面损俭。
wx.navigateBack():銷毀最上層的頁面。
wx.redirectTo({ url: 'pageE' }):替換最上層的頁面潘酗。
wx.switchTab({ url: 'pageF' }):頁面棧先清空杆兵,然后 push pageF。變?yōu)椋篬 pageF ]
wx. reLaunch({ url: 'pageH' }) :重啟小程序仔夺,并且打開 pageH琐脏,此時頁面棧為 [ pageH ]
【注】
1)wx.navigateTo 和 wx.redirectTo 只能打開非TabBar頁面,wx.switchTab 只能打開 Tabbar 頁面。
2)Tabbar 頁面初始化之后不會被銷毀
(這句話是官方給的描述日裙,但看官方的 demo吹艇,應該只是,第一個 Tabbar 頁面初始化之后不會被銷毀)
3)銷毀了的頁面再次打開昂拂,會再次觸發(fā) onLoad受神、onShow、onReady格侯,未銷毀的頁面再次打開路克、僅觸發(fā) onShow。
三养交、事件
事件捕獲和事件冒泡觸發(fā)時序:
eg:點擊 inner view瓢宦,調用順序:handleTap2碎连、handleTap4、handleTap3驮履、handleTap1鱼辙。
<view
? id="outer"
? bind:touchstart="handleTap1"
? capture-bind:touchstart="handleTap2"
>
? outer view
? <view
? ? id="inner"
? ? bind:touchstart="handleTap3"
? ? capture-bind:touchstart="handleTap4"
? >
? ? inner view
? </view>
</view>
bind* 和 catch* 的區(qū)別:
bind 事件綁定不會阻止冒泡事件向上冒泡,catch 事件綁定可以阻止冒泡事件向上冒泡玫镐。
capture-catch 將中斷捕獲階段和取消冒泡階段倒戏。
eg:點擊 inner view,只觸發(fā) handleTap2恐似。
<view
? id="outer"
? bind:touchstart="handleTap1"
? capture-catch:touchstart="handleTap2"
>
? outer view
? <view
? ? id="inner"
? ? bind:touchstart="handleTap3"
? ? capture-bind:touchstart="handleTap4"
? >
? ? inner view
? </view>
</view>
四杜跷、兼容
1、wx.getSystemInfo 與?wx.getSystemInfoSync
使用 wx.getSystemInfo 或者 wx.getSystemInfoSync 來獲取手機品牌矫夷、操作系統(tǒng)版本號葛闷、微信版本號以及小程序基礎庫版本號等,通過這個信息双藕,我們可以針對不同平臺做差異化的服務淑趾。
2、判斷當前 api 是否存在:
if (wx.openBluetoothAdapter) {
? wx.openBluetoothAdapter()
} else {
// 如果希望用戶在最新版本的客戶端上體驗您的小程序忧陪,可以這樣子提示
? wx.showModal({
? ? title: '提示',
? ? content: '當前微信版本過低扣泊,無法使用該功能,請升級到最新微信版本后重試嘶摊。'
? })
}
3延蟹、wx.canIUse
// 判斷接口及其參數(shù)在宿主環(huán)境是否可用
wx.canIUse('openBluetoothAdapter')
wx.canIUse('getSystemInfoSync.return.screenWidth')
// 判斷組件及其屬性在宿主環(huán)境是否可用
wx.canIUse('contact-button')
wx.canIUse('text.selectable')
第四部分:場景應用
1、要兼容到iOS8以下版本更卒,需要開啟樣式自動補全等孵。(項目設置中設置)
2、一些特殊的樣式設置方式:
(1)hover 樣式(手指觸摸時的樣式)
/*page.wxss */
.hover {
? background-color: gray;
}
<!--page.wxml -->
<button hover-class="hover"> 點擊button </button>
(2)button 的 loading
<!--page.wxml -->
<button loading="{{loading}}" bindtap="tap">操作</button>
(3)彈窗
wx.showToast蹂空、wx.showModal
3俯萌、HTTPS 網絡通信
(1)小程序要求: wx.request 發(fā)起的必須是 https 協(xié)議請求果录。并且,請求的域名需要在管理平臺進行配置
(2)wx.request url 長度限制:不超過 1024字節(jié)
(3)設置超時時間:app.json ??
{
? "networkTimeout": {
? ? "request": 3000
? }
}
(4)?排查異常的方法參考:4.4.6 排查異常方法
4咐熙、微信登錄
(1)AppId 是公開信息弱恒,泄露 AppId 不會帶來安全風險
(2)code 有效期 5 分鐘。但在成功換取一次用戶信息之后棋恼,code 會立即失效
(3)開發(fā)者服務器用 code 去微信服務器換取用戶信息返弹,會拿到:
openid、session_key爪飘、unionid
其中义起,session_key 用作開發(fā)者服務器后續(xù)與微信服務器通信的憑證。
由于 session_key 有效期長于 code师崎,因此默终,可以減少多次獲取 code、換取用戶信息的通信成本犁罩。
5齐蔽、本地數(shù)據(jù)緩存:wx.getStorage / wx.getStorageSync、wx.setStorage / wx.setStorageSync
本地數(shù)據(jù)緩存是小程序存儲在當前設備硬盤上的數(shù)據(jù)床估。
(1)緩存空間隔離:
1)不同小程序的本地緩存空間相互隔離
2)不同用戶的緩存相互隔離
(2)每個小程序的緩存空間上限為10MB
(3)可以利用緩存 — 提前渲染頁面:
比如商品列表數(shù)據(jù)含滴,在用戶第一次進入小程序、請求回列表數(shù)據(jù)后丐巫,緩存到本地谈况。
用戶再次進入,直接從緩存讀取上一次的數(shù)據(jù)递胧,顯示到頁面鸦做。同時,wx.request 請求最新列表數(shù)據(jù)谓着,請求回之后泼诱,頁面重新渲染最新數(shù)據(jù)。
注意赊锚,這種性能提升方式治筒,僅適合數(shù)據(jù)實時性要求不高的場景。
(4)可以利用緩存 —?緩存用戶登錄態(tài) SessionId
按官方文檔舷蒲,建議同步手動緩存一個過期時間耸袜。
wx.setStorageSync('EXPIREDTIME',expiredTime)
使用緩存的 SessionId 前,先判斷是否過期牲平,如果過期堤框,直接 wx.login 重新登錄,減少攜帶舊 SessionId 的不必要請求。
6蜈抓、設備能力
(1)wx.scanCode:調起微信掃一掃
(2)wx.getNetworkType:獲取網絡狀態(tài)(wifi启绰、2G、3G沟使、4G委可、5G)
(3)wx.onNetworkStatusChange:動態(tài)監(jiān)聽網絡狀態(tài)變化的接口
第五部分:小程序的發(fā)布
1、發(fā)布正式版后腊嗡,正式小程序無法使用着倾,問題排查
(1)如果小程序使用到 Flex 布局,并且需要兼容 iOS8 以下系統(tǒng)時燕少,請檢查上傳小程序包時卡者,開發(fā)者工具是否已經開啟“上傳代碼時樣式自動補全”。
(2)小程序使用的服務器接口應該走 HTTPS 協(xié)議客们,并且對應的網絡域名確保已經在小程序管理平臺配置好虎眨。
(3)在測試階段不要打開小程序的調試模式進行測試,因為在調試模式下镶摘,微信不會校驗域名合法性,容易導致開發(fā)者誤以為測試通過岳守,導致正式版小程序因為遇到非法域名無法正常工作凄敢。
(4)發(fā)布前請檢查小程序使用到的網絡接口已經在現(xiàn)網部署好,并且評估好服務器的機器負載情況湿痢。
2涝缝、發(fā)布模式
全量發(fā)布和分階段發(fā)布(灰度發(fā)布)
【注】
并非全量發(fā)布之后,用戶就會立即使用到最新版的小程序譬重。
因為微信客戶端有舊版本小程序包緩存拒逮。
微信客戶端在某些特定的時機異步去更新最新的小程序包。
一般我們認為全量發(fā)布的24小時后臀规,所有用戶才會真正使用到最新版的小程序滩援。
3、數(shù)據(jù)分析
(1)常規(guī)分析
開發(fā)網頁和 App 應用都需要開發(fā)者自己通過編寫代碼來上報訪問數(shù)據(jù)塔嬉,小程序平臺則直接內置在宿主環(huán)境底層玩徊,無需開發(fā)者新增一行代碼。
開發(fā)者可以登錄小程序管理平臺谨究,通過左側“數(shù)據(jù)分析”菜單可以進入數(shù)據(jù)分析查看恩袱。
(2)自定義分析:見官網
4、監(jiān)控與告警
小程序宿主環(huán)境已經內置了異常檢測的模塊胶哲,并且上報到小程序平臺畔塔。
目前只提供了腳本錯誤告警,如果需要監(jiān)控異常的訪問或者服務接口耗時時,需要開發(fā)者自行開發(fā)監(jiān)控系統(tǒng)澈吨,并在小程序邏輯代碼加上對應的數(shù)據(jù)上報把敢。
比較推薦的方法是通過運維中心的監(jiān)控告警功能,開發(fā)者設置合理的錯誤閾值棚辽,再通過加入微信告警群技竟,當小程序運行發(fā)生大量異常現(xiàn)象時屈藐,微信告警群會提醒開發(fā)者榔组,此時開發(fā)者再登錄小程序管理平臺查閱錯誤日志。
第六部分:底層框架
一联逻、雙線程模型
1搓扯、什么是雙線程模型?
渲染層 + 邏輯層包归。
渲染層負責 UI 渲染锨推,每個頁面使用一個 webview 線程來渲染。
邏輯層負責執(zhí)行 JS 邏輯公壤。
二者通過微信客戶端(native)進行通信换可。
2、為什么是雙線程模型厦幅?
1)渲染層運行環(huán)境:確保性能
由成熟的 Web 技術渲染頁面沾鳄,并以大量的接口提供豐富的客戶端原生能力。
與邏輯層分離确憨,頁面渲染不被 JS 的執(zhí)行阻塞译荞。
2)邏輯層運行環(huán)境:確保安全
由于 web 技術的靈活性,使用 web 技術渲染頁面休弃,外部可以通過 JavaScript 操作頁面吞歼,并獲取到敏感組件的數(shù)據(jù)信息,缺乏安全性塔猾。
因此篙骡,需要一個安全的沙箱環(huán)境,去運行開發(fā)者的 JavaScript 代碼丈甸。這個沙箱環(huán)境医增,僅僅提供純 JavaScript 的解釋執(zhí)行環(huán)境。
由于客戶端系統(tǒng)都具有JavaScript 的解釋引擎(iOS 有內置?JavaScriptCore 框架老虫、安卓有騰訊 x5 內核提供的?JsCore 環(huán)境)叶骨,因此,我們可以創(chuàng)建一個單獨的線程去執(zhí)行 JavaScript祈匙。這個單獨的線程忽刽,就是小程序的邏輯層天揖。
二、組件系統(tǒng):Exparser
Exparser 是微信小程序的組件組織框架跪帝,內置在小程序基礎庫中今膊,為小程序的各種組件提供基礎的支持。小程序內的所有組件伞剑,包括內置組件和自定義組件斑唬,都由Exparser組織管理。
1黎泣、運行原理:
以 Component 為例(Page流程大致相仿恕刘,只是參數(shù)形式不一樣)
在小程序啟動時,構造器會將開發(fā)者設置的properties抒倚、data褐着、methods等定義段,寫入Exparser 的組件注冊表中。
這個組件在被其它組件引用時,就可以根據(jù)這些注冊信息來創(chuàng)建自定義組件的實例鞭执。
2、組件間通信
父 => 子:WXML 屬性值
子 => 父:事件系統(tǒng)
1)事件冒泡:
input-with-label 的 WXML:
<label>
? <input />
? <slot />
</label>
頁面 WXML:
<view>
? <input-with-label>
? ? <button />
? </input-with-label>
</view>
l 如果事件是非冒泡的馅扣,那只能在 button 上監(jiān)聽到事件;
l 如果事件是在 Shadow Tree 上冒泡的着降,那 button 差油、 input-with-label 、view 可以依次監(jiān)聽到事件鹊碍;
l 如果事件是在 Composed Tree 上冒泡的,那 button 食绿、 slot 侈咕、label 、 input-with-label 器紧、 view 可以依次監(jiān)聽到事件耀销。
【附】
Shadow Tree?對應一個組件,Composed Tree?對應一個頁面铲汪。
一個??Composed Tree?由多個?Shadow Tree?構成熊尉。
2)triggerEvent
在自定義組件中使用 triggerEvent 觸發(fā)事件時,可以指定事件的 bubbles掌腰、composed 和 capturePhase 屬性狰住,用于標注事件的冒泡性質。
triggerEvent 事例:
Component({
? methods: {
? ? helloEvent: function() {
? ? ? this.triggerEvent('hello', {}, {
? ? ? ? bubbles: true,? ? ? // 這是一個冒泡事件
? ? ? ? composed: true,? ? // 這個事件在Composed Tree 上冒泡
? ? ? ? capturePhase: false // 這個事件沒有捕獲階段
? ? ? })
? ? }
? }
})
三齿梁、原生組件:由客戶端渲染的組件(非 webview 渲染)
部分內置組件催植,是由客戶端原生渲染的肮蛹,如 vedio、map创南、canvas伦忠、picker 組件。
原生組件的層級比所有在 WebView 層渲染的普通組件要高稿辙。
限制:
一些CSS樣式無法應用于原生組件昆码。
四、小程序與客戶端通信原理
1邻储、視圖層與客戶端的通信
iOS:利用了WKWebView 的提供 messageHandlers 特性赋咽。
安卓:往 WebView 的 window 對象注入一個原生方法,最終會封裝成 WeiXinJSBridge 這樣一個兼容層芥备。
2冬耿、邏輯層與客戶端的通信
iOS:同上。另外萌壳,可以往 JavaScripCore 框架注入一個全局的原生方法
安卓:同上亦镶。
第七部分:性能優(yōu)化
一、啟動
1袱瓮、主流程
在小程序啟動時缤骨,微信會為小程序展示一個固定的啟動界面,界面內包含小程序的圖標尺借、名稱和加載提示圖標绊起。
2、代碼包下載
這里下載的燎斩,是經過編譯虱歪、壓縮、打包之后的代碼包栅表。
【性能優(yōu)化點】
控制代碼包大小?有助于減少小程序的啟動時間笋鄙。
【具體方式】
(1)精簡代碼,去掉不必要的 WXML 結構和未使用的 WXSS 定義怪瓶。
(2)減少在代碼包中直接嵌入的資源文件萧落。
(3)壓縮圖片,使用適當?shù)膱D片格式洗贰。
必要時找岖,進行分包處理。
小程序啟動時敛滋,只需要先將主包下載完成许布,就可以立刻啟動小程序。
3绎晃、代碼包加載
微信會在小程序啟動前為小程序準備好通用的運行環(huán)境爹脾。
這個運行環(huán)境包括幾個供小程序使用的線程帖旨,并在其中完成小程序基礎庫的初始化,預先執(zhí)行通用邏輯灵妨,盡可能做好小程序的啟動準備解阅。
小程序代碼包下載(或從緩存中讀取)完成后泌霍,小程序的代碼會被加載到適當?shù)木€程中執(zhí)行货抄。
加載過程???此時,所有 app.js朱转、頁面所在的 JS 文件和所有其他被 require 的 JS 文件會被自動執(zhí)行一次蟹地,小程序基礎庫會完成所有頁面的注冊。
其中藤为,頁面注冊怪与、即:在小程序代碼調用 Page 構造器的時候,基礎庫會記錄頁面的基礎信息缅疟,如初始數(shù)據(jù)(data)分别、方法等。
【注意】
如果一個頁面被多次創(chuàng)建存淫,并不會使得這個頁面所在的 JS 文件被執(zhí)行多次耘斩,而僅僅是根據(jù)初始數(shù)據(jù)多生成了一個頁面實例(this),在頁面 JS 文件 Page 構造器外定義的變量桅咆,在所有這個頁面的實例(this)間是共享的括授。
也就是說,Page 構造器外定義的變量岩饼,相當于當前頁面的全局變量荚虚,無論創(chuàng)建幾個頁面實例,訪問的都是同一個全局變量籍茧。
二版述、?頁面層級準備
在小程序啟動前,微信會提前準備好一個頁面層級用于展示小程序的首頁硕糊。
除此以外院水,每當一個頁面層級被用于渲染頁面腊徙,微信都會提前開始準備一個新的頁面層級简十。
頁面層級的準備工作(3個階段):
(1)啟動一個 WebView
(2)在 WebView 中初始化基礎庫,此時還會進行一些基礎庫內部優(yōu)化撬腾,以提升頁面渲染性能
(3)注入小程序 WXML 結構和 WXSS 樣式螟蝙,使小程序能在接收到頁面初始數(shù)據(jù)之后馬上開始渲染頁面(這一階段無法在小程序啟動前執(zhí)行)
【注】
小程序未加載時,微信客戶端根本沒拿到小程序代碼民傻,所以無法進行注入胰默。
三场斑、數(shù)據(jù)通信
數(shù)據(jù)通信性能提升原則:
(1)減少通信
(2)減少通信傳輸?shù)臄?shù)據(jù)量
1、頁面初始數(shù)據(jù)通信
一個新的頁面打開牵署,邏輯層將初始 data 發(fā)送給 Native漏隐,Native 做兩件事
1)將數(shù)據(jù)傳給視圖層
2)向用戶展示一個新的頁面層級(視圖層在這個頁面層級上進行界面繪制)
視圖層拿到數(shù)據(jù)后,根據(jù)頁面路徑來選擇合適的 WXML 結構奴迅,WXML 結構與初始數(shù)據(jù)相結合青责,得到頁面的第一次渲染結果。
【性能瓶頸】
1)頁面初始數(shù)據(jù)通信時間
2)初始渲染時間
【性能優(yōu)化點】
減少頁面初始數(shù)據(jù)通信時間
【具體方式】
減少傳輸數(shù)據(jù)量取具。
2脖隶、?更新數(shù)據(jù)通信
更新數(shù)據(jù)傳輸時,邏輯層首先執(zhí)行?JSON.stringify暇检,去除掉 setData 數(shù)據(jù)中不可傳輸?shù)牟糠植澹髮?shù)據(jù)發(fā)送給視圖層。
同時块仆,邏輯層會將 setData 與 data 合并构蹬,更新數(shù)據(jù)。
【性能優(yōu)化點】
1)setData 的調用頻率
2)setData 設置的數(shù)據(jù)大小
3)data 數(shù)據(jù)的精簡
【具體方式】
1)減少 setData 的調用榨乎,將多次 setData 合并成一次
2)精簡 setData 設置的數(shù)據(jù)怎燥,界面無關或比較復雜的長字符串、盡量不要用 setData 設置
3)與界面渲染無關的數(shù)據(jù)最好不要設置在 data 中
3蜜暑、用戶事件通信
用戶觸發(fā)一個事件铐姚,且這個事件存在監(jiān)聽函數(shù),視圖層會將觸發(fā)信息反饋給邏輯層肛捍。
如果事件沒有綁定監(jiān)聽函數(shù)隐绵,則不反饋給邏輯層。
【性能優(yōu)化點】
提升視圖層與邏輯層的通信性能拙毫。
【具體方式】
1)去掉不必要的事件綁定(WXML中的 bind 和 catch)依许,減少通信的數(shù)據(jù)量和次數(shù)
2)事件綁定時需要傳輸 target 和 currentTarget 的 dataset,因而不要在節(jié)點的 data 前綴屬性中放置過大的數(shù)據(jù)
四缀蹄、視圖層渲染
1峭跳、初始渲染
將初始數(shù)據(jù)套用在對應的 WXML 片段上生成節(jié)點樹。
【性能優(yōu)化點】
減少初始渲染時間缺前。
【具體方式】
減少 WXML 中節(jié)點的數(shù)量(時間開銷大體上與節(jié)點樹中節(jié)點的總量成正比)
2蛀醉、重渲染
初始渲染中得到的 data 和當前節(jié)點樹會保留下來用于重渲染。
【重渲染步驟】
1)將 data 和 setData 數(shù)據(jù)套用在 WXML 片段上衅码,得到一個新節(jié)點樹
2)將新節(jié)點樹與當前節(jié)點樹進行比較(diff)
3)將 setData 數(shù)據(jù)合并到 data 中拯刁,并用新節(jié)點樹替換舊節(jié)點樹
【性能優(yōu)化點】
提升 diff 過程速度。
【具體方式】
1)去掉不必要設置的數(shù)據(jù)
2)減少 setData 的數(shù)據(jù)量
五逝段、原生組件通信
一些原生組件支持使用 context 來更新組件垛玻。
與 setData 的不同:
數(shù)據(jù)從邏輯層傳到 native 層后割捅,直接傳入組件中(不需要 native => 視圖層,視圖層再傳入組件)
這種通信方式帚桩,可以顯著降低傳輸延遲亿驾。
六、性能優(yōu)化方案匯總
主要的優(yōu)化策略可以歸納為三點:
1)精簡代碼账嚎,降低 WXML 結構和 JS 代碼的復雜性
2)合理使用 setData 調用颊乘,減少 setData 次數(shù)和數(shù)據(jù)量
3)必要時使用分包優(yōu)化。
第八部分:小程序基礎庫的更新迭代
一醉锄、什么是基礎庫
1乏悄、職責
包裝提供組件、API恳不,處理數(shù)據(jù)綁定檩小、組件系統(tǒng)、事件系統(tǒng)烟勋、通信系統(tǒng)等一系列框架邏輯规求。
2、載入時機
啟動小程序后先載入基礎庫卵惦,接著再載入業(yè)務代碼阻肿。
渲染層 WebView 層注入的稱為 WebView 基礎庫,邏輯層注入的稱為 AppService 基礎庫沮尿。
WebView 基礎庫 +?AppService 基礎庫 ==?小程序基礎庫丛塌。
由于所有小程序在微信客戶端打開的時候,都需要注入相同的基礎庫 畜疾,所以赴邻,小程序的基礎庫不會被打包在某個小程序的代碼包里邊,它會被提前內置在微信客戶端啡捶。
這樣做的好處:
1)降低業(yè)務小程序的代碼包大小姥敛。
2)可以單獨修復基礎庫中的 Bug,無需修改到業(yè)務小程序的代碼包瞎暑。
二彤敛、基礎庫的異常捕獲
【可選方案】
1、try-catch方案了赌。
2墨榄、window.onerror方案(window.addEventListener("error", function(evt){}))
【注】
1)對比 window.onerror 的方案,try-catch 的方案有個缺點:沒法捕捉到全局的錯誤事件揍拆。
2)邏輯層不存在 window 對象渠概,因此邏輯層 AppService 側無法通過 window.onerror 來捕捉異常茶凳。
【最終方案】
1)在 WebView 側使用 window.onerror 方案進行捕捉異常嫂拴。
2)在邏輯層 AppService 側通過把 App 實例和 Page 實例的各個生命周期等方法包裹在 try-catch 里進行捕捉異常播揪。
3)在 App 構造器里提供了 onError 的回調,當業(yè)務代碼運行產生異常時筒狠,這個回調被觸發(fā)猪狈,同時能夠拿到異常的具體信息。
第九部分:微信開發(fā)者工具
一辩恼、代碼編譯
微信開發(fā)者工具和微信客戶端都無法直接運行小程序的源碼雇庙,因此我們需要對小程序的源碼進行編譯。
代碼編譯過程:
1)本地預處理
2)本地編譯
3)服務器編譯
微信開發(fā)者工具模擬器運行的代碼只經過本地預處理灶伊、本地編譯疆前,沒有服務器編譯過程。
微信客戶端運行的代碼是額外經過服務器編譯的聘萨。
1竹椒、WXML 編譯:WXML =>?JavaScript
微信開發(fā)者工具內置了一個二進制的 WXML 編譯器,這個編譯器接受 WXML 代碼文件列表米辐,處理完成之后輸出 JavaScript 代碼胸完,這段代碼是各個頁面的結構生成函數(shù)。
編譯過程將所有的 WXML 代碼最終變成一個 JavaScript 函數(shù)翘贮,預先注入在 WebView 中赊窥。
(注意,是所有的 WXML狸页,所有的锨能,統(tǒng)一變成一個?JavaScript 函數(shù),叫做:“頁面結構生成函數(shù)”)
這個函數(shù)接收頁面路徑(pagePath)作為參數(shù)芍耘,返回“頁面結構生成函數(shù)”腹侣,“頁面結構生成函數(shù)”接受頁面數(shù)據(jù)(pageData)作為參數(shù),輸出一段描述頁面結構的 JSON齿穗。
最終傲隶,通過小程序組件系統(tǒng)生成對應的 HTML。
頁面結構生成函數(shù)的使用:
//$gwx 是 WXML 編譯后得到的函數(shù)
//根據(jù)頁面路徑獲取頁面結構生成函數(shù)
var generateFun = $gwx('name.wxml')
//頁面結構生成函數(shù)接受頁面數(shù)據(jù)窃页,得到描述頁面結構的JSON
var virtualTree = generateFun({
? name:? 'miniprogram'
})
/** virtualTree == {
? tag: 'view'跺株,
? children: [{
? ? ? tag: 'view',
? ? ? children: ['miniprogram']
? ? }]
}**/
//小程序組件系統(tǒng)在虛擬樹對比后將結果渲染到頁面上
virtualDom.render(virtualTree)
2、WXSS 編譯:WXSS => 樣式信息數(shù)組
微信開發(fā)者工具內置了一個二進制的 WXSS 編譯器脖卖,這個編譯器接受 WXSS 文件列表乒省,分析文件之間的引用關系,同時預處理 rpx畦木,輸出一個樣式信息數(shù)組袖扛。
3、JavaScript 編譯:多個 js =>?app-service.js
微信客戶端在運行小程序的邏輯層的時候只需要加載一個 JS 文件(我們稱為 app-service.js)。
也就是蛆封,我們小程序代碼經過編譯(es6 => es5)唇礁、打包之后,得到的 bundle.js惨篱。
【具體步驟】
1)代碼上傳之前的編譯盏筐、壓縮(預處理)
在代碼上傳之前,微信開發(fā)者工具會對開發(fā)者的 JS 文件做一些預處理砸讳,包括 ES6 轉 ES5 和代碼壓縮(開發(fā)者可以選擇關閉預處理操作)琢融,然后上傳(上傳的還是多個 JS 文件)
2)服務器編譯、打包
將每個 JS 文件的內容分別包裹在 define 域中簿寂,再按一定的順序合并成 app-service.js 漾抬。
同時,添加主動 require app.js 和頁面 JS 的代碼常遂。
二、模擬器
1烈钞、?邏輯層模擬
邏輯層的真實運行環(huán)境:
iOS:JavaScriptCore 中
安卓:x5 的 JSCore 中
模擬運行環(huán)境:
微信開發(fā)者工具:采用一個隱藏著的 Webivew 來模擬小程序的邏輯運行環(huán)境
在微信開發(fā)者工具上 WebView 是一個chrome的?<webview />?標簽泊碑。與<iframe />標簽不同的是,<webview/> 標簽是采用獨立的線程運行的毯欣。
WebView 在請求開發(fā)者 JS 代碼時馒过,開發(fā)者工具讀取 JS 代碼進行必要的預處理后,將處理結果返回酗钞。
然后腹忽,由 WebView 解析執(zhí)行(這樣,邏輯層的 JS 文件砚作,就得到了執(zhí)行)窘奏。
雖然開發(fā)者工具上是沒有對 JS 代碼進行合并的,但是還是按照相同的加載順序進行解析執(zhí)行葫录。
【更好的模擬】
WebView 是一個瀏覽器環(huán)境着裹,支持 BOM 操作。但邏輯層本不該支持這種操作米同,一旦出現(xiàn)骇扇,我們需要正確報錯才對。
因此面粮,開發(fā)者工具將開發(fā)者的代碼包裹在 define 域的時候少孝,將瀏覽器的 BOM 對象局部變量化,從而使得在開發(fā)階段就能發(fā)現(xiàn)問題熬苍。
2稍走、 渲染層模擬
微信開發(fā)者工具使用 chrome 的?<webview />標簽來加載渲染層頁面。
每個渲染層 WebView 加載:
http://127.0.0.1:9973/pageframe/pageframe.html
開發(fā)者工具底層搭建的 HTTP 本地服務器在收到這個請求的時候,就會編譯 WXML 文件和 WXSS 文件婿脸,然后將編譯結果作為 HTTP 請求的返回包粱胜。
當確定加載頁面的路徑之后,如 index 頁面盖淡,開發(fā)工具會動態(tài)注入如下一段腳本:
// 改變當前 webview 的路徑,確保之后的圖片網絡請求能得到正確的相對路徑
history.pushState('', '', 'pageframe/index')
// 創(chuàng)建自定義事件凿歼,將頁面結構生成函數(shù)派發(fā)出去褪迟,由小程序渲染層基礎庫處理
document.dispatchEvent(new CustomEvent("generateFuncReady", {
? detail: {
? ? generateFunc: $gwx('./index.wxml')
? }
}))
// 注入對應頁面的樣式,這段函數(shù)由 WXSS 編譯器生成
setCssToHead()
3答憔、客戶端模擬
在微信開發(fā)者工具上味赃,通過借助 BOM(瀏覽器對象模型)以及 node.js 訪問系統(tǒng)資源的能力,同時模擬客戶端的 UI 和交互流程虐拓,使得大部分的 API 能夠正常執(zhí)行心俗。
4、通訊模擬
我們需要一個有效的通訊方案使得小程序的邏輯層蓉驹、渲染層和客戶端之間進行數(shù)據(jù)交流城榛,才能將這三個部分串聯(lián)成為一個有機的整體。
【原理】
微信開發(fā)者工具的有一個消息中心底層模塊維持著一個 WebSocket 服務器态兴,小程序的邏輯層的 WebView 和渲染層頁面的 WebView 通過 WebSocket 與開發(fā)者工具底層建立長連狠持,使用 WebSocket 的 protocol 字段來區(qū)分 Socket 的來源。
三瞻润、調試器:界面調試 + 邏輯調試
nw.js 對 <webview/> 提供打開 Chrome Devtools 調試界面的接口喘垂,使得開發(fā)者工具具備對小程序的邏輯層和渲染層進行調試的能力。
同時绍撞,為了方便調試小程序正勒,開發(fā)者工具在 Chrome Devtools 的基礎上進行擴展和定制。
【界面調試】
微信小程序團隊通過腳本注入的方式傻铣、將 Chrome Devtools 的 Element 面板隱藏章贞,同時開發(fā)了 Chrome Devtools 插件 WXML 面板。
開發(fā)者工具會在每個渲染層的 WebView 中注入界面調試的腳本代碼非洲,負責獲取 WebView 中的 DOM 樹阱驾、獲取節(jié)點樣式、監(jiān)聽節(jié)點變化怪蔑、高亮選中節(jié)點里覆、處理界面調試命令。并將界面調試信息通過 WebSocket 經由開發(fā)者工具轉發(fā)給 WXML 面板進行處理缆瓣。
【邏輯調試】
直接使用 Chrome Devtools 的 Sources 面板調試邏輯層 JS 代碼喧枷。
四、微信開發(fā)者工具原理總結
1、渲染層
通過編譯過程我們將 WXML 文件和 WXSS 文件都處理成 JS 代碼隧甚,使用 script 標簽注入在一個空的 html文件中(我們稱為:page-frame.html)
2车荔、邏輯層
我們將所有的 JS 文件編譯成一個單獨的 app-service.js
3、小程序運行時
1)邏輯層使用 JsCore 直接加載 app-service.js戚扳,渲染層使用 WebView 加載 page-frame.html
2)在確定頁面路徑之后忧便,通過動態(tài)注入 script 的方式調用 WXML 文件和 WXSS 文件生成的 JS 代碼,再結合邏輯層的頁面數(shù)據(jù)帽借,最終渲染出指定的頁面
4珠增、開發(fā)者工具使用一個隱藏著的 <webview/> 標簽來模擬 JSCore 作為小程序的邏輯層運行環(huán)境
5、開發(fā)者工具利用 BOM砍艾、node.js 以及模擬的 UI 和交互流程實現(xiàn)對大部分客戶端 API 的支持
6蒂教、開發(fā)者工具底層有一個 HTTP 服務器來處理來自 WebView 的請求,并將開發(fā)者代碼編譯處理后的結果作為 HTTP 請求的返回脆荷,WebView 按照普通的網頁進行渲染
7凝垛、開發(fā)者工具底層維護著一個 WebSocket 服務器,用于在 WebView 與開發(fā)者工具之間建立可靠的消息通訊鏈路
8蜓谋、微信開發(fā)者工具使用 webview.showDevTools 打開 Chrome Devtools 調試邏輯層 WebView 的 JS 代碼
9梦皮、微信小程序團隊開發(fā)了 Chrome Devtools 插件 WXML 面板對渲染層頁面 WebView 進行界面調試