微信小程序setData源碼分析

開發中setData 是小程序開發中使用最頻繁的接口,也是最容易引起性能問題的接口,下面經過源碼分析其組裝和更新數據的流程。瞭解其過程才能寫出性能更好的代碼。javascript

背景

  • setData 是小程序開發中使用最頻繁的接口,也是最容易引起性能問題的接口。詳見官網描述html

  • 常見的 setData 操做錯誤java

    1.頻繁的去 setDatagit

    2.每次 setData 都傳遞大量新數據github

    3.後臺態頁面進行 setData小程序

  • 針對第二點官網給出意見是,其中 key 能夠以數據路徑的形式給出,支持改變數組中的某一項或對象的某個屬性,如 array[2].message,a.b.c.d,而且不須要在 this.data 中預先定義微信小程序

  • 下面經過源碼深刻分析的方式瞭解小程序是怎麼針對數據路徑進行組裝和構造數據數組

小程序邏輯層框架源碼

  • 微信小程序運行在三端:iOS(iPhone/iPad)、Android 和 用於調試的開發者工具。在開發工具上,小程序邏輯層的 javascript 代碼是運行在 NW.js 中,視圖層是由 Chromium 60 Webview 來渲染的。這裏簡單點就直接經過開發者工具來查找源碼。
  • 在微信開發者工具中,編譯運行你的小程序項目,而後打開控制檯,輸入 document 並回車,就能夠看到小程序運行時,WebView 加載的完整的 WAPageFrame.html,以下圖:

能夠看到./ dev/WAService.js這個庫就小程序邏輯層基礎庫,提供邏輯層基礎的 API 能力

  • 在微信小程序 IDE 控制檯輸入 openVendor 命令,能夠打開微信小程序開發工具的資源目錄

  • 咱們能夠看到小程序各版本的運行時包 .wxvpkg。.wxvpkg 文件可使用 wechat-app-unpack 解開,解開后里面就是WAService.js 和 WAWebView.js 等代碼

  • 另外也能夠只直接經過開發者工具的Sources面板查找到WAService.js的源碼

分析setData源碼

  • 在WAService.js中全局查找setData方法,找到定義此方法的地方,以下

  • 源代碼使用了大量的逗號運算符,逗號運算符的優先級是最低的,比條件選擇符還低
  • 大量使用void 0 表示undefined
  • setData函數定義中添加了關鍵的註釋以下:
function(c, e) {
    // 保存閉包內的this對象,即經常使用的that
    var u = this;
    // 官網定義 Page.prototype.setData(Object data, Function    callback),
    // 即 c: Object對象,e: Function界面更新渲染完畢後的回調函數
    try {
        // 返回 [object Object] 中的Object
        var t = v(c);
        if ("Object" !== t)
            return void E("類型錯誤", "setData accepts an Object rather than some " + t);
        Object.keys(c).forEach(function(e) {
            // e: 可枚舉屬性的鍵值, void 0 表示undefined (https://github.com/lessfish/underscore-analysis/issues/1)
            void 0 === c[e] && E("Page setData warning", 'Setting data field "' + e + '" to undefined is invalid.');
            // t爲包含子對象屬性名的屬性數組, u.data和u.__viewData__都是page.data的深拷貝副本
            var t = N(e)
              , n = j(u.data, t)
              , r = n.obj
              , o = n.key;
            if (r && (r[o] = y(c[e])), void 0 !== c[e]) {
                var i = j(u.__viewData__, t)
                  , a = i.obj
                  , s = i.key;
                a && (a[s] = y(c[e]))
            }
        }),
        __appServiceSDK__.traceBeginEvent("Framework", "DataEmitter::emit"),
        this.__wxComponentInst__.setData(JSON.parse(JSON.stringify(c)), e),
        __appServiceSDK__.traceEndEvent()
    } catch (e) {
        k(e)
    }
}
複製代碼
  • 關鍵函數N(e),解析屬性名(包含.和[]等數據路徑符號),返回相應的層級數組,如
{abc: 1}中abc屬性名 => [abc], {a.b.c: 1}中'a.b.c'屬性 => [a,b,c], {"array[0].text": 1} => [array, 0, text]
複製代碼

關鍵的註釋以下:bash

function N(e) {
    // 若是屬性名不是String字符串就拋出異常
    if ("String" !== v(e))
        throw E("數據路徑錯誤", "Path must be a string"),
        new M("Path must be a string");
    for (var t = e.length, n = [], r = "", o = 0, i = !1, a = !1, s = 0; s < t; s++) {
        var c = e[s];
        if ("\\" === c)
            // 若是屬性名中包含\\. \\[  \\] 三個轉義屬性字符就將. [ ]三個字符單獨拼接到字符串r中保存,不然就拼接\\
            s + 1 < t && ("." === e[s + 1] || "[" === e[s + 1] || "]" === e[s + 1]) ? (r += e[s + 1],
            s++) : r += "\\";
        else if ("." === c)
            // 遇到.字符而且r字符串非空時,就將r保存到n數組中並清空r; 目的是將{ a.b.c.d: 1 }中的鏈式屬性名分開,保存到數組n中,如[a,b,c,]
            r && (n.push(r),
            r = "");
        else if ("[" === c) {
            // 遇到[字符而且r字符串非空時,就將r保存到n數組中並清空r;目的是將{ array[11]: 1 }中的數組屬性名保存到數組n中,如[array,]
            // 若是此時[爲屬性名的第一個字符就報錯,也就是說屬性名不能直接爲訪問器, 如{ [11]: 1}
            if (r && (n.push(r),
            r = ""),
            0 === n.length)
                throw E("數據路徑錯誤", "Path can not start with []: " + e),
                new M("Path can not start with []: " + e);
            // a賦值爲true, i賦值爲false
            i = !(a = !0)
        } else if ("]" === c) {
            if (!i)
                throw E("數據路徑錯誤", "Must have number in []: " + e),
                new M("Must have number in []: " + e);
            // 遍歷到{ array[11]: 1 }中的']'的時候,就將a賦值爲false, 並將o保存到數組n中,如[array,11,]
            a = !1,
            n.push(o),
            o = 0
        } else if (a) {
            if (c < "0" || "9" < c)
                throw E("數據路徑錯誤", "Only number 0-9 could inside []: " + e),
                new M("Only number 0-9 could inside []: " + e);
            // 遍歷到{ array[11]: 1 }中的'11'的時候,就將i賦值爲true, 並將string類型的數字計算成Number類型保存到o中
            i = !0,
            o = 10 * o + c.charCodeAt(0) - 48
        } else
            r += c  // 普通類型的字符就直接拼接到r中
    }
    // 將普通的字符串屬性名,.和]後面剩餘的字符串保存到數組n中,如{abc: 1} => [abc], {a.b.c: 1} => [a,b,c], {array[0].text: 1} => [array, 0, text]
    if (r && n.push(r),0 === n.length)
        throw E("數據路徑錯誤", "Path can not be empty"),
        new M("Path can not be empty");
    return n
}
複製代碼
  • 關鍵函數j(e, t),解析出屬性最終對應的子對象的屬性名,以及對應的子對象
var x = Object.prototype.toString;
function _(e) {
    return "[object Object]" === x.call(e)
}
function j(e, t) {
    // e: page.data的深拷貝副本, t爲包含子對象屬性名的屬性數組
    /*
        - 遍歷屬性數組[a,b], e={a: {b: 1}}
        1. i=0, 此時o爲Object類型時, n = a, r = {a: {b: 1}}, o = {b: 1};
        2. i=1, 此時o爲Object類型時, n = b, r = {b: 1}, o = 1;
        retrun { obj: {b: 1}, key: b}

        - 遍歷屬性數組[a,0,b], e={a: [{b: 1}]}
        1. i=0, 此時t[i]=a, o爲Object類型時, n = a, r = {a: [{b: 1}]}, o = [{b: 1}];
        2. i=1, 此時t[i]=0, o爲Array類型時, n = 0, r = [{b: 1}], o = {b: 1};
        3. i=2, 此時t[i]=b, o爲Object類型時, n = b, r = {b: 1}, o = 1;
        retrun { obj: {b: 1}, key: b}
    */
    for (var n, r = {}, o = e, i = 0; i < t.length; i++)
        Number(t[i]) === t[i] && t[i] % 1 == 0 ? // t[i]是否爲有效的Number
        Array.isArray(o) || (r[n] = [], o = r[n]) :
        _(o) || (r[n] = {}, o = r[n]), 
        n = t[i], o = (r = o)[t[i]]; //注意因爲逗號分隔符的優先級是最低的,因此這一行會在前面的條件運算符執行完,再執行
    return {
        obj: r,
        key: n
    }
}
複製代碼
  • 最後經過r && (r[o] = y(c[e]))的方式將新的值賦給匹配出的子對象的屬性,這裏j(e,t)函數內部是經過引用的方式向外傳遞出r,因此這裏改變r[o]的值也會將u.data內部的值相應修改,完成局部刷新
  • 因爲不一樣的版本解包後,裏面壓縮以後的方法名稱可能跟上面的對不上,可是大致的結構都是同樣的

總結

1.官方提供的array[2].message,a.b.c.d方式就是經過解析成[array,2,message]和[a,b,c,d],找到相應的子結構進行復制操做,到達減小數據量的目的;微信

2.分頁加載的時候,爲了不將整個list數據從新傳輸,就能夠利用數據路徑的方式只追加新的數據

假設原數組長度 length 爲 10,新數組 newList 長度爲 3
this.setData{
  'list[10]': newList[0],
  'list[11]': newList[1],
  'list[12]': newList[2],
}
複製代碼

參考資料

相關文章
相關標籤/搜索