微信小程序開發採坑記錄1

問題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函數的作用,就是保證頁面的userInfohasUserInfo被正確賦值,無論用戶信息在頁面加載完成之前還是之後返回。

所謂回調(callback)就是在異步調用時,當異步處理完成後要執行的代碼塊。

 

假設需求:

  1. 首先在數據庫的medicine表中有3條藥品信息記錄,每條記錄簡單的包含藥品的一些基礎信息。
  2. 在小程序中,我們需要從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模式改造上述需求,代碼如下:

  1. 如上,第15行重新定義了promise模式的函數,getDataByPromise返回的是一個promise對象
  2. 在第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

https://blog.csdn.net/zjw_python/article/details/80880208

https://blog.csdn.net/mengalong/article/details/83541710

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章