- 复杂瀑布流长列表页踩坑记录,内存不足问题【2】
第二期来啦,带来了新的方案和代码片段~ 第一期点此查看 上期问题 经过一系列的实践,上期的方案有些问题,其中最麻烦的就是,需要对外传递一个当前index,然后控制前后数据展示;这里对于每个用到[代码]skeleton[代码]组件的页面来说,都要重复的写一个方法来承接这个index,然后渲染页面对应的数据。 优化 依然是监听[代码]skeleton[代码]曝光,这里监听的方案变为出现在屏幕上下[代码]n[代码]屏的内容块进行展示,此范围外的内容块就卸载掉。 核心代码 [代码] // 修改了监听是否显示内容的方法,改为前后showNum屏高度渲染 // 监听进入屏幕的范围relativeToViewport({top: xxx, bottom: xxx}) let info = SystemInfo.getInfo() //获取系统信息 let { windowHeight = 667 } = info.source.system let showNum = 2 //超过屏幕的数量,目前这个设置是上下2屏 let listItemContainer = this.createIntersectionObserver() listItemContainer.relativeToViewport({ top: showNum * windowHeight, bottom: showNum * windowHeight }) .observe(`#list-item-${this.data.skeletonId}`, (res) => { // 此处来控制slot展示,详见代码片段 }) [代码] 干货 话不多说,干货放后面,点击获取代码片段
2019-12-05 - IOS scroll-view中的自定义组件fixed问题
这个是正常现象,因为 iOS 下加了 -webkit-overflow-scrolling: touch,这个会产生滚动惯性,体验更好,但会改变 fixed 的行为,建议不在 scroll-view 里有 fixed 元素
2020-04-23 - 请问在其他页面,怎么监听到变化了的app.globalData的某一个属性的值?
请问在其他页面,怎么监听到变化了的app.globalData的某一个属性的值?
2020-05-18 - 微信小程序setData源码分析
背景 setData 是小程序开发中使用最频繁的接口,也是最容易引发性能问题的接口。详见官网描述 常见的 setData 操作错误 频繁的去 setData 每次 setData 都传递大量新数据 后台态页面进行 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 能力 查找WAService.js源码 在微信小程序 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][代码] 关键的注释如下 [代码]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[代码]内部的值相应修改,完成局部刷新 由于不同的版本解包后,里面压缩之后的方法名称可能跟上面的对不上,但是大体的结构都是一样的 总结 官方提供的array[2].message,a.b.c.d方式就是通过解析成[array,2,message]和[a,b,c,d],找到相应的子结构进行复制操作,到达减少数据量的目的; 分页加载的时候,为了避免将整个list数据重新传输,就可以利用数据路径的方式只追加新的数据 [代码]假设原数组长度 length 为 10,新数组 newList 长度为 3 this.setData{ 'list[10]': newList[0], 'list[11]': newList[1], 'list[12]': newList[2], } [代码] 参考资料 微信小程序技术原理分析 小程序开发指南
2019-08-24