問題1:
在微信小程序開發過程中,在獲取userinfo或其他異步處理函數的過程中採用this碰到一些問題,問題如下:
我的login的success回調函數如下:
success: function(res) {
if(res.statusCode == 200) {
console.log(res.data)
this.globalData.sessionId = res.data.sessionId
}
運行時,此處的this會報錯如下:
Cannot read property 'globalData' of undefined;at App getData function;at api operateWXData success callback function
具體截圖如下圖所示:
然後我查看了app.js另外一處的success回調函數
success: res => {
// 可以將 res 發送給後臺解碼出 unionId
this.globalData.userInfo = res.userInfo
// 由於 getUserInfo 是網絡請求,可能會在 Page.onLoad 之後才返回
// 所以此處加入 callback 以防止這種情況
if (this.userInfoReadyCallback) {
this.userInfoReadyCallback(res)
}
而此處的this有效,我懷疑是success: function(res) {} 和 success: res => {}的區別,於是我修改了第一處的代碼爲:
success: res => {
if(res.statusCode == 200) {
console.log(res.data)
this.globalData.sessionId = res.data.sessionId
}
執行成功!如果不想修改回調函數,可以在函數定義之前增加var that = this,然後此處修改this爲that即可。
分析原因:
此處代碼中的res =>{ }與普通的function(res){}函數定義功能相同,其中res即爲形參對象,但前者不同的是針對this的處理上。前者是採用的是ECMAScript 6.0(關於ES 6.0與Javascript之間的關係詳見:https://blog.csdn.net/u012331525/article/details/80517488)的新特性:箭頭函數(關於箭頭函數的詳細介紹詳見:https://blog.csdn.net/weixin_41593408/article/details/86771813)。
箭頭函數有以下幾點注意事項:
- 函數體內的
this
對象就是定義時所在的對象,而不是調用時所在的對象 - 不可以作爲構造函數
- 不可以使用
arguments
對象 - 不可以使用yield命令,因此箭頭函數不能用作 Generator 函數
普通函數如下代碼:
var obj = {
x:1,
func: function(){ console.log(this.x) }, // 此處的 this 代表 obj
test: function(){
setTimeout(function(){
alert(this); // 因爲使用了異步,在運行過程中,this發生了指針轉移,不再指向obj,而是指向全局 Window對象
this.func();
},1000);
}
};
obj.test();
//報錯
// TypeError: this.func is not a function
上述代碼中,在test函數中定義了一個延時調用函數,其實際執行時間爲1000ms之後,此時test已經執行完畢,當延時函數執行時,this值爲全局window對象,而全局的window對象不存在func函數,所以報錯。
箭頭函數代碼如下:
var obj = {
x: 1,
func: function(){ console.log(this.x) },
test: function(){
setTimeout(() => {
alert(this); // [object Object] 此處的this指向obj
this.func();
},1000);
}
};
obj.test(); // 這回this就指向obj了
同樣在test中定義延時調用,但由於箭頭函數特性,使其this值總是指向函數定義生效時所在對象,此處即爲obj,所以不會出現上述錯誤。其本質要點在與實際上,箭頭函數自身沒有自己的this,導致其內部的this就是外層代碼塊的this
在微信小程序的開發工具中,已經有ES6語法轉換ES5的設置,如下:
問題2:關於異步加載數據及回調函數(Callback)理解
小程序中大部分網絡交互的接口實現都是異步的,因此在寫小程序時,不可避免的就必須和異步進行打交道。微信小程序基本是通過Javascript+wxss+wxml(類html)組合而成,小程序中的主體邏輯是用js實現的。對於精通前端技術的人來說,javascript中的promise、callback應該是非常熟悉了,這部分我來說說自己對小程序中如何使用callback及promise實現異步處理和回調的理解及總結。我們以微信小程序中獲取用戶信息userInfoReadyCallback爲例來說明:
app.js
App({
onLaunch: function () {
// 展示本地存儲能力
var logs = wx.getStorageSync('logs') || []
logs.unshift(Date.now())
wx.setStorageSync('logs', logs)
// 登錄
wx.login({
success: res => {
}
})
// 獲取用戶信息
wx.getSetting({
success: res => {
if (res.authSetting['scope.userInfo']) {
// 已經授權,可以直接調用 getUserInfo 獲取頭像暱稱,不會彈框
wx.getUserInfo({
success: res => {
// 可以將 res 發送給後臺解碼出 unionId
this.globalData.userInfo = res.userInfo
// 由於 getUserInfo 是網絡請求,可能會在 Page.onLoad 之後才返回
// 所以此處加入 callback 以防止這種情況
if (this.userInfoReadyCallback) {
this.userInfoReadyCallback(res)
}
}
})
}
}
})
},
globalData: {
userInfo: null,
}
})
index.js
/index.js
//獲取應用實例
const app = getApp()
Page({
data: {
motto: '歡迎使用迷你計算器',
userInfo: {},
hasUserInfo: false,
canIUse: wx.canIUse('button.open-type.getUserInfo')
},
//事件處理函數
bindViewTap: function() {
wx.navigateTo({
url: '../logs/logs'
})
},
onLoad: function () {
if (app.globalData.userInfo) {
this.setData({
userInfo: app.globalData.userInfo,
hasUserInfo: true
})
} else if (this.data.canIUse){
// 由於 getUserInfo 是網絡請求,可能會在 Page.onLoad 之後才返回
// 所以此處加入 callback 以防止這種情況
app.userInfoReadyCallback = res => {
this.setData({
userInfo: res.userInfo,
hasUserInfo: true
})
}
} else {
// 在沒有 open-type=getUserInfo 版本的兼容處理
wx.getUserInfo({
success: res => {
app.globalData.userInfo = res.userInfo
this.setData({
userInfo: res.userInfo,
hasUserInfo: true
})
}
})
}
},
getUserInfo: function(e) {
console.log(e)
app.globalData.userInfo = e.detail.userInfo
this.setData({
userInfo: e.detail.userInfo,
hasUserInfo: true
})
}
})
首先執行的是app.js的wx.getUserInfo,這個是獲取用戶信息的網絡請求,由於其返回結果不知道在index頁面加載完成之前還是之後完成,因此分爲兩種情況:
- 在index頁面加載完成之前返回:此時優先執行app.js中success函數的代碼,app.globalData.userInfo就保存了用戶信息。運行到這裏時,由於userInfoReadyCallback函數是在index.onload中定義的,因此此時該函數並沒有被定義,所以不執行該函數。之後執行index.js中onload中的代碼,執行第一個if分支,賦值給頁面的userInfo和hasUserInfo
- 在index頁面加載完成之後返回:此時優先執行index.js中onload中的代碼,由於用戶信息還沒有返回,所以app.globalData.userInfo爲null,執行第二個if分支,定義userInfoReadyCallback函數。隨後數據被返回,執行success的代碼,app.globalData.userInfo在此時才保存了用戶的信息,並執行userInfoReadyCallback函數,賦值給頁面的userInfo和hasUserInfo
注:因爲wx.getUserInfo
是異步網絡請求,不知道異步請求先執行完畢還是page.onload先執行完畢,判斷的關鍵在於userInfoReadyCallback方法是否定義。
1. 方法(userInfoReadyCallback)如果定義了,則說明page.onload比當前方法運行的早(page已經完成初始化),app的globalData還沒有數據,通過此回調可以及時的刷新數據
2. 方法如果沒有定義,則說明page.onload比當前方法運行的晚(page還沒有初始化),app的globalData是有值的,可以在page.onload中取globalData裏面的值
因此總體來說userInfoReadyCallback
函數的作用,就是保證頁面的userInfo
和hasUserInfo
被正確賦值,無論用戶信息在頁面加載完成之前還是之後返回。
所謂回調(callback)就是在異步調用時,當異步處理完成後要執行的代碼塊。
假設需求:
- 首先在數據庫的medicine表中有3條藥品信息記錄,每條記錄簡單的包含藥品的一些基礎信息。
- 在小程序中,我們需要從medicine表中查出所有藥品記錄,並在頁面中展示
在小程序中,微信提供的數據庫訪問接口都是異步的,因此不能用簡單的同步模式寫代碼,這時就需要使用callback或者promise
我們先來看看以傳統的同步模式來實現的話代碼會寫成如下形式:
分析如下:
按照同步模式的理解,以上代碼第8行,調用this.getDbData函數查詢數據庫,在函數內將查詢結果賦值給全局變量data中的medicine中,並輸出查詢結果。
第9行,在調用了getDbData函數之後,再輸出變量data的內容,發現medicine變量並沒有被賦值,如下圖
從上邊的執行結果可以看到,第9行先執行了,而getDbData中的輸出後執行了,這就是因爲getDbData中調用數據庫的接口,在微信API中是異步實現的,因此要想實現查詢獲取了相關數據之後,再對數據做進一步處理就需要使用callback機制或者promise
首先,我們通過callback機制來改造如上同步調用模式爲異步調用模式:
如上,在第8行增加了this.process_data作爲callback傳入getDbData函數中第12~14行,是callback函數的定義,並在該函數中輸出相關全局變量的內容,第25行,是執行完數據庫查詢並且完成全局變量賦值之後,將全局變量傳給callback函數並調用,整體執行結果如下:
我們可以如上文userInfoReadyCallback一樣,通過判斷所需數據是否爲空,在回調函數中實現數據在頁面中加載。
接下來,我們通過promise模式改造上述需求,代碼如下:
- 如上,第15行重新定義了promise模式的函數,getDataByPromise返回的是一個promise對象
-
在第7行調用函數之後,在then中執行相關後置動作,具體執行效果如下:
完整代碼如下:
Page({
data: {
"medicine": "N/A",
},
onLoad: function (options) {
// callback模式處理異步調用
// var that = this;
// this.getDbData("medicine", {}, "medicine", this.process_data)
// console.log("in onLoad:", this.data)
// promise模式處理異步調用
this.getDataByPromise("medicine", {}).then((data) => {
this.setData({
"medicine": data
})
console.log("in onLoad promise:", this.data)
})
},
getDataByPromise: function (coll_name, search_cond) {
var promise = new Promise((resolve, reject) => {
var that = this;
const db = wx.cloud.database();
db.collection(coll_name).where(search_cond).get({
success: function (res) {
console.log("in promise info:", res.data)
resolve(res.data)
},
error: function (e) {
console.log(e)
reject("查詢數據庫失敗")
}
});
});
return promise;
},
process_data: function(data){
console.log("in onLoad callback:", this.data)
},
getDbData: function (coll_name, search_cond, data_key, cb) {
const db = wx.cloud.database()
var that = this;
var ready_data = {};
db.collection(coll_name).where(search_cond).get({
success: function (res) {
ready_data[data_key] = res.data;
that.setData(ready_data);
console.log("查詢數據庫完成:", that.data, res.data);
cb(that.data)
}
})
}
})
參考文檔
https://blog.csdn.net/iot_player/article/details/87031698
https://blog.csdn.net/zjw_python/article/details/80641963
https://www.cnblogs.com/NKnife/p/6283605.html