小程序的運行環境分紅渲染層和邏輯層:WXML 模板和 WXSS 樣式工做在渲染層,JS 腳本工做在邏輯層。渲染層和數據相關;邏輯層負責產生、處理數據,經過 Page 實例的 data 屬性傳遞數據到渲染層。php
小程序的渲染層和邏輯層分別由2個線程管理:渲染層的界面使用了WebView 進行渲染;邏輯層採用JsCore線程運行JS腳本。一個小程序存在多個界面,因此渲染層存在多個WebView線程,這兩個線程的通訊會經由微信客戶端(下文中也會採用Native來代指微信客戶端)作中轉,邏輯層發送網絡請求也經由Native轉發,小程序的通訊模型以下圖所示:html
從邏輯組成來講,一個小程序是由多個「頁面」組成的「程序」。ios
「小程序」指的是產品層面的程序,而「程序」指的是代碼層面的程序實例。json
宿主環境提供了 App() 構造器用來註冊一個程序App。App() 構造器必須寫在項目根目錄的app.js裏,App實例是單例對象,在其餘JS腳本中可使用宿主環境提供的 getApp() 來獲取程序實例。小程序
1 // other.js 2 var appInstance = getApp()
App() 的調用方式如上所示,App構造器接受一個Object參數,參數說明以下:api
1 App({ 2 onLaunch: function(options) {}, 3 onShow: function(options) {}, 4 onHide: function() {}, 5 onError: function(msg) {}, 6 globalData: 'I am global data' 7 })
其中onLaunch / onShow / onHide 三個回調是App實例的生命週期函數;當小程序發生腳本錯誤,或者 API 調用失敗時,會觸發 onError 方法並帶上錯誤信息;globalData 用於存放App實例的數據,能夠添加任意的函數或數據到 Object 參數中,在App實例回調用 this 能夠訪問。數組
爲了不程序上的混亂,咱們不該該從其餘代碼裏主動調用App實例的生命週期函數。
在微信客戶端中打開小程序有不少途徑:從羣聊會話裏打開,從小程序列表中打開,經過微信掃一掃二維碼打開,從另一個小程序打開當前小程序等,針對不一樣途徑的打開方式,小程序有時須要作不一樣的業務處理,因此微信客戶端會把打開方式帶給onLaunch和onShow的調用參數options。要獲取最新的場景值說明請查看官方文檔:https://mp.weixin.qq.com/debug/wxadoc/dev/framework/app-service/app.html。緩存
1 App({ 2 onLaunch: function(options) { console.log(options) }, 3 onShow: function(options) { console.log(options) } 4 })
options屬性說明以下:微信
屬性 | 類型 | 說明 |
path | String | 打開小程序的頁面路徑 |
query | Object | 打開小程序的頁面參數query |
scene | Number | 打開小程序的場景值,詳細場景值請參考小程序官方文檔 |
shareTicket | String | shareTicket,詳見小程序官方文檔 |
referrerInfo | Object | 當場景爲由從另外一個小程序或公衆號或App打開時,返回此字段 |
referrerInfo.appId | String | 來源小程序或公衆號或App的 appId,詳見下方說明 |
referrerInfo.extraData | Object | 來源小程序傳過來的數據,scene=1037或1038時支持 |
前邊咱們提到,小程序的JS腳本是運行在JsCore的線程裏,小程序的每一個頁面各自有一個WebView線程進行渲染,因此小程序切換頁面時,小程序邏輯層的JS腳本運行上下文依舊在同一個JsCore線程中。同時App實例又是單例的,所以不一樣頁面直接能夠經過App實例下的屬性來共享數據。網絡
1 // app.js 2 App({ 3 globalData: 'I am global data' // 全局共享數據 4 }) 5 // 其餘頁面腳本other.js 6 var appInstance = getApp() 7 console.log(appInstance.globalData) // 輸出: I am global data
注意:全部頁面的腳本邏輯都跑在同一個JsCore線程,頁面使用setTimeout或者setInterval的定時器,而後跳轉到其餘頁面時,這些定時器並無被清除,須要開發者本身在頁面離開的時候進行清理。
一個小程序能夠有不少頁面,每一個頁面承載不一樣的功能,頁面之間能夠互相跳轉。
一個頁面是分三部分組成:界面、配置和邏輯。界面由WXML文件和WXSS文件來負責描述,配置由JSON文件進行描述,頁面邏輯則是由JS腳本文件負責。一個頁面的文件須要放置在同一個目錄下,其中WXML文件和JS文件是必須存在的,JSON和WXSS文件是可選的
頁面路徑須要在小程序代碼根目錄app.json中的pages字段聲明,不然這個頁面不會被註冊到宿主環境中。腳本中的路徑採用相對路徑,app.json的pages字段的代碼路徑以pages文件夾開始。
1 { 2 "pages":[ 3 "pages/index/page", // 第一項默認爲首頁 4 "pages/other/other" 5 ] 6 }
宿主環境提供了 Page() 構造器用來註冊一個小程序頁面,Page()在頁面腳本page.js中調用。Page構造器接受一個Object參數,參數說明以下:
1 Page({ 2 data: { text: "This is page data." }, 3 onLoad: function(options) { }, 4 onReady: function() { }, 5 onShow: function() { }, 6 onHide: function() { }, 7 onUnload: function() { }, 8 onPullDownRefresh: function() { }, 9 onReachBottom: function() { }, 10 onShareAppMessage: function () { }, 11 onPageScroll: function() { } 12 })
其中data屬性是當前頁面WXML模板中能夠用來作數據綁定的初始數據;onLoad / onReady / onShow / onHide /onUnload 5個回調是Page實例的生命週期函數;onPullDownRefresh / onReachBottom / onShareAppMessage / onPageScroll 4個回調是頁面的用戶行爲。具體說明以下表:
參數屬性 | 類型 | 描述 |
---|---|---|
data | Object | 頁面的初始數據 |
onLoad | Function | 生命週期函數--監聽頁面加載,觸發時機早於onShow和onReady |
onReady | Function | 生命週期函數--監聽頁面初次渲染完成 |
onShow | Function | 生命週期函數--監聽頁面顯示,觸發事件早於onReady |
onHide | Function | 生命週期函數--監聽頁面隱藏 |
onUnload | Function | 生命週期函數--監聽頁面卸載 |
onPullDownRefresh | Function | 頁面相關事件處理函數--監聽用戶下拉動做 |
onReachBottom | Function | 頁面上拉觸底事件的處理函數 |
onShareAppMessage | Function | 用戶點擊右上角轉發 |
onPageScroll | Function | 頁面滾動觸發事件的處理函數 |
其餘 | Any | 能夠添加任意的函數或數據,在Page實例的其餘函數中用 this 能夠訪問 |
以上三個事件觸發的時機是onLoad早於 onShow,onShow早於onReady
用於傳遞上一個頁面傳遞到下一個頁面的參數
1 // pages/list/list.js 2 // 列表頁使用navigateTo跳轉到詳情頁 3 wx.navigateTo({ url: 'pages/detail/detail?id=1&other=abc' }) 4 5 // pages/detail/detail.js 6 Page({ 7 onLoad: function(option) { 8 console.log(option.id) 9 console.log(option.other) 10 } 11 })
頁面的打開路徑定義被定義爲頁面URL,其組成格式和網頁的URL相似,在頁面路徑後使用英文 ? 分隔path和query部分,query部分的多個參數使用 & 進行分隔,參數的名字和值使用 key=value 的形式聲明。在頁面Page構造器裏onLoad的option能夠拿到當前頁面的打開參數,其類型是一個Object,其鍵值對與頁面URL上query鍵值對一一對應。和網頁URL同樣,頁面URL上的value若是涉及特殊字符(例如:&字符、?字符、中文字符等,詳情參考URI的RFC3986說明 ),須要採用UrlEncode後再拼接到頁面URL上。
WXML能夠經過數據綁定的語法綁定從邏輯層傳遞過來的數據字段,來自於頁面Page構造器的data字段,data參數是頁面第一次渲染時從邏輯層傳遞到渲染層的數據。
宿主環境所提供的Page實例的原型中有setData函數,咱們能夠在Page實例下的方法調用this.setData把數據傳遞給渲染層,從而達到更新界面的目的。因爲小程序的渲染層和邏輯層分別在兩個線程中運行,因此setData傳遞數據實際是一個異步的過程,因此setData的第二個參數是一個callback回調,在此次setData對界面渲染完畢後觸發。setData其通常調用格式是 setData(data, callback),其中data是由多個key: value構成的Object對象。
1 // page.js 2 Page({ 3 onLoad: function(){ 4 this.setData({ 5 text: 'change data' 6 }, function(){ 7 // 在此次setData對界面渲染完畢後觸發 8 }) 9 } 10 })
實際開發中,一般只須要設置須要改變的屬性便可。
1 // page.js 2 Page({ 3 data: { 4 a: 1, b: 2, c: 3, 5 d: [1, {text: 'Hello'}, 3, 4] 6 } 7 onLoad: function(){ 8 // a須要變化時,只須要setData設置a字段便可 9 this.setData({a : 2}) 10 } 11 })
宿主環境提供了四個和頁面相關的用戶行爲回調:
用戶轉發 onShareAppMessage
只有定義了此事件處理函數,右上角菜單纔會顯示「轉發」按鈕,在用戶點擊轉發按鈕的時候會調用,此事件須要return一個Object,包含title和path兩個字段,用於自定義轉發內容。
一個小程序擁有多個頁面,咱們能夠經過wx.navigateTo推入一個新的頁面。在首頁使用2次wx.navigateTo後,頁面層級會有三層,咱們把這樣的一個頁面層級稱爲頁面棧。以下圖所示:
爲了表述方便,咱們採用這樣的方式進行描述頁面棧:[ pageA, pageB, pageC ],其中pageA在最底下,pageC在最頂上。宿主環境限制了頁面棧的最大層級爲10層,也就是當頁面棧到達10層以後就沒有辦法再推入新的頁面了。
使用 wx.navigateTo({ url: 'pageD' }) 能夠往當前頁面棧多推入一個 pageD,此時頁面棧變成 [ pageA, pageB, pageC, pageD ]。
使用 wx.navigateBack() 能夠退出當前頁面棧的最頂上頁面,此時頁面棧變成 [ pageA, pageB, pageC ]。
使用wx.redirectTo({ url: 'pageE' }) 是替換當前頁變成pageE,此時頁面棧變成 [ pageA, pageB, pageE ],當頁面棧到達10層無法再新增的時候,每每就是使用redirectTo這個API進行頁面跳轉。
小程序提供了原生的Tabbar支持,咱們能夠在app.json聲明tabBar字段來定義Tabbar頁(注:更多詳細參數見Tabbar官方文檔 )。
1 { 2 "tabBar": { 3 "list": [ 4 { "text": "Tab1", "pagePath": "pageA" }, 5 { "text": "Tab1", "pagePath": "pageF" }, 6 { "text": "Tab1", "pagePath": "pageG" } 7 ] 8 } 9 }
在剛剛的例子所在的頁面棧中使用wx.switchTab({ url: 'pageF' }),此時原來的頁面棧會被清空(除了已經聲明爲Tabbar頁pageA外其餘頁面會被銷燬),而後會切到pageF所在的tab頁面,頁面棧變成 [ pageF ],此時點擊Tab1切回到pageA時,pageA不會再觸發onLoad,由於pageA沒有被銷燬。
wx.navigateTo和wx.redirectTo只能打開非TabBar頁面,wx.switchTab只能打開Tabbar頁面。咱們還可使用 wx. reLaunch({ url: 'pageH' }) 重啓小程序,而且打開pageH,此時頁面棧爲 [ pageH ]。
路由方式 | 觸發時機 | 路由前頁面生命週期 | 路由後頁面生命週期 |
---|---|---|---|
初始化 | 小程序打開的第一個頁面 | onLoad, onShow | |
打開新頁面 調用 | API wx.navigateTo | onHide | onLoad, onShow |
頁面重定向 調用 | API wx.redirectTo | onUnload | onLoad, onShow |
頁面返回 調用 | API wx.navigateBack | onUnload | onShow |
Tab | 切換 調用 API wx.switchTab | 請參考表3-6 | 請參考表3-6 |
重啓動 | 調用 API wx.reLaunch | onUnload | onLoad, onShow |
組件就是小程序頁面的基本組成單元。組件是在WXML模板文件聲明中使用的,WXML的語法和HTML語法類似,小程序使用標籤名來引用一個組件,一般包含開始標籤和結束標籤,該標籤的屬性用來描述該組件。
1 <!-- page.wxml --> 2 <image mode="scaleToFill" src="img.png"></image>
全部組件都擁有下表列舉的屬性:
屬性名 | 類型 | 描述 | 其餘說明 |
---|---|---|---|
id | String | 組件的惟一標示 | 保持整個頁面惟一 |
class | String | 組件的樣式類 | 在對應的WXSS中定義的樣式類 |
style | String | 組件的內聯樣式 | 能夠經過數據綁定進行動態設置的內聯樣式 |
hidden | Boolean | 組件是否顯示 | 全部組件默認顯示 |
data-* | Any | 自定義屬性 | 組件上觸發的事件時,會發送給事件處理函數 |
bind / catch | EventHandler | 事件 | 詳情見3.5節 |
更多相關屬性,請在使用時前往官方文檔進行查閱相關組件說明 https://mp.weixin.qq.com/debug/wxadoc/dev/component/。
幾乎全部小程序的API都掛載在wx對象(小程序的宿主環境所提供的全局對象)底下(除了Page/App等特殊的構造器),因此本書談到API概念時,一般指的是wx對象底下的方法。
小程序提供的API按照功能主要分爲幾大類:網絡、媒體、文件、數據緩存、位置、設備、界面、界面節點信息還有一些特殊的開放接口,咱們介紹一下API通常調用的約定:
1 wx.request({ 2 url: 'test.php', 3 data: {}, 4 header: { 'content-type': 'application/json' }, 5 success: function(res) { 6 // 收到https服務成功後返回 7 console.log(res.data) 8 }, 9 fail: function() { 10 // 發生網絡錯誤等狀況觸發 11 }, 12 complete: function() { 13 // 成功或者失敗後觸發 14 } 15 })
能夠在官方API文檔 https://mp.weixin.qq.com/debug/wxadoc/dev/api/瞭解到對應的API參數細節。
咱們把「用戶在渲染層的行爲反饋」以及「組件的部分狀態反饋」抽象爲渲染層傳遞給邏輯層的「事件」。
常見的事件類型:
類型 | 觸發條件 |
---|---|
touchstart | 手指觸摸動做開始 |
touchmove | 手指觸摸後移動 |
touchcancel | 手指觸摸動做被打斷,如來電提醒,彈窗 |
touchend | 手指觸摸動做結束 |
tap | 手指觸摸後立刻離開 |
longpress | 手指觸摸後,超過350ms再離開,若是指定了事件回調函數並觸發了這個事件,tap事件將不被觸發 |
longtap | 手指觸摸後,超過350ms再離開(推薦使用longpress事件代替) |
transitionend | 會在 WXSS transition 或 wx.createAnimation 動畫結束後觸發 |
animationstart | 會在一個 WXSS animation 動畫開始時觸發 |
animationiteration | 會在一個 WXSS animation 一次迭代結束時觸發 |
animationend | 會在一個 WXSS animation 動畫完成時觸發 |
當事件回調觸發的時候,會收到一個事件對象,對象的詳細屬性以下表所示:
屬性 | 類型 | 說明 |
---|---|---|
type | String | 事件類型 |
timeStamp | Integer | 頁面打開到觸發事件所通過的毫秒數 |
target | Object | 觸發事件的組件的一些屬性值集合 |
currentTarget | Object | 當前組件的一些屬性值集合 |
detail | Object | 額外的信息 |
touches | Array | 觸摸事件,當前停留在屏幕中的觸摸點信息的數組 |
changedTouches | Array | 觸摸事件,當前變化的觸摸點信息的數組 |
事件綁定的寫法和組件屬性一致,以key="value"的形式,其中:
如下示例中,點擊 inner view 會前後調用handleTap一、handleTap二、handleTap三、handleTap4。
1 <view id="outer" bind:tap="handleTap4" capture-bind:tap="handleTap1"> 2 outer view 3 <view id="inner" bind:tap="handleTap3" capture-bind:tap="handleTap2"> 4 inner view 5 </view> 6 </view>
bind事件綁定不會阻止冒泡事件向上冒泡,catch事件綁定能夠阻止冒泡事件向上冒泡。若是將以上代碼的capture-bind:tap="handleTap1"改爲capture-catch:tap="handleTap1",點擊inner view只會觸發handleTap1(catch事件阻止了tap事件冒泡)。
1 <view id="outer" bind:tap="handleTap4" capture-catch:tap="handleTap1"> 2 outer view 3 <view id="inner" bind:tap="handleTap3" capture-bind:tap="handleTap2"> 4 inner view 5 </view> 6 </view>
注意:除前邊表中列舉的事件類型以外的其餘組件自定義事件,如無特殊聲明都是非冒泡事件,如<form/>的submit事件,<input/>的input事件,<scroll-view/>的scroll事件。
針對不一樣手機進行程序上的兼容,此時可使用 wx.getSystemInfo 或者 wx.getSystemInfoSync 來獲取手機品牌、操做系統版本號、微信版本號以及小程序基礎庫版本號等,經過這個信息,咱們能夠針對不一樣平臺作差別化的服務。
能夠經過wx.getSystemInfoSync獲取宿主環境信息:
1 wx.getSystemInfoSync() 2 /* 3 { 4 brand: "iPhone", // 手機品牌 5 model: "iPhone 6", // 手機型號 6 platform: "ios", // 客戶端平臺 7 system: "iOS 9.3.4", // 操做系統版本 8 version: "6.5.23", // 微信版本號 9 SDKVersion: "1.7.0", // 小程序基礎庫版本 10 language: "zh_CN", // 微信設置的語言 11 pixelRatio: 2, // 設備像素比 12 screenWidth: 667, // 屏幕寬度 13 screenHeight: 375, // 屏幕高度 14 windowWidth: 667, // 可以使用窗口寬度 15 windowHeight: 375, // 可以使用窗口高度 16 fontSizeSetting: 16 // 用戶字體大小設置 17 } 18 */
經過判斷API是否存在作兼容:
1 if (wx.openBluetoothAdapter) { 2 wx.openBluetoothAdapter() 3 } else { 4 // 若是但願用戶在最新版本的客戶端上體驗您的小程序,能夠這樣子提示 5 wx.showModal({ 6 title: '提示', 7 content: '當前微信版本太低,沒法使用該功能,請升級到最新微信版本後重試。' 8 }) 9 }
小程序還提供了wx.canIUse這個API,用於判斷接口或者組件在當前宿主環境是否可用,其參數格式爲: ${API}.${method}.${param}.${options}或者${component}.${attribute}.${option}
各個段的含義以下:
1 // 判斷接口及其參數在宿主環境是否可用 2 wx.canIUse('openBluetoothAdapter') 3 wx.canIUse('getSystemInfoSync.return.screenWidth') 4 wx.canIUse('getSystemInfo.success.screenWidth') 5 wx.canIUse('showToast.object.image') 6 wx.canIUse('onCompassChange.callback.direction') 7 wx.canIUse('request.object.method.GET') 8 9 // 判斷組件及其屬性在宿主環境是否可用 10 wx.canIUse('contact-button') 11 wx.canIUse('text.selectable') 12 wx.canIUse('button.open-type.contact')
咱們能夠選擇合適的判斷方法來作小程序的向前兼容,以保證咱們的小程序在舊版本的微信客戶端也能工做正常。在不得已的狀況下(小程序強依賴某個新的API或者組件時),還能夠經過在小程序管理後臺設置「基礎庫最低版本設置」來達到不向前兼容的目的。例如你選擇設置你的小程序只支持1.5.0版本以上的宿主環境,那麼當運行着1.4.0版本宿主環境的微信用戶打開你的小程序的時候,微信客戶端會顯示當前小程序不可用,而且提示用戶應該去升級微信客戶端。