- 【分享】wx-performance,让测速变得简单
wx-performance 微信小程序测速耗时管理SDK 使用此组件需要依赖小程序基础库 2.2.1 以上版本,同时依赖开发者工具的 npm 构建。具体详情可查阅官方 npm 文档。 说明 简化耗时管理和上报逻辑 小程序·测速系统开始内测。测速系统旨在帮助开发者简单方便的测速网络性能、渲染/加载性能等。 测速结果数据支持地域、运营商、系统、网络类型等关键维度交叉分析,支持分钟级数据实时查看。 微信只提供了[代码]wx.reportPerformance(id, cost)[代码]接口,具体的耗时计算需要业务方自己计算。一般情况下我们计算任务耗时都是通过打点的方式,比如: [代码]let t1 = new Date().getTime(); let pfmId = 1001; // some sync task ... let cost = new Date().getTime() - t1; if (wx.reportPerformance) { wx.reportPerformance(pfmId, cost); } // ============ let t2 = new Date().getTime(); let pfmId1 = 1002; // some async task setTimeout(() => { let cost = new Date().getTime() - t2; if (wx.reportPerformance) { wx.reportPerformance(pfmId1, cost); } }, 100) [代码] 我们可以看到常规打点的方式代码比较繁琐: 需要自己进行耗时计算 变量多,增加维护成本 接口兼容性处理 本SDK旨在提供一种更简单的方式进行耗时管理和上报,使用SDK后的代码: [代码]var Performance = require('wx-performance') let pfmId = 1001; let pfmId1 = 1002; let record = Performance.startRecord(pfmId); // some sync task ... record.finish(); // ============ let record1 = Performance.startRecord(pfmId1); // some async task setTimeout(() => { record1.finish(); }, 100) [代码] [代码]Performance.startRecord[代码]接口返回一个有finish接口的对象,只需要在结束的地方执行就会进行耗时自动计算与上报。 防重复调用处理 有时我们会出现重复调用finish接口的场景,导致上报数据不准确,比如:我们需要计算页面第一次onShow的耗时,我们的代码可能如下: [代码]let pfmId = 1001; Page({ t1: null, onLoad() { this.t1 = new Date().getTime(); }, onShow() { let cost = new Date().getTime() - this.t1; wx.reportPerformance(pfmId, cost); } }) [代码] 而我们都知道onShow会被多次调用,比如从下一级页面返回当前页,就会导致上报异常数据。为了避免这种情况,finish接口做了防重复调用的处理。 [代码]var Performance = require('wx-performance') let pfmId = 1001; Page({ record: null, onLoad() { this.record = Performance.startRecord(pfmId); }, onShow() { // finish接口只会上传第一次掉用的数据 this.record.finish() } }) [代码] 安装 [代码]npm install --save wx-performance [代码] 使用 [代码]var Performance = require('wx-performance') let pfmId = 1001; let record = Performance.startRecord(pfmId) // some sync task ... record.finish() [代码]
2020-03-01 - wx.navigateToMiniProgram提示框内容
wx.navigateToMiniProgram打开小程序的提示框出现英文提示,Launching MiniProgram + 中文小程序名称。为什么会出现中英混合?
2019-05-17 - 微信小程序之SelectorQuery
在开发小程序展开全文组件时需要用到节点查询API - [代码]wx.createSelectorQuery()[代码] 来查询全文内容的高度。 [图片] [代码]wx.createSelectorQuery()[代码] 返回一个 [代码]SelectorQuery[代码] 对象实例。 [代码]SelectorQuery[代码] 有五个方法([代码]in[代码],[代码]select[代码],[代码]selectAll[代码],[代码]selectViewport[代码],[代码]exec[代码]),第一个返回 [代码]SelectorQuery[代码],后四个返回 [代码]NodesRef[代码]。 [代码]NodesRef[代码] 有四个方法([代码]fields[代码],[代码]boundingClientRect[代码],[代码]scrollOffset[代码],[代码]context[代码]),第一个返回 [代码]NodesRef[代码],后三个返回 [代码]SelectorQuery[代码]。 对照官方提供的示例代码来看 [代码]const query = wx.createSelectorQuery() // query 是 SelectorQuery 对象 query.select('#the-id').boundingClientRect() // select 后是 NodesRef 对象,然后 boundingClientRect 返回 SelectorQuery 对象 query.selectViewport().scrollOffset() // selectViewport 后是 NodesRef 对象,然后 scrollOffset 返回 SelectorQuery 对象 query.exec(function (res) { // exec 返回 NodesRef 对象 res[0].top // #the-id节点的上边界坐标 res[1].scrollTop // 显示区域的竖直滚动位置 }) [代码] 问题:每行执行返回的 [代码]SelectorQuery[代码] 对象是相同的吗? 答案:是的,都是同一个对象。 问题:直接执行 [代码]query.select('#the-id').boundingClientRect().exec[代码] 也可以吗? 答案:可以,[代码]boundingClientRect()[代码] 返回就是 [代码]query[代码]。 问题:这样连写 [代码]query.select('#the-id').boundingClientRect().selectViewport().scrollOffset()[代码] 算两条查询请求吗? 答案:是两条请求。 问题:[代码]query.exec[代码] 执行后会清空前面的查询请求吗?再次执行还能拿到结果吗? 答案:可以,[代码]query[代码] 不会清空请求。 问题:[代码]boundingClientRect[代码] 和 [代码]scrollOffset[代码] 可以接受 [代码]callback[代码] 参数,它与 [代码]query.exec[代码] 执行顺序是怎样,修改 [代码]res[代码] 结果会影响到后面的 [代码]callback[代码] 吗? 答案:先执行 [代码]boundingClientRect[代码] 和 [代码]scrollOffset[代码] 的 [代码]callback[代码],再执行 [代码]query.exec[代码] 的 [代码]callback[代码];修改 [代码]res[代码] 结果会影响到后面 [代码]exec[代码] 的结果。 上面的问题通过小程序开发者工具中的 [代码]WAService.js[代码] 源码简单美化还原后可以了解 [代码]SelectorQuery[代码] 的代码逻辑 SelectorQuery.js [代码]import NodesRef from 'NodesRef'; import requestComponentInfo from 'requestComponentInfo'; export default function(pluginId) { return class SelectorQuery { constructor(plugin) { if (plugin && plugin.page) { this._component = this._defaultComponent = plugin.page; this._webviewId = this._defaultComponent.__wxWebviewId__; } else { var pages = __internalGlobal__.getCurrentPagesByDomain(''); this._defaultComponent = pages[pages.length - 1], this._component = null; this._webviewId = null; } this._queue = []; this._queueCb = []; } in(component) { if (!this._webviewId) { this._webviewId = component.__wxWebviewId__; this._component = component; } else if (this._webviewId !== component.__wxWebviewId__) { console.error('A single SelectorQuery could not work in components in different pages. A SelectorQuery#in call has been ignored.'); } else { this._component = component; } return this; } select(selector) { return new NodesRef(this, this._component, selector, true); } selectAll(selector) { return new NodesRef(this, this._component, selector, false); } selectViewport() { return new NodesRef(this, 0, '', true); } _push(selector, component, single, fields, callback) { if (!this._webviewId) { this._webviewId = this._defaultComponent ? this._defaultComponent.__wxWebviewId__ : undefined; } const rootNodeId = pluginId ? '' : r.getRootNodeId(this._webviewId); this._queue.push({ component: null != component ? (0 === component ? 0 : component.__wxExparserNodeId__) : rootNodeId, selector, single, fields, }); this._queueCb.push(callback || null); } exec(callback) { requestComponentInfo(this._webviewId, { pluginId, queue: this._queue, }, (results) => { const queueCb = this._queueCb; results.forEach((res, index) => { if ('function' == typeof queueCb[index]) { queueCb[index].call(this, res); } }); if ('function' == typeof callback) { callback.call(this, results); } }) } } } [代码] requestComponentInfo.js [代码]const subscribe = function(eventType, callback) { const _callback = function(event, webviewId, nativeInfo = {}) { const { data = {}, options } = event; const startTime = options && options.timestamp || 0; const endTime = Date.now(); if ('function' == typeof callback) { callback(data, webviewId); Reporter.speedReport({ key: 'webview2AppService', data, timeMark: { startTime, endTime, nativeTime: nativeInfo.nativeTime || 0, } }); } }; __safeway__.bridge.subscribe(eventType, _callback); } const publish = function(eventType, data, webviewIds) { const event = { data, options: { timestamp: Date.now(), } }; __safeway__.bridge.publish(eventType, event, webviewIds); } const requestCb = {}; let requestId = 1; subscribe('responseComponentInfo', function(data) { const reqId = data.reqId; const callback = requestCb[reqId]; if (callback) { delete requestCb[reqId]; callback(data.res); } }); export default function requestComponentInfo(webviewId, reqs, callback) { const reqId = requestId++; if (!webviewId) { console.warn('An SelectorQuery call is ignored because no proper page or component is found. Please considering using `SelectorQuery.in` to specify a proper one.'); return; } requestCb[reqId] = callback, publish('requestComponentInfo', { reqId, reqs, }, [webviewId], ); } [代码] NodesRef.js [代码]export default class NodesRef { constructor(selectorQuery, component, selector, single) { this._selectorQuery = selectorQuery; this._component = component; this._selector = selector; this._single = single; } fields(fields, callback) { this._selectorQuery._push(this._selector, this._component, this._single, fields, callback, ); return this._selectorQuery; } boundingClientRect(callback) { this._selectorQuery._push( this._selector, this._component, this._single, { id: true, dataset: true, rect: true, size: true, }, callback, ); return this._selectorQuery; } scrollOffset(callback) { this._selectorQuery._push( this._selector, this._component, this._single, { id: true, dataset: true, scrollOffset: true, }, callback, ); return this._selectorQuery; } } [代码]
2019-05-16