- 【优化】利用函数防抖和函数节流提高小程序性能
大家好,上次给大家分享了swiper仿tab的小技巧: https://developers.weixin.qq.com/community/develop/doc/000cc0b94ac5f8dcf4e7666475b804 今天给大家分享两个有用的函数,《函数防抖和函数节流》 函数防抖和函数节流是都优化高频率执行js代码的一种手段,因为是js实现的,所以在小程序里也是适用的。 首先先来理解一下两者的概念和区别: 函数防抖(debounce)是指事件在一定时间内事件只执行一次,如果在这段时间又触发了事件,则重新开始计时,打个很简单的比喻,比如在打王者荣耀时,一定要连续干掉五个人才能触发hetai kill '五连绝世'效果,如果中途被打断就得重新开始连续干五个人了。 函数节流(throttle)是指限制某段时间内事件只能执行一次,比如说我要求自己一天只能打一局王者荣耀。 这里也有个可视化工具可以让大家看一下三者的区别,分别是正常情况下,用了函数防抖和函数节流的情况下:http://demo.nimius.net/debounce_throttle/ 适用场景: 函数防抖 搜索框搜索联想。只需用户最后一次输入完,再发送请求 手机号、邮箱验证输入检测 窗口resize。只需窗口调整完成后,计算窗口大小。防止重复渲染 高频点击提交,表单重复提交 函数节流 滚动加载,加载更多或滚到底部监听 搜索联想功能 实现原理 函数防抖 [代码]const _.debounce = (func, wait) => {[代码][代码] [代码][代码]let timer;[代码] [代码] [代码][代码]return[代码] [代码]() => {[代码][代码] [代码][代码]clearTimeout(timer);[代码][代码] [代码][代码]timer = setTimeout(func, wait);[代码][代码] [代码][代码]};[代码][代码]};[代码] 函数节流 [代码]const throttle = (func, wait) => {[代码][代码] [代码][代码]let last = 0;[代码][代码] [代码][代码]return[代码] [代码]() => {[代码][代码] [代码][代码]const current_time = +[代码][代码]new[代码] [代码]Date();[代码][代码] [代码][代码]if[代码] [代码](current_time - last > wait) {[代码][代码] [代码][代码]func.apply([代码][代码]this[代码][代码], arguments);[代码][代码] [代码][代码]last = +[代码][代码]new[代码] [代码]Date();[代码][代码] [代码][代码]}[代码][代码] [代码][代码]};[代码][代码]};[代码] 上面两个方法都是比较常见的,算是简化版的函数 lodash中的 Debounce 、Throttle lodash中已经帮我们封装好了这两个函数了,我们可以把它引入到小程序项目了,不用全部引入,只需要引入debounce.js和throttle.js就行了,链接:https://github.com/lodash/lodash 使用方法可以看这个代码片段,具体的用法可以看上面github的文档,有很详细的介绍:https://developers.weixin.qq.com/s/vjutZpmL7A51 系甘先,得闲饮茶
2019-01-14 - 分享一个自适应的自定义导航栏组件
自定义导航栏的一些常见问题: 1、怎么适配手机状态栏高度,使导航栏与胶囊按钮对齐? 2、写了导航栏组件但是用了fixed定位,脱离普通文档流,要麻烦的去每个页面加padding-top。 3、怎么让导航栏跟page融合到一起,跟随page滚动,这样就不用在这个页面单独适配状态栏了。 4、怎么自动识别非首页启动的小程序,在自定义导航栏加个返回首页的引导? 使用案例可导入代码片段【现在导入】查看。 子页面 [图片][图片] [图片] 非首页启动的小程序 [图片] 跟随页面,不置顶。 [图片] 监测滚动切换导航栏的显示或隐藏。 [图片] -----------------------------------------------------------------------------------------------------------------------------------------
2019-05-26 - 瀑布流布局解决方案 BrickLayout
BrickLayout 晓瀑布流为使用者提供开箱即用的瀑布流布局的一种可行性的方案,使用者仅需要按照对应所需的字段传入瀑布流组件,即可快速实现瀑布流布局。支持排序和动态展开收起。
2018-08-23 - setData与渲染
1、问题与猜想 setData的流程是什么? 一次setData调用必定会引起一次渲染? 1.1、setData的流程 ? 通信过程:jscore -> native -> webview,native以队列处理消息,最终的结果可以大致理解为插了一段 eval 代码到对应webview中。 jscore -> native 的时间可以基本忽略。 数据量比较大的时候 native -> webview 比较耗时 native -> webview 的通信,在ios上会抢占CPU资源,影响页面渲染 eval编译代码也比较耗时,抢占渲染线程 1.2、一次setData调用必定会引起一次渲染 ? 一次setData并不会引起一次渲染,如果在一次渲染周期内,收到了多次setData,那么只会有一次渲染。setData会在当前的js执行栈中插入一段js代码,如果刚好赶上了当前的渲染周期,那多次setData也只会引起一次渲染。 2、验证测试 (demo测试) 2.1、同一页面连续setData 前少后多:一般是赶不上第一次的渲染周期,会触发两次 前多后少:只有一次 2.2、不同页面的setData 前少后多:ios上影响当前页面渲染,android没有影响 前多后少:基本影响,后台页面set的数据比较少的时候,用的资源基本可以忽略 后台页面setData首先会抢占通信通道,其次会抢占CPU资源,ios上如果后台页面set的数据多,当前页面渲染会有很明显延迟,android上无影响。 2.3、页面和组件同时setData 前少后多:只有一次 前多后少:只有一次 2.4、同一组件连续setData 前少后多:一般是赶不上第一次的渲染周期,会触发两次 前多后少:只有一次 2.5、不同组件同时setData 前少后多:一次渲染 前多后少:只有一次 当多个组件同时setData时,感觉引擎层会搜集所有改变,一次渲染,这样也比较合理,某个属性改变,引起多个组件同时渲染的场景还是挺多的。 3、结论 多用组件,因为组件是局部patch、render,页面是全部 同一页面,同一组件的同时setData要合并,特别是页面 后台页面最好不要setData,会抢占通信通道和CPU资源,影响前台页面的渲染,如果控制好set的量级和次数,可以接受。 如果父组件、子组件同时setData,不用考虑合并成一次 多个组件同时setData可以不用合并,引擎层直接合并了,只会触发一次渲染 以上只是个人的一点思考,并用demo验证了下,有错误的地方 欢迎讨论
2018-12-04 - 小程序异常监控收集
前言你是否经常碰到业务反馈,线上的小程序某个页面打不开了,订单没法结算了,但是你当时测试的时候都是好好的。 由于线上环境复杂,一些问题只会在特定网络环境或者设备上发生,对于这类问题,异常信息的收集就显得格外重要了,我们不但希望收集错误的堆栈信息,还需要用户操作流程,设备信息等,以便复现错误。 简单收集小程序App()生命周期里提供了onError函数,可以通过在onError里收集异常信息 App({ // 监听错误 onError: function (err) { // 上报错误 wx.request({ url: "https://url", // 自行定义报告服务器 method: "POST", errMsg: err }) } }) 用户操作路径收集一些较隐蔽的错误如果只有错误栈信息,排查起来会比较难,如果有用户操作的路径,在排查时就方便多了。 方法一:暴力打点方法收集 优点:简单直接 缺点:污染业务代码,造成较多垃圾代码 方法二:函数劫持(推荐使用) 需要在App函数中的onLaunch、onShow、onHide生命周期插入监控代码,我们通过重写App生命周期函数来实现。 App = function(app) { ["onLaunch", "onShow", "onHide"].forEach(methodName => { app[methodName] = function(options) { // 构造访问日志对象 var breadcrumb = { type: "function", time: utils.now(), belong: "App", // 来源 method: methodName, path: options && options.path, // 页面路径 query: options && options.query, // 页面参数 scene: options && options.scene // 场景编号 }; self.pushToBreadcrumb(breadcrumb); // 把执行对象加入到面包屑中 }) }但是这样写,会把用户自定义的内容给覆盖掉,所以我们还需要把用户定义的函数和监控代码合并。 var originApp = App // 保存原对象 App = function(app) { // .... 此处省略监控代码 // .... 此处省略监控代码 originApp(app) // 执行用户定义的方法 }记录结果 可以从下面的json看出,用户到了detail页面,执行了onLoad => getDetail => onReady => buy 当执行buy方法的时候报错。 [{"method":"onLoad","route":"pages/film/detail","options":{"id":"4206"}}, {"method":"getDetail","route":"pages/film/detail","options":{"id":"4206"}}, {"method":"onReady","route":"pages/film/detail","options":{"id":"4206"}},{"method":"buy","route":"pages/film/detail","options":{"id":"4206"}}] 上报策略考虑到在大型应用中,日志量比较大,我们采取抽样,合并,过滤三个方法减少日志的输出,代码实现可以参考lib/report.js 代码组织项目使用rollup作为构建工作,实现ES6转ES5,模块加载功能。 项目目录如下: config.js // 配置文件 core.js // 劫持小程序核心代码 events.js // 监听自定义事件 report.js // 上报类 utils.js // 工具类 🌟喜欢的点个star:https://github.com/zhengguorong/xbossdebug-wechat
2018-06-06 - 小程序使用async/await 来处理异步
小程序使用async/await 来处理异步 regeneratorRuntime https://github.com/facebook/regenerator/blob/master/packages/regenerator-runtime/runtime.js 详细看代码片段 https://developers.weixin.qq.com/s/FlIPCOmw7P3c //引入编译模块 const regeneratorRuntime =require("../libs/regeneratorRuntime.js") const promisify = require('../libs/promisify.js'); //微信 API,转成返回 Promise 的接口 云开发API的有Promise 风格 不需要再转了 const getUserInfo= promisify(wx.getUserInfo); const login = promisify(wx.login); wx.cloud.init(); Page({ data: { }, onLoad: async function () { console.log(this) const user = await getUserInfo(); console.log("onLoad user",user) const code = await login(); console.log("onLoad code",code); const res = await wx.cloud.database().collection('todos').where({ _openid: 'xxx' }).get(); console.log("onLoad res", res) }, onShow: async function(){ const user = await getUserInfo(); console.log("onshow", user) } })
2018-11-10 - display和opacity的动画
问题描述在做小程序的弹窗的时候,需要实现一个透明度的渐变动画,并且在关闭的时候把弹窗移除。如果不移除的话,就无法选中弹窗层级下面的元素。 需要实现的效果[图片] 这个功能有两个要求: 透明度渐变的动画 点击关闭的时候把弹窗移除,让其不要再占据空间。 刚开始我是怎么写的hah[代码]/* 显示样式*/[代码] [代码].modal-visible { [代码] [代码] display: block; [代码] [代码] opacity: 1; [代码] [代码] transition: opacity 100ms ease;[代码] [代码]}[代码] [代码] [代码] [代码]/* 隐藏样式*/[代码] [代码].modal-hidden { [代码] [代码] display: none; [代码] [代码] opacity: 0; [代码] [代码] transition: opacity 100ms ease;[代码] [代码]}[代码]然后保存发现并没有透明度的过渡效果!!!! 解决过程上google搜索[代码]display animation stackoverflow[代码],发现有很多人跟我遇到了同样的问题。 链接 https://stackoverflow.com/questions/8449933/animation-css3-display-opacity/8450102 问题出现的原因元素先执行了display为none,再去执行opacity的渐变动画,由于元素被提前移除,导致后面的动画执行无效。 常用的解决方法1. 使用css3的animation [代码]@keyframes fadeInFromNone {[代码][代码] 0% { [代码] [代码] display: none; [代码] [代码] opacity: 0;[代码] [代码] }[代码][代码] 1% { [代码] [代码] display: block; [代码] [代码] opacity: 0;[代码] [代码] }[代码][代码] 100% { [代码] display: block; [代码] opacity: 1;[代码] [代码] } }[代码] 2. 使用css的visible、height、transition-delay实现[代码]visible[代码]可以隐藏元素,但是隐藏后元素并没有移除,所以需要让元素的高度变为0,让其不再占据空间。 利用transition-delay让元素height变为0延迟执行,在opacity的渐变动画执行完再去执行height变化 [代码].modal-visilble { [代码] [代码] visibility: visible; [代码] [代码] height: auto; [代码] [代码] opacity: 1; [代码] [代码] /* 100ms为动画执行时间, 0ms为延迟时间 */[代码] [代码] transition: opacity 100ms ease 0ms; [代码][代码]}[代码] [代码] [代码] [代码].modal-hidden { [代码] [代码] visibility: hidden; [代码] [代码] opacity: 0; [代码] [代码] height: 0; [代码] [代码] transition: height 0ms ease 100ms, visibility 100ms ease 0ms, opacity 100ms ease 0ms; [代码] [代码]}[代码]3. 结合使用js。在动画结束的时候让元素display为none 两种方法 第一种: 监听动画的transitionend事件 第二种: 定时器。设定[代码](setTimeout)[代码]为动画的执行时间,定时器时间到了之后移除元素(display: none)。 总结主要是对css动画属性更熟悉了 transition-delay、animation,以及动画结束的事件transitionend 欢迎点赞hahaha
2018-11-01