開發中setData 是小程序開發中使用最頻繁的接口,也是最容易引起性能問題的接口,下面經過源碼分析其組裝和更新數據的流程。瞭解其過程才能寫出性能更好的代碼。javascript
setData 是小程序開發中使用最頻繁的接口,也是最容易引起性能問題的接口。詳見官網描述html
常見的 setData 操做錯誤java
1.頻繁的去 setDatagit
2.每次 setData 都傳遞大量新數據github
3.後臺態頁面進行 setData小程序
針對第二點官網給出意見是,其中 key 能夠以數據路徑的形式給出,支持改變數組中的某一項或對象的某個屬性,如 array[2].message,a.b.c.d,而且不須要在 this.data 中預先定義微信小程序
下面經過源碼深刻分析的方式瞭解小程序是怎麼針對數據路徑進行組裝和構造數據數組
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)
}
}
複製代碼
{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
}
複製代碼
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
}
}
複製代碼
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],
}
複製代碼