首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >第二章 小程序开发指南5-5

第二章 小程序开发指南5-5

原创
作者头像
抡大勺学小程序开发
修改2025-08-25 20:44:25
修改2025-08-25 20:44:25
1730
举报
2.5 小程序的协同工作和发布

在中大型的公司里,人员的分工非常仔细,一般会有不同岗位角色的员工同时参与同一个小程序项目。考虑到这样的情况,小程序平台设计了不同的权限管理使得项目管理者可以更加高效管理整个团队的协同工作。

以往在开发完网页之后,需要把网页的代码和资源放在服务器上,让用户通过互联网来访问。在小程序的平台里,开发者完成开发之后,需要在开发者工具里提交小程序的代码包,然后在小程序管理平台中发布小程序,用户可以通过搜索或者其他入口进入小程序。

在本章会把团队的协同工作的注意事项和小程序发布前后所涉及的概念和流程做一些介绍。

2.5.1 协同工作

2.5.1.1 人员组织结构和权限分配

多数情况下,一个团队多人同时参与同一个小程序项目,每个角色所承担的工作或者权限都不一样,中大公司的分工更为仔细。为了更形象的表达团队不同角色的关系以及权限的管理,下面通过虚拟一个项目成员组织结构来描述日常如何协同合作完成一个小程序的发布。组织关系如图2-xx所示。

图2-xx 虚拟小程序项目组

项目管理成员负责统筹整个项目的进展和风险、把控小程序对外发布的节奏。产品组提出需求。设计组与产品讨论并对需求进行抽象,设计出可视化流程与图形,输出设计方案。开发组依据设计方案,进行程序代码的编写。代码编写完成后,产品组与设计组体验小程序的整体流程。测试组编写测试用例并对小程序进行各种边界测试。项目一般的成员构成与工作流程如图2-xx所示。

图2-xx 提需求到发布小程序的流程

为了便于管理,小程序平台给不同角色定义了7种权限,如表2-xx所示。

权限

说明

开发者权限

可使用小程序开发者工具及开发版小程序进行开发

体验者权限

可使用体验版小程序

登录

可登录小程序管理后台,无需管理员确认

数据分析

使用小程序数据分析功能查看小程序数据

开发管理

小程序提交审核、发布、回退

开发设置

设置小程序服务器域名、消息推送及扫描普通链接二维码打开小程序

暂停服务设置

暂停小程序线上服务

表2-xx 小程序平台的7种权限

管理者可以很方便的分配这些权限给项目的各个组织成员,小程序的管理比传统的网页开发和App应用开发更为简单便捷。虚拟组织的成员权限可以按照表2-xx进行分配:

成员

权限分配

项目管理组成员

拥有所有权限

开发组成员

开发者权限 / 体验者权限 / 数据分析

产品组成员

体验者权限 / 数据分析

测试组成员

体验者权限

表2-xx 虚拟组织成员的权限分配

需要留意,项目管理者控制整个小程序的发布、回退、下架等敏感操作,不应把敏感操作的权限分配给不相关人员。

2.5.1.2 小程序的版本

一般的软件开发流程,开发者编写代码自测开发版程序,直到程序达到一个稳定可体验的状态时,开发者会把这个体验版本给到产品经理和测试人员进行体验测试,最后修复完程序的Bug后发布供外部用户正式使用。小程序的版本根据这个流程设计了小程序版本的概念,如表2-xx所示。

权限

说明

开发版本

使用开发者工具,可将代码上传到开发版本中。 开发版本只保留每人最新的一份上传的代码。点击提交审核,可将代码提交审核。开发版本可删除,不影响线上版本和审核中版本的代码。

体验版本

可以选择某个开发版本作为体验版,并且选取一份体验版。

审核中版本

只能有一份代码处于审核中。有审核结果后可以发布到线上,也可直接重新提交审核,覆盖原审核版本。

线上版本

线上所有用户使用的代码版本,该版本代码在新版本代码发布后被覆盖更新。

表2-xx 小程序的版本

考虑到项目是协同开发的模式,一个小程序可能同时由多个开发者进行开发,往往开发者在小程序开发者工具上编写完代码后需要到手机进行真机体验,所以每个开发者拥有自己对应的一个开发版本。因为处于开发中的版本是不稳定的,开发者随时会修改代码覆盖开发版,为了让测试和产品经理有一个完整稳定的版本可以体验测试,小程序平台允许把其中一个开发版本设置成体验版,因此建议在项目开发阶段特殊分配一个开发角色,用于上传稳定可供体验测试的代码,并把他上传的开发版本设置成体验版。

刚刚说到开发者需要真机调试开发版本时,可以点击开发者工具的预览按钮,此时开发者工具会打包当前项目,并上传到微信服务器生成一个二维码,开发者使用当前开发身份的微信扫描二维码就可以在手机上体验对应的开发版本,如图2-xx所示。

图2-xx 预览小程序开发版

在小程序管理平台上可以选取某个开发版本作为体验版本,操作成功之后可以得到一个体验版的二维码,当前项目有权限的体验者均可用其对应的微信号进行扫码体验。

2.5.2 用户体验审视

在小程序发布之前,建议开发者务必对小程序做认真严格的用户体验审视。有创造性的产品思路、友好流畅的用户体验和稳定谨慎的运营也是一款优秀小程序能够脱颖而出的关键因素。

本小节将从产品和设计两个方面,帮助开发者在小程序发布之前审视自身产品设计和用户体验情况,达到较高的用户体验水准,且是对用户友好的一款产品。

2.5.2.1 产品和运营思路

希望开发者能坚持“一切以用户价值为依归”这一产品观,并创造和发挥价值。产品设计过程中,开发者亦需要保持一定的克制心态。繁复的需求和商业行为,在用户利益面前都需要谨慎平衡。

在运营行为上,小程序需要遵从微信小程序的运营规范。若开发者的小程序违反了其中的条款、相关平台规则或法律法规,或对公众平台、开放平台造成了影响,可能会被强制处罚。条款将根据新问题、相关法律法规或产品运营需要对其内容进行修改并更新,详细条款请查阅小程序运营文档。

2.5.2.2 体验和设计评估

在小程序的设计和开发阶段,希望开发者始终将优秀的用户体验作为产品目标之一,通过积极的用户体验和设计评估,在不断的迭代中完善用户体验,用心打磨小程序,从而更好的实现产品价值,激发用户正向情感。因此,建议小程序在开发过程中以及发布之前,开发者务必对自身产品的体验和设计做全面走查。

开发者可以依据以下9点基础设计原则,对小程序的体验进行评估。

2.5.2.2.1 导航清晰

导航是确保用户在网页中浏览跳转时不迷路的最关键因素。导航需要告诉用户,当前在哪,可以去哪,如何回去等问题。开发者在小程序的设计中,应确保各个页面之间层级清晰明确。每个界面的导航,都指向清晰,有路可退,实际应用样例请参考如图2-xx所示。

图2-xx 不同页面之间应保持明确的导航层级

2.5.2.2.2 流程明确

为了让用户能够顺畅地使用小程序,在用户进行操作时,应确定当前页面只设置了单一任务,且不出现目标流程之外的内容。此举有利于让用户明确当前操作的目的,从而集中精力聚焦当前任务,并通过简单操作达到结果。在图2-xx中,出现了目标流程之外的操作,使用户体验受阻。

图2-xx 出现目标流程之外的操作而打断用户的体验

2.5.2.2.3 重点突出

每个页面都应该有明确的重点,以便用户每进入一个新页面的时候都能快速理解页面的内容。在确定过了重点的前提下,应尽量避免页面上出现其它与用户的决策和操作无关的干扰因素。图2-xx以搜索功能作为案例说明,突出的搜索入口以达到重点突出,操作明确。

图2-xx 案例说明:搜索功能

图2-xx对比了有主次之分的按钮排列与平级按钮排列的效果,有主次之分的视觉展示能让操作决策更快速。

图2-xx 案例说明:多按钮情况

2.5.2.2.4 符合预期

对用户友好的产品,需要在其设计阶段便将自身信息架构和模型与用户的心理模型匹配,以便用户能够依据以往的使用经验或者其他生活经验,降低使用的理解和学习成本,从而快速达成使用的目的。

2.5.2.2.5 等待与反馈

在用户使用产品时,往往页面过长时间的等待会引起不良情绪。在不得不需要用户以一定的加载等待时间作为代价时,需要用明确的等待状态告知用户,以舒缓用户在等待时期的不良情绪。图2-xx是不同的加载样式举例。

图2-xx 加载样式举例:标题栏加载、Toast加载、按钮加载

在设计加载等待状态时,应该注意以下事项:

(1)若载入等待时间较长,应提供取消操作,并使用进度条显示载入的进度;

(2)载入过程中,应该保持动画效果;无动画效果的加载很容易让人产生该界面已经卡死的错觉;

(3)不要在同一个页面同时使用超过1个加载动画。

此外,对于用户的操作结果,小程序也需要给出明确的结果反馈,以增强用户的操作信心和控制感。开发者可以根据实际情况,选择不同的反馈样式。例如,对于页面局部的操作,可在操作区域予以直接反馈。对于页面级别的操作结果,可以使用弹出式提示、模态对话框或结果页面展示。具体示例如图2-xx所示。

图2-xx提示样式举例:图标弹出提示、文字弹出提示、对话框

2.5.2.2.6 异常处理

在设计任务和流程时,往往用户会因为各种原因导致操作失败。此类异常场景往往是用户最为沮丧和需要帮助的时候,因此,要注意在异常状态下的设计。在出现异常的时候需要给予用户清晰的状态提示,并告知解决方案,使其有路可退。

上文提到的模态对话框和结果页面都可以作为异常状态的提醒方式。除此之外,在表单项较多的页面中,应该明确指出出错项目,以便用户修改,如图2-xx所示。

图2-xx 表单异常提醒

2.5.2.2.7 内容和文案准确友好

在产品通过文案或者页面表达内容时,需要斟酌使用的内容和文案。使用的语言应当简洁,礼貌并容易被用户理解。此外,还要注意专业术语需要被清楚解释,特有词汇全局需要用统一的特定表达,重要内容能够被快速获取,且页面不存在无关文案干扰用户决策。

2.5.2.2.8 和谐统一

小程序内的设计风格应该是统一、和谐且具有延续性的,这样才能确保用户建立完整的产品品牌认知,更好地辨析不同的小程序。

2.5.2.2.9 平台适配

在小程序的设计过程中,应该充分考虑iOS与Android平台不同的设计规范,对设计进行适当调整以适应不同平台上用户的使用习惯。

设计文档内容将不断进行修改、完善并更新,可以查看线上资讯以获取最新小程序的信息。

2.5.2.3 用户体验测试和完善体验

建议开发者在发布小程序之前,结合体验和设计评估过程,同步进行用户体验测试,以发现更多设计在实际场景和应用中存在的难以预见的可用性问题。或者以用户测试环节,验证体验和设计评估迭代成果,在不断的更新和迭代中打磨小程序体验。

以下是关于进行用户体验测试的一些建议:

(1)用户体验测试,可以在设计、开发、测试等全部阶段使用。是一种低成本检测设计质量的方法。

(2)尽早进行用户体验测试。在产品及设计过程中,尚未启动开发时,便可以用低保真/高保真模型进行用户体验测试,以检测设计质量。这样更有利于及早发现问题并进行调整,减少开发成本。

(3)用户测试之前需要确定好需要被检验的流程和任务,需要为用户构建明确的目标,并提示用户以完成任务的方式完成目标。

(4)最好选择产品真实的受众作为被测试的用户。并在测试时,使用户处于真实的场景和时间下。

(5)设计开放性的问题让用户回答。不用带有主观性的询问语言,引导用户回答。

(6)在用户测试过程中,需要全程做好记录。

2.5.3 发布

2.5.3.1 发布前最后的检查

有不少开发者在发布小程序后发现正式版小程序无法正常使用,下面罗列一下开发者在发布前常常遗漏的点:

(1)如果小程序使用Flex布局,并且需要兼容iOS8以下的系统时,请检查上传小程序包时,开发者工具是否已经开启“上传代码时样式自动补全”的功能。

(2)小程序使用的服务器接口应该走HTTPS协议,并且对应的网络域名确保已经在小程序管理平台配置好。

(3)在测试阶段不要打开小程序的调试模式进行测试,因为在调试模式下,微信不会校验域名合法性,容易导致开发者误以为测试通过,导致正式版小程序因为遇到非法域名无法正常工作。

(4)发布前请检查小程序使用到的网络接口已经在现网部署好,并且评估好服务器的机器负载情况。

当体验版进行充分的检查和测试后达到发布状态时,项目管理者可以在小程序平台进行提交审核的操作,提交审核后,微信审核团队会根据相关的运营规范进行提审小程序的审核。审核通过之后,管理者可以随时发布自己的小程序。

2.5.3.2 发布模式

小程序提供了两种发布模式:全量发布和分阶段发布。全量发布是指当点击发布之后,所有用户访问小程序时都会使用当前最新的发布版本。分阶段发布是指分不同时间段来控制部分用户使用最新的发布版本,分阶段发布也称为灰度发布。一般来说,普通小程序发布时采用全量发布即可,当小程序承载的功能越来越多,使用的用户数越来越多时,采用分阶段发布是一个非常好的控制风险的办法。因为随着程序的复杂度提高以及影响面的扩大,新版本的代码改动或多或少会带来Bug,作为服务方当然不希望异常的服务状态一下子扩散到整个用户群体,此时应该通过分阶段发布来逐步观察服务的稳定性,再决定是否进行全量发布。

还需要留意一点,并非全量发布之后,用户就会立即使用到最新版的小程序,这是因为微信客户端存在旧版本的小程序包缓存。用户在使用小程序时会优先打开本地的小程序包,微信客户端在某些特定的时机异步去更新最新的小程序包。一般认为全量发布的24小时之后,所有用户会真正使用到最新版的小程序。

2.5.3.3 小程序码

很多场景下用户会通过扫码快速进入一个小程序,在小程序设计的初期,小程序平台提供的二维码的形式。但是发现用户在扫描一个二维码时,他并不知道当前这次扫码会出现什么样的服务,因为二维码的背后有可能是公众号、小程序、网页服务、支付页面、添加好友等不同的服务。为了让用户在扫码之前就有一个明确的预期,因此微信设计了小程序码,如图2-xx所示。

图2-xx 小程序数据助手”的小程序码

小程序码在样式上更具辨识度和视觉冲击力,相对于二维码来说,小程序主题的品牌形象更加清晰明显,可以帮助开发者更好地推广小程序。在发布小程序之后,小程序管理平台会提供对应的小程序码的预览和下载,开发者可以自行下载用于线上和线下的小程序服务推广。

2.5.4 运营

在发布完小程序之后,还需要关注小程序的运行数据,分析数据背后的原因,帮助小程序产品迭代优化和运营,开发者可以利用小程序平台里边提供的面向产品的运营数据分析和面向开发的运维中心来完成数据运营的工作。小程序提供了微信开发者社区供开发者互助答疑,小程序官方也会在社区上同步各类最新的信息,例如小程序Bug的修复情况、解答一些开发者疑问等等。

2.5.4.1 数据分析

2.5.4.1.1 常规分析

开发网页和App应用都需要开发者自己通过编写代码来上报访问数据,小程序平台则直接内置在宿主环境底层,无需开发者新增一行代码。

小程序数据分析是面向小程序开发者、运营者的数据分析工具,提供关键指标统计、实时访问监控、自定义分析等,帮助小程序产品迭代优化和运营。主要功能包括每日例行统计的标准分析,以及满足用户个性化需求的自定义分析。开发者可以登录小程序管理平台,通过左侧“数据分析”菜单可以进入数据分析查看。

图2-xx为小程序当前日期的昨日关键用户指标,反映小程序昨日用户活跃概况,以及对比一天前、一周前、一月前的增长率。

图2-xx 小程序昨日关键用户指标

还可以查看关键指标的趋势,包括累计访问用户数、打开次数、访问次数等指标,可选择时间进行对比,如图2-xx所示。

图2-xx 小程序趋势概况

在小程序平台还可以查看小程序的访问分析,包括小程序用户访问规模、来源、频次、时长、深度、留存以及页面详情等数据,可以用于具体分析用户新增、活跃和留存情况。为了让开发者可以更加详细了解使用的用户情况,数据分析也提供了小程序的用户画像数据,包括用户年龄、性别、地区、终端及机型分布。

2.5.4.1.2 自定义分析

除了小程序宿主环境提供的数据分析能力,为了让开发者可以更加灵活多维和近实时的用户行为分析,小程序平台提供了自定义上报的特性,开发者可以对用户在小程序内的行为做精细化跟踪,满足页面访问等标准统计以外的个性化分析需求。例如,电商类小程序通过配置自定义上报,收集数据,可以完成如下分析:

(1)购买商品的人,各省份、城市、年龄、性别的分布如何?不同用户群购买的商品数量、商品价格有什么差别?

(2)用户访问商品页、查看商品详情、查看评论、下单、支付、完成购买,逐步的转化率如何?不同用户群的转化是否有差异?

(3)今天参与线上活动的用户,各个时段(小时级)的活跃度如何?

在小程序官方提供的小程序自定义分析文档里有非常详细的指引,为了不占篇幅,不在这里展开阐述,有兴趣的读者可以查阅相关文档。

2.5.4.2 运维中心

程序变更总会伴随一些Bug的产生,小程序的逻辑代码由JavaScript脚本编写,JavaScript可以通过一些方法来检测运行时异常的发生,小程序宿主环境已经内置了异常检测的模块,并且上报到小程序平台,开发者可以通过小程序平台的“运维中心”查看具体的错误日志,开发者根据日志详细定位自己代码的异常处并及时修复。通过开发者自己主动去查阅日志定位错误的方式会显得比较被动,因为开发者不可能时时刻刻都在电脑前关注这个日志的变化曲线,因此比较推荐的方法是通过运维中心的监控告警功能,开发者设置合理的错误阈值,再通过加入微信告警群,当小程序运行发生大量异常现象时,微信告警群会提醒开发者,此时开发者再登录小程序管理平台查阅错误日志。小程序平台目前只提供了脚本错误告警,如果需要监控异常的访问或者服务接口耗时时,需要开发者自行开发监控系统,并在小程序逻辑代码加上对应的数据上报。

2.5.4.3 微信开发者社区

小程序的宿主环境和微信客户端持续在迭代变更,避免不了变更的同时引起一些Bug导致小程序无法正常工作,所以微信官方提供了微信开发者社区,开发者可以在社区上进行提问或者查看问题进展,小程序官方会在社区第一时间同步各种Bug的解决办法。由于小程序官方的人力有限,所以鼓励开发者在社区上互助答疑,提高效率。

为了提高发问的质量,建议开发者在提问时提供尽可能多的信息,以便回答者可以根据信息去复现问题并解决问题。下边给出一般提问反馈Bug时需要的信息:

(1)一个清晰的问题标题,直接简洁的描述问题的核心点,可以有效的让回答者在问题列表中更快的理解你的难处。

(2)Bug的类型归属,例如是开发者工具出现的问题,还是小程序API出现的问题,开发者要描述清楚。

(3)ug发生的环境是什么?是在微信客户端的iOS端还是安卓端,对应的微信版本是多少,宿主环境的版本是多少?

(4)Bug详细的描述,开发者应该清晰地描述Bug的具体表现,产生Bug的具体途径,并且给出自己期望的结果,以便回答者可以验证是否能解决此问题。

(5)给出一个最简单能够复现问题的代码能够让回答者更快的定位问题所在。

开发者也可以利用微信开发者社区提出一些需求,小程序官方会从社区的反馈中了解到开发者的很多需求,在小程序的版本迭代中针对部分需求提供了对应的组件或者API支持。

在本章中阐述了软件项目的各个角色的合作流程,罗列了小程序提供的角色权限管理,让项目管理者可以更便捷地管理团队成员。同时为了配合项目开展的各个流程,小程序提供了不同的版本,开发者要合理运用不同版本的特点来进行开发、测试、体验等工作。还给了一些用户体验审视的原则,开发者可以在发布小程序之前认真审视自己的小程序体验,以达到更高的用户水准。最后提到发布时可以利用小程序平台提供的两种模式进行发布,开发者根据业务情况选择合理的模式进行发布,并且利用小程序平台提供的数据分析服务来帮助运营自己的小程序,让用户可以使用一个稳定可靠的小程序服务。

2.6 底层框架

经过前面章节的学习,相信大家对小程序开发已经非常熟悉了。从这一章开始,会向大家介绍在编写小程序代码背后的方方面面的细节,而在这一章里会先深入小程序底层,介绍底层的架构设计,一些细节原理,以及大家所熟悉的组件系统。通过这一章的学习,可以在之后的小程序开发中编写出更合理的代码,遇到问题可以有依据可循,能想到更好的解决办法。

2.6.1 双线程模型

在前面章节中就有提到过小程序是基于双线程模型的,在这个模型中,小程序的逻辑层与渲染层分开在不同的线程里运行,这跟传统的Web单线程模型有很大的不同,使得小程序架构上多了一些复杂度,也多了一些限制。至于为何选择基于双线程模型来搭建小程序,以及因此而产生的问题和解决方案,接下来将一一进行介绍。

2.6.1.1 技术选型

微信官方在对小程序的架构设计时的要求只有一个,就是要快,包括要渲染快、加载快等。当用户点开某个小程序时,期望体验到的是只有很短暂的加载界面,在一个过渡动画之后可以马上看到小程序的主界面。

首先需要确定用什么技术来渲染小程序界面,这是跟开发者的学习门槛息息相关的。

一般来说,渲染界面的技术有三种:

(1)用纯客户端原生技术来渲染

(2)用纯Web技术来渲染

(3)介于客户端原生技术与Web技术之间的,互相结合各自特点的技术(下面统称Hybrid技术)来渲染

由于小程序的宿主是微信,所以不太可能用纯客户端原生技术来编写小程序。如果这么做,那么小程序代码需要与微信代码一起编包,跟随微信发布版本,这种方式跟开发节奏必然都是不对的。因此,需要像Web技术那样,有一份随时可更新的资源包放在云端,通过下载到本地,动态执行后即可渲染出界面。

但是,如果用纯Web技术来渲染小程序,在一些有复杂交互的页面上可能会面临一些性能问题,这是因为在Web技术中,UI渲染跟JavaScript的脚本执行都在同一个单线程里执行,这就容易导致一些逻辑任务抢占UI渲染的资源。

按照上面的讨论,使用纯客户端原生技术或者纯Web技术都有各自的缺点,那么如果使用两者结合起来的Hybrid技术来渲染小程序,能否优于各自独立渲染的技术方案呢?实际上,这种Hybrid技术在业界过去几年里演化过数种技术方案,典型的如早期的PhoneGap,还有近两年流行的ReactNative(下面简称RN),还有像微信网页里的JS-SDK这种轻量级的应用。

从渲染底层来看,PhoneGap与微信JS-SDK是类似的,它们最终都还是使用浏览器内核来渲染界面。而RN则不同,虽然是用Web相关技术来编写,同样是利用了JavaScript解释执行的特性,但RN在渲染底层是用客户端原生渲染的。实际上,小程序最初选型时RN是候选之一,虽然说RN是结合了React框架的代码组成方式,但是完全可以剥离React框架这套写法,定义出更符合小程序特点的代码组成方式。不过,最终并没有选择这种类RN技术,原因有三:

(1)RN所支持的样式是CSS的子集,会满足不了Web开发者日渐增长的需求,而对RN的改造具有不小的成本和风险。

(2)RN现有能力下还存在的一些不稳定问题,比如性能、Bug等。RN是把渲染工作全都交由客户端原生渲染,实际上一些简单的界面元素使用Web技术渲染完全能胜任,并且非常稳定。

(3)RN存在一些不可预期的因素,比如近期就出现了许可协议问题。

最终选择类似于微信JSSDK这样的Hybrid技术,即界面主要由成熟的Web技术渲染,辅之以大量的接口提供丰富的客户端原生能力。同时,每个小程序页面都是用不同的WebView去渲染,这样可以提供更好的交互体验,更贴近原生体验,也避免了单个WebView的任务过于繁重。此外,界面渲染这一块定义了一套内置组件以统一体验,并且提供一些基础和通用的能力,进一步降低开发者的学习门槛。值得一提的是,内置组件有一部分较复杂组件是用客户端原生渲染的,以提供更好的性能,在后面的章节中将深入介绍。

2.6.1.2 管控与安全

基于Web技术来渲染小程序是存在一些不可控因素和安全风险的。这是因为Web技术是非常开放灵活的,可以利用JavaScript脚本随意地跳转网页或者改变界面上的任意内容。

微信原本定义了一套内置组件以提供统一的体验,用户进入小程序时,小程序代码包会被拉到本地,这使得小程序可以离线浏览(只要小程序开发者把一些应用数据缓存到了本地),但要是开发者通过JavaScript把渲染小程序的WebView跳转到其他在线网页,这个体验就变得非常糟。

除此之外,微信也提供一种可以展示敏感数据的组件(这些数据只能被展示,开发者并不能拿到数据),若开发者可以通过JavaScript操作界面(DOM树),从而直接获取这些敏感数据,那么小程序毫无安全可言。

为了解决管控与安全问题,必须阻止开发者使用一些浏览器提供的,诸如跳转页面、操作DOM、动态执行脚本的开放性接口。假设一个一个禁止,那势必会进入一个攻防战,这是因为JavaScript的灵活性以及浏览器接口的丰富性,很容易遗漏一些危险的接口,而且就算被找到所有危险的接口,也许在下一次浏览器内核更新而新增了一个可能会在这套体系下产生漏洞的接口,这样还是无法完全避免。

因此,要彻底解决这个问题,必须提供一个沙箱环境来运行开发者的JavaScript代码。这个沙箱环境不能有任何浏览器相关接口,只提供纯JavaScript的解释执行环境,那么像HTML5中的ServiceWorker、WebWorker特性就符合这样的条件,这两者都是启用另一线程来执行JavaScript。但是考虑到小程序是一个多WebView的架构,每一个小程序页面都是不同的WebView渲染后显示的,在这个架构下不好去用某个WebView中的ServiceWorker去管理所有的小程序页面。

得益于客户端系统有JavaScript的解释引擎(在iOS下是用内置的JavaScriptCore框架,在安卓则是用腾讯x5内核提供的JsCore环境),可以创建一个单独的线程去执行JavaScript,在这个环境下执行的都是有关小程序业务逻辑的代码,也就是前面一直提到的逻辑层。而界面渲染相关的任务全都在WebView线程里执行,通过逻辑层代码去控制渲染哪些界面,那么这一层当然就是所谓的渲染层。这就是小程序双线程模型的由来。

2.6.1.3 天生的延时

既然小程序是基于双线程模型,那么就意味着任何数据传递都是线程间的通信,也就是都会有一定的延时。这不像传统Web那样,当界面需要更新时,通过调用更新接口UI就会同步地渲染出来。在小程序架构里,这一切都会变成异步。

异步会使得各部分的运行时序变得复杂一些。比如在渲染首屏的时候,逻辑层与渲染层会同时开始初始化工作,但是渲染层需要有逻辑层的数据才能把界面渲染出来,如果渲染层初始化工作较快完成,就要等逻辑层的指令才能进行下一步工作。因此逻辑层与渲染层需要有一定的机制保证时序正确,这些工作在小程序框架里会处理好,开发者只需要理解生命周期,以及控制合适的时机更新UI即可。更多的运行流程细节会在后面的章节中详细介绍。

除了逻辑层与渲染层之间的通信有延时,各层与客户端原生交互同样是有延时的。以逻辑层为例,开发者的代码是跑在逻辑层这个线程之上,而客户端原生是跑在微信主线程(安卓上的线程)之上,所以注册给逻辑层有关客户端能力的接口,实际上也是跟微信主线程之间的通信,同样意味着有延时。这也是看到大部分提供的接口都是异步的原因。

在理解了小程序架构下很多天生的延时后,会更容易想到一些问题的解决方法。比如,在使用一块画布(canvas组件)做图像处理并导出照片时,会习惯地在调用draw方法渲染后立即调用wx.canvasToTempFilePath接口来导出图片,实际上很有可能调用的时刻画布还未完成渲染而使导出的图片不是期望的效果。示例代码如下:

var ctx = wx.createCanvasContext('myCanvas');

ctx.fillRect(0, 0, 100, 100);

// ……

ctx.draw();

// 以下调用应该要在ctx.draw调用的回调函数里执行

wx.canvasToTempFilePath({

  canvasId: 'myCanvas',

  success(res) {

    console.log('canvasToTempFilePathresult: ' + res.tempFilePath);

  }

});

2.6.2 组件系统

小程序的视图是在WebView里渲染的,那么搭建视图的方式自然就需要用到HTML语言。如果直接提供HTML的能力,那么前面章节所介绍的为解决管控与安全而建立的双线程模型就成为摆设了。开发者可以利用A标签实现跳转到其它在线网页,也可以动态执行JavaScript等。除了管控与安全外,还有一些的不足之处:

(1)标签众多,增加理解成本;

(2)接口底层,不利于快速开发;

(3)能力有限,会限制小程序的表现形式。

因此,微信官方设计一套组件框架——Exparser。基于这个框架,内置了一套组件,以涵盖小程序的基础功能,便于开发者快速搭建出任何界面。同时也提供了自定义组件的能力,开发者可以自行扩展更多的组件,以实现代码复用。

2.6.2.1 Exparser框架

Exparser是微信小程序的组件组织框架,内置在小程序的基础库中,为小程序的各种组件提供基础的支持。小程序内的所有组件,包括内置组件和自定义组件,都由Exparser组织管理。

Exparser的组件模型与WebComponents标准中的ShadowDOM高度相似。Exparser会维护整个页面的节点树相关信息,包括节点的属性、事件绑定等,相当于一个简化版的ShadowDOM实现。Exparser的主要特点包括以下几点:

(1)基于ShadowDOM模型:模型上与WebComponents的ShadowDOM高度相似,但不依赖浏览器的原生支持,也没有其他依赖库;实现时,还针对性地增加了其他API以支持小程序组件编程。

(2)可在纯JS环境中运行:这意味着逻辑层也具有一定的组件树组织能力。

(3)高效轻量:性能表现好,在组件实例极多的环境下表现尤其优异,同时代码尺寸也较小。

小程序中,所有节点树相关的操作都依赖于Exparser,包括WXML到页面最终节点树的构建、createSelectorQuery调用和自定义组件特性等。

2.6.2.2 内置组件

基于Exparser框架,内置了一套组件,提供了视图容器类、表单类、导航类、媒体类、开放类等几十种组件。有了这么丰富的组件,再配合WXSS,可以搭建出任何效果的界面。在功能层面上,也满足了绝大部分的需求。

一般而言,会把一个组件内置到小程序框架里的一个重要原则是:这个组件是基础的。换句话说,没有这个组件的话,在小程序架构里无法实现或者实现不好某类功能。比如像一些开放类组件,有open-data组件提供展示群名称、用户信息等微信体系下的隐私信息,有button组件里open-type属性所提供分享、跳转App等敏感操作的能力。还有比如像视图容器类组件movable-view这种因双线程模型导致手势识别不好实现的组件,这是因为手势识别需要高频率捕捉手指的触摸事件,而在双线程模型中,触摸事件从渲染层发出,派发到逻辑层,这中间是有一定的延时而导致视图跟随手指运动这类交互变得有些卡顿。

2.6.2.3 自定义组件

自定义组件是开发者可以自行扩充的组件。开发者可以将常用的节点树结构提取成自定义组件,实现代码复用。

2.6.2.3.1 ShadowTree的概念

以下面的代码为例来阐述ShadowTree的概念。

<view>

  <input-with-label>

    <label>

      TEXT

    </label>

    <input />

  </input-with-label>

</view>

这里如果将input-with-label抽象成一个组件,那么可以将整个节点树拆分成两部分:

(1)组件节点树(Shadow Tree)

<label>

  <slot />

</label>

<input />

(2)调用组件的节点树

<view>

  <input-with-label>

    TEXT

  </input-with-label>

</view>

在Exparser的组件模型中,这两个节点树可以被拼接成上方的页面节点树。其中,组件的节点树称为“ShadowTree”,即组件内部的实现;最终拼接成的页面节点树被称为“ComposedTree”,即将页面所有组件节点树合成之后的树。在进行了这样的组件分离之后,整个页面节点树实质上被拆分成了若干个ShadowTree(页面的body实质上也是一个组件,因而也是一个ShadowTree)。

同时,各个组件也将具有各自独立的逻辑空间。每个组件都分别拥有自己的独立数据、setData调用,createSelectorQuery也将运行在ShadowTree的层面上。关于具体如何使用自定义组件特性,这里不再详细讨论,请参阅小程序开发文档。

2.6.2.3.2 运行原理

在使用自定义组件的小程序页面中,Exparser将接管所有的自定义组件注册与实例化。从外部接口上看,小程序基础库提供有Page和Component两个构造器。以Component为例,在小程序启动时,构造器会将开发者设置的properties、data、methods等定义段写入Exparser的组件注册表中。这个组件在被其它组件引用时,就可以根据这些注册信息来创建自定义组件的实例。Page构造器的大体运行流程与之相仿,只是参数形式不一样。这样每个页面就有一个与之对应的组件,称为“页面根组件”。

在初始化页面时,Exparser会创建出页面根组件的一个实例,用到的其他组件也会响应创建组件实例(这是一个递归的过程)。组件创建的过程大致有以下几个要点:

(1)根据组件注册信息,从组件原型上创建出组件节点的JS对象,即组件的this;

(2)将组件注册信息中的data复制一份,作为组件数据,即this.data;

(3)将这份数据结合组件WXML,据此创建出ShadowTree,由于ShadowTree中可能引用有其他组件,因而这会递归触发其他组件创建过程;

(4)将ShadowTree拼接到ComposedTree上,并生成一些缓存数据用于优化组件更新性能;

(5)触发组件的created生命周期函数;

(6)如果不是页面根组件,需要根据组件节点上的属性来设置组件的属性值;

(7)当组件实例被展示在页面上时,触发组件的attached生命周期函数,如果ShadwTree中有其他组件,也逐个触发它们的生命周期函数。

2.6.2.3.3 组件间通信

不同组件实例间的通信有WXML属性值传递、事件系统、selectComponent和relations等方式。其中,WXML属性值传递是从父组件向子组件的基本通信方式,而事件系统是从子组件向父组件的基本通信方式。

Exparser的事件系统完全模仿ShadowDOM的事件系统。在通常的理解中,事件可以分为冒泡事件和非冒泡事件,但在ShadowDOM体系中,冒泡事件还可以划分为在ShadowTree上冒泡的事件和在ComposedTree上冒泡的事件。如果在ShadowTree上冒泡,则冒泡只会经过这个组件ShadowTree上的节点,这样可以有效控制事件冒泡经过的范围。

input-with-label组件的WXML示例代码如下:

<label>

  <input />

  <slot />

</label>

页面的WXML示例代码如下:

<view>

  <input-with-label>

    <button />

  </input-with-label>

</view>

用上面的例子来说,当在button上触发一个事件时:

(1)如果事件是非冒泡的,那么只能在button上监听到事件;

(2)如果事件是在ShadowTree上冒泡的,那么button、input-with-label、view可以依次监听到事件;

(3)如果事件是在ComposedTree上冒泡的,那么button、slot、label、input-with-label、view可以依次监听到事件。

在自定义组件中使用triggerEvent触发事件时,可以指定事件的bubbles、composed和capturePhase属性,用于标注事件的冒泡性质。

triggerEvent事例代码如下:

Component({

  methods: {

    helloEvent: function () {

      this.triggerEvent('hello', {}, {

        bubbles: true,      // 这是一个冒泡事件

        composed: true,     // 这个事件在Composed Tree 上冒泡

        capturePhase: false // 这个事件没有捕获阶段

      })

    }

  }

})

小程序基础库自身也会通过这套事件系统提供一些用户事件,如tap、touchstart和form组件的submit等。其中,tap等用户触摸引发的事件是在ComposedTree上的冒泡事件,其他事件大多是非冒泡事件。

2.6.3 原生组件

在内置组件中,有一些组件较为特殊,它们并不完全在Exparser的渲染体系下,而是由客户端原生参与组件的渲染,这类组件称为“原生组件”,这也是小程序Hybrid技术的一个应用。

2.6.3.1 原生组件运行机制

要介绍原生组件的运行机制,需要从一行代码看起。

展示一个地图组件的WXML的示例代码如下:

<map latitude="39.92" longtitude="116.46"></map>

在原生组件内部,其节点树非常简单,基本上可以认为只有一个div元素。上面这行代码在渲染层开始运行时,会经历以下几个步聚:

(1)组件被创建,包括组件属性会依次赋值。

(2)组件被插入到DOM树里,浏览器内核会立即计算布局,此时可以读取出组件相对页面的位置(x,y坐标)、宽高。

(3)组件通知客户端,客户端在相同的位置上,根据宽高插入一块原生区域,之后客户端就在这块区域渲染界面。

(4)当位置或宽高发生变化时,组件会通知客户端做相应的调整。

可以看出,原生组件在WebView这一层的渲染任务是很简单,只需要渲染一个占位元素,之后客户端在这块占位元素之上叠了一层原生界面。因此,原生组件的层级会比所有在WebView层渲染的普通组件要高。

原生组件层级示意图如图2-xx所示。

图2-xx 原生组件层级示意图

引入原生组件主要有3个好处:

(1)扩展Web的能力。比如像输入框组件(input,textarea)有更好地控制键盘的能力。

(2)体验更好,同时也减轻WebView的渲染工作。比如像地图组件(map)这类较复杂的组件,其渲染工作不占用WebView线程,而交给更高效的客户端原生处理。

(3)绕过setData、数据通信和重渲染流程,使渲染性能更好。比如像画布组件(canvas)可以直接用一套丰富的绘图接口进行绘制。

常用的几个原生组件如表2-xx所示。

组件名

名称

是否有**context**

描述

video

视频

播放视频

map

地图

展示地图

canvas

画布

提供一个可以自由绘图的区域

picker

弹出式选择器

初始时没有界面,点击时弹出选择器

表2-xx 常用的几个原生组件

交互比较复杂的原生组件都会提供“context”,用于直接操作组件。以canvas为例,小程序提供了wx.createCanvasContext方法来创建canvas的context。这是一个可以用于操作canvas的对象,对象下提供了很多绘图的方法,如“setFillStyle”方法可以设置填充样式,“fillRect”方法用于绘制矩形(这些方法与HTML DOM Canvas兼容)。

使用canvas组件context对象的WXML的示例代码如下:

<canvas canvas-id="myCanvas"></canvas>

使用canvas组件context对象的JS的示例代码如下:

const ctx = wx.createCanvasContext('myCanvas')

ctx.setFillStyle('red')

ctx.fillRect(10, 10, 150, 75)

ctx.draw()

这段代码可以创建WXML中对应canvas节点的context,通过调用context中的方法在画布上绘制一个矩形。

类似于canvas,video、map等原生组件都可以创建context,context中提供的方法非常丰富,这里就不一一列举了。

2.6.3.2 原生组件渲染限制

原生组件脱离在WebView渲染流程外,这带来了一些限制。最主要的限制是一些CSS样式无法应用于原生组件,例如,不能在父级节点使用overflow:hidden来裁剪原生组件的显示区域;不能使用transformrotate让原生组件产生旋转等。

开发者最为常见的问题是,原生组件会浮于页面其他组件之上(相当于拥有正无穷大的z-index值),使其它组件不能覆盖在原生组件上展示。想要解决这个问题,可以考虑使用cover-view和cover-image组件。这两个组件也是原生组件,同样是脱离WebView的渲染流程外,而原生组件之间的层级就可以按照一定的规则控制。

2.6.4 小程序与客户端通信原理

2.6.4.1 视图层组件

内置组件中有部分组件是利用到客户端原生提供的能力,这类组件基本都是前一个章节描述的原生组件。既然需要客户端原生提供的能力,那么就会涉及到视图层与客户端的交互通信。这层通信机制在iOS和安卓系统的实现方式并不一样,iOS是利用了WKWebView的提供messageHandlers特性,而在安卓则是往WebView的window对象注入一个原生方法,最终会封装成WeiXinJSBridge这样一个兼容层,主要提供了调用(invoke)和监听(on)这两种方法。

实际上,在视图层与客户端的交互通信中,开发者只是间接调用的,真正调用是在组件的内部实现里。开发者插入一个原生组件,一般而言,组件运行的时候被插入到DOM树中,会调用客户端接口,通知客户端在哪个位置渲染一块原生界面。在后续开发者更新组件属性时,同样地,也会调用客户端提供的更新接口来更新原生界面的某些部分。

2.6.4.2 逻辑层接口

逻辑层与客户端原生通信机制与渲染层类似,不同在于,iOS平台可以往JavaScripCore框架注入一个全局的原生方法,而安卓方面则是跟渲染层一致的。

同样地,开发者也是间接地调用到与客户端原生通信的底层接口。一般会对逻辑层接口做层封装后才暴露给开发者,封装的细节可能是统一入参、做些参数校验、兼容各平台或版本问题等等。

在本章中介绍了小程序底层框架的设计和原理,提出了一个全新的双线程模型,这是小程序框架与业界大多数前端Web框架不同之处。基于这个模型,可以做到更好地管控以及提供更安全的环境。但同时带来了无处不在的异步问题,不过在框架层面去封装好异步带来的时序问题,让开发者只需要懂得上层更易为理解的接口。此外,也介绍了基于双线程模型的组件框架,以及原生组件的机制,让开发者进一步理解以写出更合理的代码。最后阐述了小程序是如何与客户端通信的,这是小程序运行起来的最基本的原理。

通过本章的学习,相信开发者对小程序底层框架有了一定的认识,在使用小程序提供的组件和接口时可以更好地猜测接口的原理机制,这会帮助开发者写出更稳定、性能更好的小程序。

2.7 性能优化

前面的章节已经介绍了小程序的编写方法和基础原理。本章将进一步介绍一些运行细节,以及针对性的优化策略。

2.7.1 启动

在小程序启动时,微信会为小程序展示一个固定的启动界面,界面内包含小程序的图标、名称和加载提示图标。此时,微信会在背后完成几项工作:下载小程序代码包、加载小程序代码包、初始化小程序首页。如图2-xx所示。

图2-xx 小程序启动流程图

2.7.1.1 代码包下载

在某个小程序第一次启动时,微信需要下载小程序的代码包。此后,如果小程序代码包未更新且还被保留在缓存中,则下载小程序代码包的步骤会被跳过。下载的小程序代码包不是小程序的源代码,而是编译、压缩、打包之后的代码包。代码包大小可以在开发者工具的“详情”栏中找到。

从开发者的角度来看,控制代码包大小有助于减少小程序的启动时间。对低于1MB的代码包,其下载时间可以控制在929ms(iOS)、1500ms(Android)内。

以下是一些常规的控制代码包大小的方法。

(1)精简代码,去掉不必要的WXML结构和未使用的WXSS定义。

(2)减少在代码包中直接嵌入的资源文件。

(3)压缩图片,使用适当的图片格式。

如果小程序比较复杂,优化后的代码总量可能仍然比较大,此时可以采用分包加载的方式进行优化。

2.7.1.2 分包加载流程

一般情况下,小程序的代码将打包在一起,在小程序启动时一次性下载完成。采用分包时,小程序的代码包可以被划分为几个:一个是“主包”,包含小程序启动时会马上打开的页面代码和相关资源;其余是“分包”,包含其余的代码和资源。这样,小程序启动时,只需要先将主包下载完成,就可以立刻启动小程序。这样就可以显著降低小程序代码包的下载时间。

分包目录结构示例如图2-xx所示。

图2-xx 分包目录结构示例

一个支持分包的小程序目录结构可以组织成上图的形式。

使用分包时app.json的示例配置如下:

{

  "pages":[

    "pages/index",

    "pages/logs"

  ],

  "subPackages": [

    {

      "root": "packageA",

      "pages": [

        "pages/cat",

        "pages/dog"

      ]

    }, {

      "root": "packageB",

      "pages": [

        "pages/apple",

        "pages/banana"

      ]

    }

  ]

}

代码根目录下有“packageA”和“packageB”两个子目录(它们的名字需要在app.json中声明),这两个子目录就构成了两个分包,每个分包下都可以有自己的页面代码和资源文件。而除掉这两个目录的部分就是小程序的主包。在小程序启动时,“packageA”和“packageB”两个子目录的内容不会马上被下载下来,只有主包的内容才会被下载。利用这个特性就可以显著降低初始启动时的下载时间。

使用分包时需要注意代码和资源文件目录的划分。启动时需要访问的页面及其依赖的资源文件应放在主包中。

2.7.1.3 代码包加载

微信会在小程序启动前为小程序准备好通用的运行环境。这个运行环境包括几个供小程序使用的线程,并在其中完成小程序基础库的初始化,预先执行通用逻辑,尽可能做好小程序的启动准备。这样可以显著减少小程序的启动时间。

小程序的代码包被下载(或从缓存中读取)完成后,小程序的代码会被加载到适当的线程中执行。此时,所有app.js、页面所在的JS文件和所有其他被require的JS文件会被自动执行一次,小程序基础库会完成所有页面的注册。在小程序代码调用Page构造器的时候,小程序基础库会记录页面的基础信息,如初始数据(data)、方法等。需要注意的是,如果一个页面被多次创建,并不会使得这个页面所在的JS文件被执行多次,而仅仅是根据初始数据多生成了一个页面实例(this),在页面JS文件中直接定义的变量,在所有这个页面的实例间是共享的。

例如,若从页面A使用wx.navigateTo跳转到页面B,再使用wx.navigateTo跳转到页面A,此时页面栈中有三个页面:A、B、A。这时两个A页面的实例将共享它的JS文件中Page构造器以外直接定义的变量。有经验的开发者可以利用这个特性,但一些开发者也会错误地共享出一些变量,因而使用时要小心。

小程序代码包加载期间执行的代码示例如下:

console.log('加载 page.js')

var count = 0

Page({

  onLoad: function () {

    count += 1

    console.log('第 ' + count + ' 次启动这个页面')

  }

})

如果在page.js中加入这段代码,则在小程序代码包加载阶段就会在控制台中输出一段提示语,并在每次页面被创建后输出这是这个页面第几次被创建。

在小程序代码包加载完毕后,小程序基础库会根据启动路径选择一个页面来启动。这时会根据页面路径和初始数据创建一个新页面。页面创建和运行期间会涉及许多数据通信和页面渲染,接下来会详细介绍。

2.7.2 页面层级准备

在视图层内,小程序的每一个页面都独立运行在一个页面层级上。小程序启动时仅有一个页面层级,每次调用wx.navigateTo后都会创建一个新的页面层级;相对地,wx.navigateBack会销毁一个页面层级。

对于每一个新的页面层级,视图层都需要进行一些额外的准备工作。在小程序启动前,微信会提前准备好一个页面层级用于展示小程序的首页。除此以外,每当一个页面层级被用于渲染页面,微信都会提前开始准备一个新的页面层级,使得每次调用wx.navigateTo都能够尽快展示一个新的页面。

页面层级的准备工作分为三个阶段。第一阶段是启动一个WebView,在iOS和Android系统上,操作系统启动WebView都需要一小段时间。第二阶段是在WebView中初始化基础库,此时还会进行一些基础库内部优化,以提升页面渲染性能。第三阶段是注入小程序WXML结构和WXSS样式,使得小程序能在接收到页面初始数据之后马上开始渲染页面(这一阶段无法在小程序启动前执行)。

页面层级准备过程图如图2-xx所示。

图2-xx 页面层级准备过程图

对于wx.redirectTo,这个调用不会打开一个新的页面层级,而是将当前页面层级重新初始化:重新传入页面的初始数据、路径等,视图层清空当前页面层级的渲染结果然后重新渲染页面。

2.7.3 数据通信

在每个小程序页面的生命周期中,存在着若干次页面数据通信。逻辑层向视图层发送页面数据(data和setData的内容),视图层向逻辑层反馈用户事件。

2.7.3.1 页面初始数据通信

在小程序启动或者一个新的页面被打开时,页面的初始数据(data)和路径等相关信息会从逻辑层发送给视图层,用于视图层的初始渲染。Native层会将这些数据直接传递给视图层,同时向用户展示一个新的页面层级,视图层在这个页面层级上进行界面绘制。视图层接收到相关数据后,根据页面路径来选择合适的WXML结构,WXML结构与初始数据相结合,得到页面的第一次渲染结果。

初始数据通信时序图如图2-xx所示。

图2-xx 初始数据通信时序图

分析这个流程不难得知:页面初始化的时间大致由页面初始数据通信时间和初始渲染时间两部分构成。其中,数据通信的时间指数据从逻辑层开始组织数据到视图层完全接收完毕的时间,数据量小于64KB时总时长可以控制在30ms内。传输时间与数据量大体上呈现正相关关系,传输过大的数据将使这一时间显著增加。因而减少传输数据量是降低数据传输时间的有效方式。

数据传输时间与数据量关系图如图2-xx所示。

图2-xx 数据传输时间与数据量关系图

2.7.3.2 更新数据通信

初始渲染完毕后,视图层可以在开发者调用setData后执行界面更新。在数据传输时,逻辑层会执行一次JSON.stringify来去除掉setData数据中不可传输的部分,之后将数据发送给视图层。同时,逻辑层还会将setData所设置的数据字段与data合并,使开发者可以用this.data读取到变更后的数据。

因此,为了提升数据更新的性能,开发者在执行setData调用时,最好遵循以下原则:

(1)不要过于频繁调用setData,应该考虑将多次setData合并成一次setData调用;

(2)数据通信的性能与数据量正相关,因而如果有一些数据字段不在界面中展示且数据结构比较复杂或者包含长字符串,则不应使用setData来设置这些数据;

(3)与界面渲染无关的数据最好不要设置在data中,可以考虑设置在page对象的其他字段下。

提升数据更新性能方式的代码示例如下:

Page({

  onShow: function () {

    // 不要频繁调用setData

    this.setData({ a: 1 })

    this.setData({ b: 2 })

    // 绝大多数时候可优化为

    this.setData({ a: 1, b: 2 })

    // 不要设置不在界面渲染时使用的数据,并将界面无关的数据放在data外

    this.setData({

      myData: {

        a: '这个字符串在WXML中用到了',

        b: '这个字符串未在WXML中用到,而且它很长…………………………'

      }

    })

    // 可以优化为

    this.setData({

      'myData.a': '这个字符串在WXML中用到了'

    })

    this._myData = {

      b: '这个字符串未在WXML中用到,而且它很长…………………………'

    }

  }

})

2.7.3.3 用户事件通信

视图层会接受用户事件,例如点击事件、触摸事件等。用户事件的通信比较简单:当一个用户事件被触发且有相关的事件监听器需要被触发时,视图层会将信息反馈给逻辑层。如果一个事件没有绑定事件回调函数,则这个事件不会被反馈给逻辑层。视图层中有一套高效的事件处理体系,可以快速完成事件生成、冒泡、捕获等过程。

视图层将事件反馈给逻辑层时,同样需要一个通信过程,通信的方向是从视图层到逻辑层。因为这个通信过程是异步的,会产生一定的延迟,延迟时间同样与传输的数据量正相关,数据量小于64KB时在30ms内。降低延迟时间的方法主要有两个。

(1)去掉不必要的事件绑定(WXML中的bind和catch),从而减少通信的数据量和次数;

(2)事件绑定时需要传输target和currentTarget的dataset,因而不要在节点的data前缀属性中放置过大的数据。

事件通信时间与数据量关系图如图2-xx所示。

图2-xx 事件通信时间与数据量关系图

2.7.4 视图层渲染

视图层在接收到初始数据(data)和更新数据(setData数据)时,需要进行视图层渲染。在一个页面的生命周期中,视图层会收到一份初始数据和多份更新数据。收到初始数据时需要执行初始渲染,每次收到更新数据时需要执行重渲染。

2.7.4.1 初始渲染

初始渲染发生在页面刚刚创建时。初始渲染时,将初始数据套用在对应的WXML片段上生成节点树。节点树也就是在开发者工具WXML面板中看到的页面树结构,它包含页面内所有组件节点的名称、属性值和事件回调函数等信息。最后根据节点树包含的各个节点,在界面上依次创建出各个组件。

视图层初始渲染流程图如图2-xx所示。

图2-xx 视图层初始渲染流程图

在整个流程中,时间开销大体上与节点树中节点的总量成正比例关系。因而减少WXML中节点的数量可以有效降低初始渲染和重渲染的时间开销,提升渲染性能。

简化WXML代码的例子如下:

<view data-my-data="{{myData}}">

  <!-- 这个 view 和下一行的 view 可以合并 -->

  <view class="my-class" data-my-data="{{myData}}" bindtap="onTap">

    <text>

      <!-- 这个 text 通常是没必要的 -->

      {{myText}}

    </text>

  </view>

</view>

<!-- 可以简化为 -->

<view class="my-class" data-my-data="{{myData}}" bindtap="onTap">

  {{myText}}

</view>

2.7.4.2 重渲染

初始渲染完毕后,视图层可以多次应用setData的数据。每次应用setData数据时,都会执行重渲染来更新界面。

初始渲染中得到的data和当前节点树会保留下来用于重渲染。每次重渲染时,将data和setData数据套用在WXML片段上,得到一个新节点树。然后将新节点树与当前节点树进行比较,这样可以得到哪些节点的哪些属性需要更新、哪些节点需要添加或移除。最后,将setData数据合并到data中,并用新节点树替换旧节点树,用于下一次重渲染。

视图层重渲染流程图如图2-xx所示。

图2-xx 视图层重渲染流程图

在进行当前节点树与新节点树的比较时,会着重比较setData数据影响到的节点属性。因而,去掉不必要设置的数据、减少setData的数据量也有助于提升这一个步骤的性能。

2.7.5 原生组件通信

一些原生组件支持使用context来更新组件。不同于setData,使用context来更新组件并不会涉及到重渲染过程,数据通信过程也不同。在setData的数据通信流程中,数据从逻辑层经过native层转发,传入视图层的WebView,再经过一系列渲染步骤之后传入组件。而使用context时,数据从逻辑层传到native层后,直接传入组件中,这样可以显著降低传输延迟。

setData与context对比时序图(上setData,下context)如图2-xx所示。

图2-xx setData与context对比时序图(上setData,下context)

在具体通信过程上,因为context的方法繁多,通信方式相对于setData更复杂。不过基础库会对context方法调用时的通信进行封装优化,通常开发者不需要关心这个问题。

本章主要介绍了小程序的运行流程和一些重要细节,还介绍了进行优化的基本方法。主要的优化策略可以归纳为三点:精简代码,降低WXML结构和JS代码的复杂性;合理使用setData调用,减少setData次数和数据量;必要时使用分包优化。

2.8 小程序基础库的更新迭代

小程序平台正式发布后,收到许多开发者的反馈,其中有Bug类的反馈,也有提出某些具体需求的。程序总有出错的时候,小程序的底层也有出现重大Bug的现象发生,一旦这种Bug出现,会影响到很多小程序的正常工作,因此需要迅速进行修复。同时也以小步快跑的方式发布很多新的能力供开发者使用,所以小程序的底层基础库迭代速度是非常快。在这一章会介绍小程序基础库是如何进行更新迭代的。

2.8.1 小程序基础库

前面章节提到过小程序的运行环境是分成渲染层和逻辑层的,在渲染层可以用各类组件组建界面的元素,在逻辑层可以用各类API来处理各种逻辑,这里提到的组件、API其实都是小程序基础库进行包装提供的,基础库的职责还要处理数据绑定、组件系统、事件系统、通信系统等一系列框架逻辑,才能让整个小程序有序的运作起来。小程序的基础库是JavaScript编写的,它可以被注入到渲染层和逻辑层运行。为了叙述方便,下文把小程序基础库简称为基础库。

2.8.1.1 基础库载入时机

在开发网页时,经常会引用很多开源的JS库,在使用到这些库所提供的API之前,需要先在业务代码前边引入这些库。在使用jQuery库的$函数前,需要在业务代码前用script标签先引入jQuery.js。示例代码如下:

<script src="jQuery.js"></script>

<script src="Vue.js"></script>

    <script>

    $(function(){

        //console.log("ready")

      })

</script>

同样道理,为了让小程序业务代码能够调用wx.navigateTo等API以及组件,就需要在启动小程序后先载入基础库,接着再载入业务代码。由于小程序的渲染层和逻辑层是两个线程管理,渲染层WebView层注入的称为WebView基础库,逻辑层注入的称为AppService基础库,如果没有特殊声明,后文提到基础库的概念指的是WebView基础库和AppService基础库两个的组合。

显然,所有小程序在微信客户端打开的时候,都需要注入相同的基础库,所以,小程序的基础库不会被打包在某个小程序的代码包里边,它会被提前内置在微信客户端。这样做的好处有两点:

(1)降低业务小程序的代码包大小。

(2)可以单独修复基础库中的Bug,无需修改到业务小程序的代码包。

2.8.1.2 基础库的版本号

小程序基础库版本号使用semver规范,格式为Major.Minor.Patch,其中Major、Minor、Patch均为整数,1.9.901、2.44.322、10.32.44都是符合semver风格的版本号。通常月度发布版本会把Minor提升一位,例如从1.9.x升级到1.10.x,如果是修正版本,会把Patch提升一位,例如1.10.0升级到1.10.1。Major位则是重大特性发布时才会被提升一位。

在小程序中,可以通过wx.getSystemInfo()或者wx.getSystemInfoSync()方法获取小程序版本号,示例代码如下:

var info = wx.getSystemInfoSync()

console.log("小程序基础库版本号为:" + info.SDKVersion)

还要再强调一点,不少开发者会使用错误的版本号比较方法,例如直接用字符串比较,parseInt比较等,往后当基础库版本号提升上去后会引发一些逻辑错误,示例代码如下:

var info = wx.getSystemInfoSync() // info.SDKVersion == "1.11.0"

if (info.SDKVersion > "1.9.0") { // 此时条件为false,无法进入if分支

  // 处理高版本小程序的逻辑

}

如下示例代码给出了正确的比较版本号的方法,后续小程序基础库会内置版本比较的API,建议查阅小程序官方文档使用该方法。

function compareVersion(v1, v2) {

  v1 = v1.split('.')

  v2 = v2.split('.')

  var len = Math.max(v1.length, v2.length)

  while (v1.length < len) {

    v1.push('0')

  }

  while (v2.length < len) {

    v2.push('0')

  }

  for (var i = 0; i < len; i++) {

    var num1 = parseInt(v1[i])

    var num2 = parseInt(v2[i])

    if (num1 > num2) {

      return 1

    } else if (num1 < num2) {

      return -1

    }

  }

  return 0

}

compareVersion('1.11.0', '1.9.9') // => 1 // 1表示 1.11.0比1.9.9要新

compareVersion('1.11.0', '1.11.0') // => 0 // 0表示1.11.0和1.11.0是同一个版本

compareVersion('1.11.0', '1.99.0') // => -1 // -1表示1.11.0比 1.99.0要老

2.8.2 异常

程序不可避免会出现运行时错误,JavaScript语言是一种非常灵活的脚本语言,由于没有静态编译的过程,在运行时就更容易出现异常现象,例如错误的把某个字符串类型变量当做整数类型去做处理等。

2.8.2.1 JS运行异常

在网页开发中,如果在Chrome运行如下代码:

var imgs = document.getElementsByTagName("img")

for (var i = 0, len = imgs.length; i < l; ++i) {

  //imgs[i].getAttribute("src")   balblabla

}

在Chrome浏览器的开发者工具里的Console面板中会出现脚本运行异常的错误信息,示例中for循环的条件表达式i<l中的l变量没定义,运行后出现异常,如图2-xx所示。

图2-xx 网页JS运行错误

一般语法错误以及运行时错误,浏览器都会在Console里显示对应的错误信息,以及出错的文件、行号、堆栈信息。

2.8.2.2 捕捉JS异常的方法

在WebView层有两种方法可以捕捉JS异常:

(1)try-catch方案。可以针对某个代码块使用try-catch包装,这个代码块运行出错时能在catch块里边捕捉到。

(2)window.onerror方案。也可以通过window.addEventListener("error",function(evt){}),这个方法能捕捉到语法错误跟运行时错误,同时还能知道出错的信息,以及出错的文件,行号,列号。

利用try-catch捕捉JS运行异常如图2-xx所示。

图2-xx 利用try-catch捕捉JS运行异常

通过window.onerror捕捉JS运行异常如图2-xx所示。

图2-xx 通过window.onerror捕捉JS运行异常

这两个方案都无法捕捉代码的语法错误,但是一般在开发阶段,工具就已经能够显示出脚本的语法错误,因此这类异常完全是可以在开发阶段消除,运行阶段并不会有此类异常发生。对比window.onerror的方案,try-catch的方案有个缺点:没法捕捉到全局的错误事件,也即是只有try-catch块里边的代码运行出错才会被捕捉到。逻辑层不存在window对象,因此逻辑层AppService侧无法通过window.onerror来捕捉异常。

所以小程序基础库在WebView侧使用window.onerror方案进行捕捉异常,在逻辑层AppService侧通过把App实例和Page实例的各个生命周期等方法包裹在try-catch里进行捕捉异常。同时在App构造器里提供了onError的回调,当业务代码运行产生异常时,这个回调被触发,同时能够拿到异常的具体信息,开发者自己根据业务情况处理对应的容错逻辑。

在基础库里捕捉到的运行时异常会上报到微信的服务器,然后产生类似图2-xx所示的监控曲线,通过这个监控图来观察基础库的运行情况。

基础库异常监控曲线如图2-xx所示。

图2-xx 基础库异常监控曲线

2.8.3 基础库的更新

前边章节已经提到了什么是基础库,以及如何检测基础库运行时异常的方法,现在结合这两点来说说如何进行基础库的更新。

2.8.3.1 基础库版本变动

小程序的很多能力需要微信客户端来支撑,例如蓝牙、直播能力、微信运动等,可以说,小程序基础库的迭代离不开微信客户端的发布。

为了更方便叙述,虚拟这样一个需求迭代的场景:当前微信版本为6.5.10,小程序基础库版本为1.6.0,需要往微信即将发布的6.5.11加入小程序的直播组件能力。由于有新的能力依赖微信客户端的月度发布版本提供,下一个要发布的基础库版本定为1.7.0。

为了避免新版本的基础库给线上小程序带来未知的影响,微信客户端都是携带上一个稳定版的基础库发布的。也就是微信客户端正式发布6.5.11的版本依旧内置1.6.0版本的基础库。等到微信客户端正式发布后,会开始灰度推送1.7.0版本的基础库到6.5.11的微信客户端里,在这个过程需要仔细监控各类异常现象以及开发者和用户的反馈,一般灰度时长为12小时,灰度结束后,用户设备上就会有新版本的基础库。如果存在重大Bug,那么此次推送会被回退,也就是基础库版本回到1.6.0版本。

当1.7.0版本推送稳定后,会更新小程序文档,开发者就可以在小程序中使用1.7.0版本所提供的新能力——直播组件,在这之后如果小程序基础库出现任何Bug,推送Patch版本进行修复,此时基础库版本号的Patch位会被提升一位,也就是1.7.1版本。

2.8.3.2 推送基础库过程

小程序基础库的迭代速度非常快,但是基础库的变更会影响到所有小程序的运行,如果基础库出现重大Bug没有及时发现时,会引起很多小程序无法正常使用,所以在更新基础库时会非常谨慎。

首先,在正式灰度推送新版本基础库前,内部有严格的自动化测试流程,保证已有的测试用例全部能通过,同时还会在带有最新版本库的测试机上运行访问量较高的一些小程序并检测他们是否存在一些白屏等异常现象。

接着进行灰度推送,一般灰度时长是12个小时,遇到一些重大代码改动时可能灰度时间会更久。推送过程中,会观察监控曲线是否有异动,也会仔细分析对应的上报日志是否存在致命的错误。

灰度推送完成后,会发布新能力的文档,此时也会关注微信开发者社区的Bug反馈,根据情况再决定是否要推送Patch版本进行一些Bug的修复。

在本章中介绍了如何进行小程序日常能力的迭代发布,在发布新版本基础库的过程需要有检测异常现象的能力,避免故障的发生。正是这样严格的迭代流程使得小程序框架能够稳定运行在各个版本的微信客户端中,同时这种小步快跑的迭代速度让开发者可以使用新能力去完善他们的小程序交互。在下一章会一起来了解微信开发者工具是如何在PC端模拟小程序调试的。

2.9 微信开发者工具

本章主要介绍微信开发者工具如何编译小程序代码,如何实现小程序模拟器以及如何调试小程序。

2.9.1 简介

虽然在开发语言层面小程序与传统的网页差别不大:使用JavaScript脚本语言编写逻辑代码、使用类似于HTML的WXML来描述页面的结构、使用类似于CSS的WXSS来描述节点的样式,但是由于小程序渲染和逻辑分离的运行机制与传统的网页存在差异,所以无法使用传统的网页的开发调试工具,因此微信官方推出了小程序开发生态一站式IDE——微信开发者工具。开发者可以借助微信开发者工具完成小程序的代码开发、编译运行、界面和逻辑调试、真机预览和提交发布版本等功能。

微信开发者工具如图2-xx所示。

图2-xx 微信开发者工具

微信开发者工具是一个基于nw.js,使用node.js、chromium以及系统API来实现底层模块,使用React、Redux等前端技术框架来搭建用户交互层,实现同一套代码跨Mac和Windows平台使用。

微信开发者工具底层框架如图2-xx所示。

图2-xx 微信开发者工具底层框架

2.9.2 代码编译

微信开发者工具和微信客户端都无法直接运行小程序的源码,因此需要对小程序的源码进行编译。代码编译过程包括本地预处理、本地编译和服务器编译。为了快速预览,微信开发者工具模拟器运行的代码只经过本地预处理、本地编译,没有服务器编译过程,而微信客户端运行的代码是额外经过服务器编译的。

2.9.2.1 编译WXML

WXML(WeiXin Markup Language)是小程序框架设计的一套标签语言,用于构建出页面的结构。小程序的渲染层的运行环境是一个WebView,而WebView无法直接理解WXML标签,所以需要经过编译。

微信开发者工具内置了一个二进制的WXML编译器,这个编译器接受WXML代码文件列表,处理完成之后输出JavaScript代码,这段代码是各个页面的结构生成函数。

WXML的编译过程如图2-xx所示。

图2-xx WXML的编译过程

编译过程将所有的WXML代码最终变成一个JavaScript函数,预先注入在WebView中。在运行时确定了页面路径之后,将路径作为参数传递给这个函数得到该页面的结构生成函数,页面结构生成函数接受页面数据,输出一段描述页面结构的JSON,最终通过小程序组件系统生成对应的HTML。

使用页面结构生成函数的示例代码如下:

//$gwx是WXML编译后得到的函数

//根据页面路径获取页面结构生成函数

var generateFun = $gwx('name.wxml')

//页面结构生成函数接受页面数据,得到描述页面结构的JSON

var virtualTree = generateFun({

  name: 'miniprogram'

})

/** virtualTree == {

   tag: 'view',

   children: [{

       tag: 'view',

       children: ['miniprogram']

     }]

 }**/

//小程序组件系统在虚拟树对比后将结果渲染到页面上

virtualDom.render(virtualTree)

上传代码时,微信开发者工具直接将本地的WXML代码文件提交到后台,由后台进行WXML编译,后台的WXML编译器和开发者工具本地内置的WXML编译器是同一套代码生成的。

2.9.2.2 编译WXSS

WXSS(WeiXin Style Sheets)是一套样式语言,用来决定WXML的组件应该怎么显示。为了适应广大的前端开发者,WXSS具有CSS大部分特性。同时为了更适合开发微信小程序,WXSS对CSS进行了扩充以及修改。与CSS相比,WXSS扩展的一些特性,包括rpx尺寸单位和样式导入语法,这些特性都是WebView无法直接理解的。

微信开发者工具内置了一个二进制的WXSS编译器,这个编译器接受WXSS文件列表,分析文件之间的引用关系,同时预处理rpx,输出一个样式信息数组,如图2-xx所示,每个WXSS文件对应于这个数组中的一项。

图2-xx WXSS的编译过程

在运行时,根据当前的屏幕宽度,计算出1rpx对应多少像素单位,然后将样式信息数组转换成最终的样式添加到页面中。

由于样式在微信客户端存在兼容性问题,为了方便开发者,微信开发者工具提供了上传代码时样式自动补全的功能,利用PostCSS对WXSS文件进行预处理,自动添加样式前缀。

2.9.2.3 编译JavaScript

微信客户端在运行小程序的逻辑层的时候只需要加载一个JS文件(称为app-service.js),而小程序框架允许开发者将JavaScript代码写在不同的文件中,所以在代码上传之前,微信开发者工具会对开发者的JS文件做一些预处理,包括ES6转ES5和代码压缩(开发者可以选择关闭预处理操作),在服务器编译过程将每个JS文件的内容分别包裹在define域中,再按一定的顺序合并成app-service.js。其中对于页面JS和app.js需要主动require。

IMG_256
IMG_256
IMG_256
IMG_256

图2-2 JavaScript的编译过程

2.9.3 模拟器

小程序模拟器模拟小程序在微信客户端的逻辑和界面表现,方便开发者实时查看代码效果。由于系统差异以及微信客户端特有的一些交互流程,少部分的API无法在模拟器上进行模拟,但对于绝大部分的API均能够在模拟器上呈现出正确的状态。同时微信开发者工具提供多种机型尺寸以及自定义机型尺寸功能,方便开发者进行界面样式的机型适配。

小程序模拟器如图2-xx所示。

图2-xx 小程序模拟器

2.9.3.1 逻辑层模拟

在iOS微信客户端上,小程序的JavaScript代码是运行在JavaScriptCore中,在Android微信客户端上,小程序的JavaScript代码是通过X5 JSCore来解析的。而在微信开发者工具上采用了一个隐藏着的Webivew来模拟小程序的逻辑运行环境。

微信客户端小程序运行环境模型简图如图2-xx所示。

图2-xx 微信客户端小程序运行环境模型简图

微信开发者工具小程序运行环境模型简图如图2-xx所示。

图2-xx 微信开发者工具小程序运行环境模型简图

在微信开发者工具上WebView是一个chrome的<webview/>标签。与<iframe/>标签不同的是,<webview/>标签是采用独立的线程运行的。

用于模拟小程序逻辑层的<webview/>加载的链接是:

http://127.0.0.1:9973/appservice/appservice

在开发者工具底层搭建了一个本地HTTP服务器来处理小程序模拟器的网络请求。其中:

(1)./__asdebug/asdebug.js:是开发者工具注入的脚本。

(2)./__dev__/WAService.js:是小程序逻辑层基础库。

(3)./util.js、./app.js、./index.js:开发者JS代码。

WebView在请求开发者JS代码时,开发者工具读取JS代码进行必要的预处理后,将处理结果返回,然后由WebView解析执行。虽然开发者工具上是没有对JS代码进行合并的,但是还是按照相同的加载顺序进行解析执行。

appservice内容如图2-xx所示。

图2-xx appservice内容

WebView是一个浏览器环境,而JsCore是一个单纯的脚本解析器,浏览器中的BOM对象无法在JSCore中使用,开发者工具做了一个很巧妙的工作,将开发者的代码包裹在define域的时候,将浏览器的BOM对象局部变量化,从而使得在开发阶段就能发现问题。

BOM对象局部变量化如图2-xx所示。

图2-xx BOM对象局部变量化

2.9.3.2 渲染层模拟

微信开发者工具使用chrome的<webview/>标签来加载渲染层页面,每个渲染层WebView加载:

http://127.0.0.1:9973/pageframe/pageframe.html

开发者工具底层搭建的HTTP本地服务器在收到这个请求的时候,就会编译WXML文件和WXSS文件,然后将编译结果作为HTTP请求的返回包。当确定加载页面的路径之后,例如index页面,开发工具会动态注入如下一段脚本:

// 改变当前webview 的路径,确保之后的图片网络请求能得到正确的相对路径

history.pushState('', '', 'pageframe/index')

// 创建自定义事件,将页面结构生成函数派发出去,由小程序渲染层基础库处理

document.dispatchEvent(new CustomEvent("generateFuncReady", {

  detail: {

    generateFunc: $gwx('./index.wxml')

  }

}))

// 注入对应页面的样式,这段函数由WXSS编译器生成

setCssToHead()

2.9.3.3 客户端模拟

微信客户端为丰富小程序的功能提供了大量的API。在微信开发者工具上,通过借助BOM(浏览器对象模型)以及node.js访问系统资源的能力,同时模拟客户端的UI和交互流程,使得大部分的API能够正常执行。

借助BOM,例如wx.request使用XMLHttpRequest模拟、wx.connectSocket使用WebSocket、wx.startRecord使用MediaRecorder、wx.playBackgroundAudio使用<audio/>标签;

借助node.js,例如使用fs实现wx.saveFile、wx.setStorage、wx.chooseImage等API功能。

借助模拟UI和交互流程,实现wx.navigateTo、wx.showToast、wx.openSetting、wx.addCard等。

2.9.3.4 通讯模拟

上文已经叙述了小程序的逻辑层、渲染层以及客户端在微信开发者工具上的模拟实现,除此之外,需要一个有效的通讯方案使得小程序的逻辑层、渲染层和客户端之间进行数据交流,才能将这三个部分串联成为一个有机的整体。

微信开发者工具的有一个消息中心底层模块维持着一个WebSocket服务器,小程序的逻辑层的WebView和渲染层页面的WebView通过WebSocket与开发者工具底层建立长连,使用WebSocket的protocol字段来区分Socket的来源。

逻辑层中的消息模块的示例代码如下:

// <webview/>的userAgent是可定制的

// 通过userAgent中获取开发者工具WebSocket服务器监听的端口

var port = window.navigator.userAgent.match(/port\/(\d*)/)[1]

// 通过指定 protocol == 'APPSERVICE' 告知开发者工具这个链接是来自逻辑层

var ws = new WebSocket(`ws://127.0.0.1:${port}`, 'APPSERVICE')

ws.onmessage = (evt) => {

  let msg = JSON.parse(evt.data)

  // …处理来自开发者工具的信息

}

// 调用API接口 wx.navigateBack

ws.send(JSON.stringify({

  command: 'APPSERVICE_INVOKE',

  data: {

    api: 'navigateBack',

    args: {}

  }

}))

2.9.4 调试器

代码调试是开发者工具的最主要的功能之一,包括界面调试和逻辑调试。nw.js对<webview/>提供打开Chrome Devtools调试界面的接口,使得开发者工具具备对小程序的逻辑层和渲染层进行调试的能力。同时为了方便调试小程序,开发者工具在Chrome Devtools的基础上进行扩展和定制。

如何调试webview的示例代码如下:

// 用于渲染的webview

var webview = document.createElement('webview')

//用于显示调试器的WebView

var devtoolsWebView = document.createElement('webview')

// 显示调试器

webview.showDevTools(ture, devtoolsWebView)

开发者工具上显示的调试器是调试逻辑层WebView,主要使用Chrome Devtools的Sources面板调试逻辑层JS代码。Chrome Devtools自带的Element面板并不能调试当前渲染层页面的节点(实际上是调试逻辑层WebView的节点),所以通过在devtoolsWebView中注入脚本的方式将Chrome Devtools的Element面板隐藏,同时开发了Chrome Devtools插件WXML面板,用于展示渲染层界面调试的用户交互界面。

开发者工具会在每个渲染层的WebView中注入界面调试的脚本代码,负责获取WebView中的DOM树、获取节点样式、监听节点变化、高亮选中节点、处理界面调试命令。并将界面调试信息通过WebSocket经由开发者工具转发给WXML面板进行处理。

图2-xx和图2-xx所示。

图2-xx WXML源码

图2-xx 真实的DOM树

开发者写的WXML源码与真实的DOM树之间存在较大的差异,需要经过一个最小树算法,如图2-xx所示。

图2-xx 最小树算法示例

将非小程序组件节点剔除后才能呈现出与WXML源码一致的效果。

WXML面板的最终效果如图2-xx所示。

图2-xx WXML面板的最终效果

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 2.5 小程序的协同工作和发布
    • 2.5.1 协同工作
    • 2.5.2 用户体验审视
    • 2.5.3 发布
    • 2.5.4 运营
  • 2.6 底层框架
    • 2.6.1 双线程模型
    • 2.6.2 组件系统
    • 2.6.3 原生组件
    • 2.6.4 小程序与客户端通信原理
  • 2.7 性能优化
    • 2.7.1 启动
    • 2.7.2 页面层级准备
    • 2.7.3 数据通信
    • 2.7.4 视图层渲染
    • 2.7.5 原生组件通信
  • 2.8 小程序基础库的更新迭代
    • 2.8.1 小程序基础库
    • 2.8.2 异常
    • 2.8.3 基础库的更新
  • 2.9 微信开发者工具
    • 2.9.1 简介
    • 2.9.2 代码编译
    • 2.9.3 模拟器
    • 2.9.4 调试器
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档