收藏
评论

使用串发命令模式延迟同步请求(下)官方


目前在主页的JS里面有这样的一个函数:rpx2px函数,它在这个页面文件里面直接调用了异步接口,而且这个调用只是为了取一个屏幕高度,这种写法很简洁,但是也可以浪费,在有了这个SystemInfoManager这个管理器以后可以对这个函数进行改写,依托我们已经实现的这个管理器去获取需要的系统信息,在我们统一拉取一次系统信息以后,如果后续对系统信息获取是运行时拉取的话,这种时候可以直接调用wx.getSystemInfoSync接口,因为此时这个接口它已经被我们改写了,按旧的这种同步方式调用它也已经没有问题了,但是要注意调用代码它必须是在运行时执行的,不能直接写在对象的data数据对象上,也不可以直接写在这个页面的JS顶部。

下面我们看代码演示。

在我们这个项目里面找到我们刚才所说的这样的一个函数,它在我们主页里面,主页的页面的顶部 在这个地方有对它的一个实现,其实就是调用getSystemInfoSync,调完以后去取用它windowWidth这样的一个属性,这个方法我们看一下它在哪里调用的,我们可以检索一下,是在我们创建长列表上下渲染对象的时候,然后123 有了三次调用对不对,它的作用其实是一个单位的转换,这是它的作用。

那么我们现在看一下,假如我们用我们新的管理器模块system_info_manager,我们看一下在这个里面目前有没有获取WindowsInfo的一个方法,没有,现在还没有,没有没关系,我们可以添加一个 windowWidth,然后在这个下面我们可以加一个新的方法get WindowWidth,让它return屏幕的宽度,这样就可以了。有的同学可能会说,这个地方也是这样的一种调用,在我们原来的这个地方也是这样的一种调用,那只是换了一个地方而已,有什么区别吗?还是不一样的,因为在我们这个里面有这样的一个特殊的方法retrieveSystemInfo,如果我们这个方法执行完成以后,其实这个方法它本身它已经被改造了,已经不占用时间了,已经取到本地的信息了,它拿这个属性的话也是直接可以拿到的,不需要再占用时间了,也不需要因为同步而阻塞线程的运行了。这个改完以后在我们的文件里边找到文件,首先是要引入default:systemInfoMgr等于require,然后找到我们的管理器manager,就是它,在这个地方 放在这里 这个改成get,这个代码就算是改完了。

在这个地方因为我们的代码 我们可以看一下,我们那个代码,就使用rpx2px这个代码,这个代码它其实是运行时的,是数据拿到以后运行的时候然后才去调用它,也就是它调用的时机其实不会早于我们获取系统信息的时机,这个改完了 接下来我们还要再看一下这个系统里面是不是还有其他的地方也有类似的一个代码,我们可以统一地去看一下,可以搜索这个方法,因为这个方法它是一个同步接口,直接可以拿到结果,一般情况下我们都很喜欢用这个接口,调完了以后直接可以取它的属性确实比较爽,但是使用它 如我们前面所讲,确实是有一些可能存在的一些潜在问题。

搜索一下我们目前这个里面看到有哪些,这个system_info_manager,这是我们自己刚创建的文件,这个文件可以忽略,然后这个地方有,这是我们构建的目录,这个也可以忽略掉,这个也是构建的 也可以忽略掉,这个地方是我们的用户主页,用户主页的data,上面我们用了这个,然后调它取它的这个属性 还有这个地方,我看是几个地方123,这个要取消一下,不小心给它替换了3 然后是45,有好些个地方onShow这个调用,它是展示的时候去调用的,这个其实问题,它是属于运行时问题还不是特别大,有问题是在我们这个数据对象里面,这个也可以,这个是在scroll里面,但是像这些信息其实我们是可以把它缓存下来的,没必要每次都调用。重点是这两个信息,我们这样在这个页面对象里面写一个windowWidth,windowWidth我们让它等于我们当前我们模拟器的一个宽度,当前用的模拟器是是414 736,我们这个地方可以让它是414,然后还有另外一个statusBarHeight这个值一般是22,搞两个默认值,两个默认值,看一下后面还有没有用到。

这个其实没有用到,这个无所谓,这个不需要,我们只需要搞我们后面它有重复使用的,这个是重复使用的,然后这个是Width,这个也是Width,所以我们只要搞一个就可以了,然后我们这个信息我们要在哪里去搞呢,在它本身加载的时候,比如说,或者我们放到onReady里面也可以,放到onReady里面,this.data,我们先写我们的对象里面的,先搞它 然后它等于wx.getSystemInfoSync,然后取它的windowWidth,我们这个时候取注意,那个系统信息它其实已经拉过来了,这个地方虽然也是这样的一种调用方式,没有引入我们的管理器的实例,但是我们这样取的话其实也是相当于从本地把这个信息给取出来了,然后是第二个,这个它是取的这个信息,它在data这个里面 我们也可以这样写,this.data就等于它 把这个代码给它拿过来给它一个默认值,放在这里,放在这里跟放在上面它不一样,因为这个是运行时获取,这个时候的获取的时候它已经没有同步负担了。

然后还有一个是left,这个left我们可以让它有一个默认值,然后默认的是等于414-17 对不对,然后left等于它然后减17,还有两个地方滚动的时候调用,其实它没有再去获取了,因为我们上面this对象里面本来已经有了一个缓存的数据了,直接取就可以了,包括在onShow里面这个地方也不需要 改成this就好了,它只需要取一次,因为像这种屏幕信息它是不会老变化的,现在所有的获取都在这个地方了,为了让我们这个数据 在这个页面加载完成以后它可以自动更新。

我们还可以设一个关于data上面信息的一个设置,我们其实还可以用setData将这个给它,这样 还有下面left它的值是这个值,这两行就不需要了,这样就可以了,这样的话就实现了我们页面所需要的一些信息,这些信息它有默认值,页面不会出问题,在这个页面加载完成以后我们又取了正式值,让它更加精确,这个代码写完了,我们现在单击一下编译按钮重新看一下小程序的表现,没有问题。我们还可以跳到用户主页以后,因为我们刚才不是写了data对象,可以打开AppData面板看里面的值,它页面里面值也是可以在这里面可以查看的,这两个都不是 ,user这是我们设置的一个值对吧还有left,这两个值是我们设置的一个值,代码演示我们就说到这里。


下面我们看实践三,主页数据由全页拉取改为分页拉取。

目前我们在主页里面拉取后端的动态数据的时候,是全部数据一起拉取的 数据量有点大,从Network面板里面可以看到这个数据大概在8KB至10KB之间,这无疑会延缓我们首页的一个尽快展示,在实现上我们只需要先拉取一屏的数据,然后后面的数据完全可以按需要然后分页拉取,具体怎么改造,首先我们在retrieve_home_data.js文件里面将接口地址改一下,将全页拉取的地址改为单页拉取的地址,单页拉取以后,它这个数据量大概会在1KB左右或者是不到这样的一个大小,接着在首页的wxml文件里面为recycle-view添加scrolltolower这样的一个事件监听,同时在JS文件里面也添加一个相应的事件回调函数,这样基本上就可以完成我们改造了。

当然这里面有一个问题我们需要重申一下,使用ES6语法的时候里面有一个async await,这一对关键字我们一定要成对出现,在小程序里面使用async await语法关键字的时候,如果这个方法里面用了await,那么它外面它一定要加async的关键字,如果不加的话,和我们在首页里面定义的require方法一样,就是使用变量路径的时候一样,可能会出现让人抓狂的一些异常的状况,并且这个时候工具也不报错,调试区什么提示也没有,当然这个程序就是运行不起来,这种错误其实是最难查找和解决的,如果你在开发中遇到了类似的这种情况,其实没有什么别的好办法,就是做减法,你按时间上的一个倒序将最近添加的改动的代码一一的进行恢复 屏蔽,用这种比较笨的方法然后去解决这个问题,但一般很快用这种方法就能发现这个问题出现在哪里。

接下来我们看代码演示。

要实现这样的一个优化,首先我们要确认一下后端的接口是不是有分页拉取的接口,打开我们VSCode看一下我们最终的源码server api 然后home,看一下我们分页拉取,要传一个页码进来才可以实现分页拉取,这个方法是我们全部信息的一个拉取,然后它会返回1000条的模拟的数据,这个接口也是我们目前在用的一个接口,在它的下面这个地方有一个GetBy,它有一个参数是page,这是我们分页拉取的接口地址,它调用方式就是在我们home后面加个页码就可以了,这是页码默认从1开始的 第一页就是传1,这就是我们这个接口,将这个接口拷贝一下放到我们目前的项目里面,我们这个地方已经有了,我们简单看一下,那就不用拷贝了。

我们简单看一下我们这个代码它的一个实现,首先是页码,页码它作为参数已经传进来了,我们项目模板里面它关于接口的实现是这样的,前面我们说了这个名字,Get相当于是Get请求,如果它有一个By的话代表的是我们后面要有这个参数了,而且这个参数会作为我们URL路径的一部分而存在的,就是它的一个写法,这个地方我们也可以传一个size,size有一个默认值就是10,我们默认取10条的数据,这个数据量相对是比较少的,下面这些就是关于信息的一个构建,这个构建其实也很简单,跟我们前面的所有信息的返回的时候构建也是类似的,这个有了以后接下来我们就测试一下它的一个表现 ,这个接口我们要测试一下,确认一下我们接口地址 这样的一个地址打开一个终端,这个终端已经被占用了,我们换一个,用这个终端改一下这个地址,在后面我们传个1少了一个引号对吧,这就是我们取到的 这页码是1,size是10,后面是其他的数据,如果改的话你改一下变成2然后这个页码就变成2了 ,这样的一个接口 这个接口是没有问题的,下面我们就开始改造。

在我们代码里面,小程序代码里面找到我们拉取首页数据这个代码,这个代码在我们的library下面service在这个地方,然后在接口地址后面,因为这个地方是首页拉取,首页拉取它的就是第一页,所以这个地方直接传1就可以了,这就是我们拉取第一页的数据了,这一步完成以后,接下来我们还要去改造我们的首页上,在这个地方在我们的recycle-view上面要加一个事件监听,看一下我们最终的一个代码 它的写法,添加了一个scrolltolower这样的一个事件监听,这是我们要绑定的一个方法,下一步我们看这个方法的一个实现。

这个方法不复杂,比较简单,放在这里看一下它的实现,绑定以后这个地方有一个this.page+1,这个page我们目前肯定还没有,我们要放在我们的页面对象上默认等于1,然后它滚到底部的时候,让我们页码去加1,加1以后然后去拉取新的代码,因为promisify工具函数前面已经引入了,所以这个不需要重复引入了,然后把这个page给它传给我们后端接口,这个就是我们新接口 分页拉取接口,然后获取数据以后拿到这个结果再调用我们dealWithListData这个方法,在这个方法里面我们就处理新数据,这个地方就是处理新数据,所以这个也不会有问题,另外我们还有个地方还需要改,我们第一次获取到数据的时候,在我们的onReady,不是这个 onLoad,拿到这个数据dealWith这个里边,从data里面取这个数据,它data其实里面它是有page的,把这个page给它取出来,然后我们看一下最终代码的一个实现。

把page给它取出来,取出来以后 然后setData的时候有一个设置,但其实由于它这个地方应该是有点问题的,因为它page其实在Page对象上面,不在这个data里面,然后取这个值给它赋值过去,别的就没有了,所以我们这个地方取出来以后,我们可以this.page等于page这样就可以了,这样应该就可以了,这个代码已经写完了,现在我们单击编译一下看一下运行效果。首先它默认会加载第一页的数据,我们打开Network面板看一下这个地方 这有一个1,然后这个接口地址现在已经变成了这个,然后再看一下它加载的数据大小size等于819B,比原来8KB 10KB已经小了很多了 ,接下来我们再滚动 往下滚动 然后让它见底,见底以后,报错了,我们看一下在哪里报错了,在这个方法里面报错了,提示没有,不能在空对象上访问这个page 59我们看一下这个方法应该在上面,它等于this 它加1,这个打印信息现在有了吗,看一下前面有没有打印 没有打印 它还没有打印出来这个信息,我们清一下编译缓存 重新再测试一下。

这个错误又再一次的触发了,刚才我们看到这个地方出了一个问题,在我们滚动的时候它报了一个错误,提示不能在一个空对象上。未定义的对象上去获取page属性 ,这种情况我们怎么办呢,我们靠猜的话也不行可以打个断点,打开Source面板以后,在这个地方打个断点看一下,然后再次滚动,让代码的错误再次出现,当我们把鼠标放在this上面的时候发现它是一个undefined,这个地方也就是我们错误产生的根源了,我们this对象丢失了,this对象丢失在JS开发里面是一个相当普遍的事情,这个对象丢失跟我们的方法事件回调函数的一个写法有关系。

我们可以这样改,将async给它取出来,将这种箭头函数的这种写法改一下,把这个async放在它的前面,这样跟刚才效果是一样的,它只是变了一种写法而已,然后我们再单击测试一下 看它这个表现,继续向上滑动触发这个事件 让它触发事件,已经11页出来了对吧,第11条出来说明它这个接口已经加载了,第2页的数据已经加载了,打开Network面板可以看到这个地方有一个新页的数据的加载,再往下滑动,我们发现第3页,然后再往下第4页,现在我们已经实现了分页拉取了,至于我们这个地方this对象丢失这个问题 这个写法其实我们还可以再尝试另外一种写法,我们刚才那种写法的一个修改版,我们这个地方不用箭头函数,用普通的函数写法,用function这样一种写法,写完以后我们再测试一下看它表现。

向上滑动没有问题,页面都可以加载 ,我们先前那个写法为啥不可以,为啥我们后面这两种写法就可以了,为什么,因为我们先前那种写法是箭头函数,是我们将一个箭头函数赋值给我们的这样的一个变量,它其实相当于是一种函数变量的写法,箭头函数在这个JS里边本身是没有这个this对象,它的this对象其实取决于它在哪一个所在的父函数,它的父函数里边的this指向哪里它就指向哪里,但是偏偏不巧,就是我们onRecycleViewScrollToLower这样的一个回调函数,它是我们异步线程由这个事件驱动然后触发的,它是从异步线程过来的,就所有的从异步线程过来的回调函数我们都要小心,因为它一来一去,它里边this对象很容易丢失,这个就是我们的这样的一个改写,没有问题了,代码演示我们就说到这里。


最后我们总结一下,在JS页面文件里面不是在Page或者是App对象之内直接添加这个代码的话,和App.onLaunch和Page.onLoad里面添加这个代码一样,如果它们是同步代码,那么它们就会阻塞主线程的一个执行,这个时间会花费在代码的注入阶段,因此我们尽量不要在这个页面的文件顶部直接编写同步代码。还有使用串发复合命令对象可以将跨文件的代码执行点对齐把它们给串起来,实现这个代码的延迟执行,正如我们刚才在示例源码里面所做的那样,这个数据在哪里定义就在哪里拉取,条件成熟的时机在哪里产生,我们就哪里触发命令的执行,坚持这样的一个编写原则,可以显著提升我们代码的可阅读性、可维护性。

有时候为了应用面向对象的软件设计思想,我们建了很多的基类和派生类,将相互联系的代码割裂到了不同的文件里边,这样的代码它其实是不太容易理解的,我们为了理解它们在大脑里面需要建一个不小的临时栈,然后去存储暂时没有理解的内容,还需要不断地去运行这个程序边查看边阅读,一步一步直到阅读了所有的源码文件,并且同时我们还需要努力地在大脑里面建立对它们之间关系的一个网络,等到最后所有文件都读完了,我们这个时候才恍然大悟原来作者他是这样编写的,他这个地方这样设计是这样的一个意思,使用串发复合命令以及我们在6.1课里面学习的并发复合命令,我们将这些命令应用于我们这个软件设计里面,可以显著改善因应用面向对象设计思想而带来的软件设计上的一个弊端。这节课我们就讲到这里,上面的网址是我们本节课涉及到的文档地址。

点击查看开放文档:


这节课我们主要学习了使用串发复合命令延迟调用代码,并实现了一个系统消息管理器也就是SystemInfoManager这个管理器,它目前还不算十分完善,但是你可以根据自己的需要随时进行拓展。下节课我们学习一个小技巧,在小程序切换至后台以后关闭对setData方法的一个调用。

最后看一下思考题。这里有个问题请你思考一下,我们知道小程序的运行时状态至少有三种,一个是正常状态,第二个是从前台进入后台以后它有一个5秒的后台运行状态,第三个是从后台运行状态至被销毁之前它有一个30分钟的挂起状态,在挂起状态里面它这个代码本身已经被冻结,所有代码都不会运行,这个阶段 其实我们不需要管,在后台运行状态里面,虽然这个时候它这个视图不可见,但是这个代码仍然是可以运行的,那么这个时间段以内,有什么样的简单的一个办法可以节省设备资源的一个消耗吗?这个问题现在留给你思考一下,我们下节课深入探讨一下这个问题。

最后一次编辑于  2022-07-14
赞 2
收藏
登录 后发表内容

小程序性能优化实践

课程标签