技术博客——微信小程序的架构与原理

  • 在两个月的微信小程序开发过程中,我曾走了不少弯路,也曾被很多现在看来十分可笑的问题所困扰。这些弯路与困扰,基本上都是由于当时对小程序的架构理解不够充分,对小程序的原理学习不够深入。我在解决这些问题的过程中,不仅学到了很多有意义的、对开发有直接帮助的知识点,更在微信小程序的架构与原理上补了不少课,对于我在微信小程序的设计上大有裨益。在这篇博客中,我将平常学习到的关于微信小程序的架构与原理的知识记录下来,同时记录我在一些功能上的代码实现,这些功能的实现曾经困扰过我、让我走了一些弯路。

    微信小程序的推出,使得开发者的技术门槛降低、减少了推广与开发成本,也节约了用户的手机存储空间、节省了使用时间成本。它作为一种全新的连接用户与服务的方式,与传统意义上的应用程序有很多区别,同时在开发过程中也有一些共性。

    小程序的前身是微信的JS API,最初这类API并不是对外暴露的,仅仅是提供给腾讯内部的一些业务使用。后来JS API进一步地引入了更多类别的接口,功能愈加丰富,包括拍摄、录音、语音识别以及二维码等重要功能,这些API整合成为一套完善的、公开的网页开发工具包,称为JS-SDK。JS-SDK在Web技术之上,做了很多方面的优化,使得Web开发者具有更多的能力。例如JS-SDK中的“微信Web资源离线存储”功能,该功能通过使用微信离线存储,使得Web开发者可以借助微信提供的存储能力,直接从微信本地加载Web资源,减少网页加载时间。

  • 双线程机制

    • 小程序的运行环境分成渲染层和逻辑层,其中 WXML 模板和 WXSS 样式工作在渲染层,JS 脚本工作在逻辑层。网页开发中的渲染线程和脚本线程是互斥的,这也是为什么长时间的脚本运行可能会导致页面失去响应。而在小程序中,二者是分开的,分别运行在不同的线程中。其中渲染层(对应渲染线程)在webview中渲染,一个页面对应一个webview,所以渲染层会存在多个webview线程;逻辑层(对应脚本线程)运行在JSCore线程中。渲染层和逻辑层之间通过微信客户端(Native)做中转,而Native又与第三方服务器之间进行网络通信。
  • 程序与页面

    • 整个小程序只有一个App实例,可以通过getApp()函数获取。App实例全局共享,其中app.js文件中定义了全局变量,绑定了全局生命周期函数、错误监听函数等回调函数;app.json文件中定义了所有页面路径(pages字段)、顶部导航栏样式、底部tabBar设置等全局信息;app.wxss中定义了全局组件样式,所有页面的wxml可以直接使用app.wxss所定义的样式。
    • 以开发者的视角看页面生命周期,对于页面生命周期的理解主要体现在回调函数中。当一个页面被初次加载,会调用onLoad()函数。一个页面加载完毕之后,从其他页面跳再次转回到该页面,或是在tabBar中切换回到该页面,onLoad()不再被执行。所以onLoad()函数适合做一些初始化的工作,例如获取用户信息、静态配置数据等。onLoad()函数执行完毕后,紧接着会调用onShow()函数。onShow()函数的调用时机是页面显示之时,包括页面初次启动、页面从隐藏到显示、从后台切换到前台等。onShow()函数适合做一些与实时更新的数据相关联,但又对实时性要求不太高的工作,比如推荐热搜关键词,尽管关键词搜索频度可能每时每刻都在变化,但是用户在点开热搜页面后并不希望看到关键词疯狂变化。如果页面被关闭,或是小程序切换到后台,此时会调用onHide()函数。onHide()函数适合做一些与数据保存相关的工作,比如要将用户在页面上做某种操作的次数保存到数据库,这种保存操作适合在用户离开页面后执行,如果实时地与数据库进行交互则会造成较大的开销。当一个页面被销毁,比如彻底退出小程序,此时会调用onUnload()函数。开发者可能并不经常用到onUnload()函数,但是如果一个功能与用户使用小程序期间的所有数据相关联,包括小程序挂载到后台时产生的数据,那么可能会在onUnload()执行某些操作。
    • 页面中的其他类型的回调函数,比如onPullDownRefresh()、onReachBottom()也经常被开发者使用。前者是监听用户的下拉动作,后者是监听用户的上拉触底动作。若要利用onPullDownRefresh()实现下拉刷新,首先要在页面的json文件里设置window属性:
"window":{
"enablePullDownRefresh":true
}

之后在onPullDownRefresh()中添加监听到下拉动作所要执行的操作,比如在标题栏中显示加载3秒钟:

onPullDownRefresh(){
console.log('--------下拉刷新-------')
wx.showNavigationBarLoading() //在标题栏中显示加载
setTimeout(function () {
wx.hideNavigationBarLoading() //完成停止加载
wx.stopPullDownRefresh() //停止下拉刷新
}, 3000)
}

若要利用onReachBottom()函数实现上拉加载,可以在页面的json文件里设置:

"onReachBottomDistance":10,

表示距离底部还有多少px的距离时监听到上拉动作,当然也可以不设置,采用默认值0。之后在onReachBottom()函数中实现动作,以下是我实现的上拉加载更多表情的函数:

onReachBottom:function() {
var judge = 1
if (this.data.isLoading == 1) return;
//上拉加载方式:获取数据,拼接数组
var loadTime = this.data.globalShowIndex
console.log("loadTime:",loadTime)
//每次加载一行表情(3个)
var init = 9 + loadTime*3
var globalList = this.data.showListCache
console.log("globalLenth:",globalList.length)
if (init >= globalList.length) {
wx.showToast({
title: '抱歉,没有更多了',
duration:2000
})
}
else {
var temp3 = []
for (var i = init;i < init+3;i++) {
var path = globalList[i]
temp3.push({'file_id':path})
}
this.data.showPicList.push(temp3)
this.setData({
isLoading:0,
showPicList:this.data.showPicList
})
this.data.globalShowIndex++
}
}

此外,还可以wxml文件中做一些与上拉下拉动作相配合的设置,优化视觉体验,比如上拉加载时页面底部的提示:

<view wx:if='{{!isRefreshing}}' class="weui-loadmore">
<view wx:if='{{isLoading}}'>
<view class="weui-loading"></view>
<view class="weui-loadmore-tips">正在加载</view>
</view>
<view wx:elif='{{more}}'>
<!--bindtap为onReachBottom()回调函数,点击此处同样执行上拉加载动作-->
<view class="weui-loadmore-tips" bindtap='onReachBottom'>加载更多 </view>
</view>
<view wx:else>
<view class="weui-loadmore-tips">抱歉,没有更多了</view>
</view>
</view>

其中isRefreshing等指示变量需要在onReachBottom()函数中根据执行过程更改。

  • 小程序的云开发

    • 微信小程序开发与Web开发的一个重要的不同之处,在于微信小程序开发者可以使用云开发功能,无需搭建服务器,即可使用云端能力。云开发为开发者提供完整的原生云端支持和微信服务支持,弱化后端和运维概念,无需搭建服务器,使用平台提供的 API 进行核心业务开发,即可实现快速上线和迭代。云开发提供了以下几大基础能力支持:1.云函数,开发者无需自建服务器,在云端运行代码。2.数据库,开发者无需自建数据库,既可以在开发工具中直接操作,又能在云函数、js逻辑函数中读写。3.存储,开发者无需自建存储和CDN,既可以在开发工具中直接上传、下载文件,又能在程序中通过微信提供的API进行上传下载。4.云调用, 使用小程序开放接口,包括服务端调用、获取开放数据等。
    • 使用云开发的难点主要在于数据库的使用,因为对于数据库的查询只能依靠小程序提供的统一接口,并没有sql语句那么强的灵活性。例如嵌套查询操作,如果只用统一接口的话操作就比较复杂。另外,微信云存储的增删查改接口种类较为繁多,若要灵活掌握可能需要较长时间的学习周期。所以在实际开发中有时会采用多级操作的方式,暂存中间操作结果,进行下一级操作。这样可能会造成内存上的额外开销,但是对于初学者来说可以方便地实现一些数据库的复杂操作。
for (var i = 0;i < batchTimes;i++) {
var res = await db.collection("expression_visit_times").where({
id:fileid
}).skip(i*100).get()
if ((res.data[0] == null) && (i == batchTimes-1)) {
await db.collection('expression_visit_times').add({
data: {
id:fileid,
tag:filetag,
times:1
}
}).then(res=>{
console.log("第一次访问表情")
})
}
else if(res.data[0] != null){
visits = res.data[0].times
visits++
_id = res.data[0]._id
try{
await db.collection('expression_visit_times').doc(_id).
update({
data:{
times:visits
}
}).then(res=>{
console.log("更新成功")
})
}
catch(e){
console.log(e)
}
}
}

以上是我实现的对于expression_visit_times集合的一个比较复杂的操作:如果集合中存在file_id对应的表情的记录,那么就把它的访问次数加1;如果不存在,则新增一条该表情的访问记录。我将查询的结果保存下来进行判断,之后做进一步的操作。在我们的开发过程中,存在大量复杂的数据库操作,如果对于繁多的增删查改接口了解不够深入,可能会出现许多意想不到的问题。比如where().update()操作,在页面的逻辑函数中会报错,但是在云函数中可以执行。如果开发者不能在期限内非常熟练地掌握数据库操作,那么可以出于稳妥起见,可以将一些基础的、不容易出错的数据库操作进行嵌套、组合,保证正确性。

  • 同步与异步

    • 微信小程序开发,甚至Web开发中困扰初学者的普遍问题就是程序运行过程中的同步与异步问题。我们知道,JavaScript是单进程执行的,同步操作会对程序的执行进行阻塞处理。比如在浏览器页面程序中,如果一段同步的代码需要执行很长时间(比如一个很大的循环操作),则页面会产生卡死的现象。所以,在JavaScript中,提供了一些异步特性,为程序提供了性能和体验上的益处,比如利用setTimeout()进行回调处理,或是将时间开销较大的数据请求做异步处理,使之不阻塞当前页面的主进程。
    • 异步处理尽管会优化程序的使用体验,但也对开发者提出了新的问题:如果一个数据请求与之后的函数存在数据关联,后者需要前者请求到的数据,按照JavaScript的异步执行,后面的函数不被数据请求阻塞,会被优先执行。这样的问题在开发中普遍存在,如果处理不好二者之间的关系,将会对程序的正确性造成很大的威胁。
    • 上述问题可以用回调函数的方式解决,只需把后面的函数放到success:function()之中,当前面的操作完成,回调success函数,便可以执行后面的操作。但是这又引入了新的问题:如果现在有一系列的同步操作,记作operation1-operation5,后面的操作均需要等待前面的操作,那么我们的代码将会变成下面的样子:
operation1({
success:function(res){
operation2({
success:function(res){
operation3({
success:function(res){
operaion4({
success:function(res){
operation5({})
}
})
}
})
}
})
}
})

看起来非常不舒服,维护起来也比较困难。就算我们能够忍受这种代码风格,那么还有一个致命的问题无从解决:for循环中的异步问题。如果一个异步操作被放在了for循环中,那它就会变成薛定谔的猫:你不知道它会在什么时候被执行,可能它的几次执行并不是严格按照for循环的遍历次序。

  • 在云开发中,我们经常在云函数中使用Async-await方法,将异步请求变为同步请求。也就是说把让一个操作阻塞后面的操作,直到该操作完成。在云函数中使用Async-await比较简单,只需要以promise风格声明函数,之后加上async关键字,就可以在函数中使用await,将异步操作变成同步操作了:
exports.main = async (event, context) => {
const db = cloud.database()
const request = event.request
if (request == 1) {
//后续操作等待统计'expression_visit_times'集合记录数目
const countResult = await db.collection('expression_visit_times').count()
const total = countResult.total
// 计算需分几次取
var batchTimes = Math.ceil(total / 100)
if (batchTimes==0) {
batchTimes = 1
}
var resultArray = []
for (var i = 0;i < batchTimes;i++) {
var temp = await db.collection("expression_visit_times").skip(i*100).get()
resultArray = resultArray.concat(temp.data)
}
console.log("resultArray:",resultArray)
return {
data:resultArray
}
}
  • 在小程序端,目前是不支持Async-await方法的。但是可以通过手动添加API的方式,进行小程序端的配置。参考链接如下:https://www.jb51.net/article/158648.htm.

技术博客——微信小程序的架构与原理的更多相关文章

  1. 技术博客——微信小程序UI的设计与美化

    技术博客--微信小程序UI的设计与美化 在alpha阶段的开发过后,我们的小程序也上线了.看到自己努力之后的成果大家都很开心,但对比已有的表情包小程序,我们的界面还有很大的提升空间,许多的界面都是各个 ...

  2. 技术博客--微信小程序canvas实现图片编辑

    技术博客--微信小程序canvas实现图片编辑 我们的这个小程序不仅仅是想给用户提供一个保存和查找的平台,还希望能给用户一个展示自己创意的舞台,因此我们实现了图片的编辑部分.我们对对图片的编辑集成了很 ...

  3. [技术博客] 微信小程序的formid获取

    微信小程序的formid获取 formId的触发 微信小程序可以通过收集用户的formid,获取formid给用户主动推送微信消息.获取formid有两个途径,一个是触发一次表单提交,或者触发一次支付 ...

  4. [技术博客]微信小程序审核的注意事项及企业版小程序的申请流程

    关于小程序审核及企业版小程序申请的一些问题 微信小程序是一个非常方便的平台.由于微信小程序可以通过微信直接进入,不需要下载,且可使用微信账号直接登录,因此具有巨大的流量优势.但是,也正是因为微信流量巨 ...

  5. [技术博客]微信小程序开发中遇到的两个问题的解决

    IDE介绍 微信web开发者工具 前端语言 微信小程序使用的语言为wxml和wss,使用JSON以及js逻辑进行页面之间的交互.与网页的html和css略有不同,微信小程序在此基础上添加了自己的改进, ...

  6. 腾讯技术分享:微信小程序音视频与WebRTC互通的技术思路和实践

    1.概述 本文来自腾讯视频云终端技术总监rexchang(常青)技术分享,内容分别介绍了微信小程序视音视频和WebRTC的技术特征.差异等,并针对两者的技术差异分享和总结了微信小程序视音视频和WebR ...

  7. 腾讯技术分享:微信小程序音视频技术背后的故事

    1.引言 微信小程序自2017年1月9日正式对外公布以来,越来越受到关注和重视,小程序上的各种技术体验也越来越丰富.而音视频作为高速移动网络时代下增长最快的应用形式之一,在微信小程序中也当然不能错过. ...

  8. 迅雷首席架构师刘智聪:微信小程序的架构与系统设计的几点观感

    笔者注:本文来自于迅雷首席工程师刘智聪的个人分享,他毕业于南昌大学化学系,加入迅雷后设计开发了多款迅雷核心产品,凭借“大规模网络流媒体服务关键支撑技术”项目获得2015年国家科学技术进步奖二等奖,同时 ...

  9. 微信小程序基础架构

    一个微信小程序界面由一个页面描述文件,一个页面逻辑文件,一个样式表文件来进行描述 在主目录中的三个以app开头的文件就是微信小程序的主描述文件 app.js :主逻辑文件,用来注册小程序 app.js ...

随机推荐

  1. 20210717 noip18

    考前 从小饭桌出来正好遇到雨下到最大,有伞但还是湿透了 路上看到一个猛男搏击暴风雨 到了机房收拾了半天才开始考试 ys 他们小饭桌十分明智地在小饭桌看题,雨下小了才来 考场 状态很差. 开题,一点想法 ...

  2. Python - 通过PyYaml库操作YAML文件

    PyYaml简单介绍 Python的PyYAML模块是Python的YAML解析器和生成器 它有个版本分水岭,就是5.1 读取YAML5.1之前的读取方法 def read_yaml(self, pa ...

  3. Mysql常用sql语句(6)- limit 限制查询结果的条数

    测试必备的Mysql常用sql语句系列 https://www.cnblogs.com/poloyy/category/1683347.html 前言 实际工作中,我们的数据表数据肯定都是万级别的,如 ...

  4. ElasticAlert基于聚合告警

    背景 最近公司网站经常被漏洞扫描,虽然并没有什么漏洞给对方利用,但是每次扫描我们也必须要察觉到,如果扫描的量太大,可以考虑从公有云的安全组上禁用掉这个IP,所以需要统计指定时间内每个IP的访问次数,这 ...

  5. (xxl_job | quartz):XXL_JOB 对比 Quartz 一决高下!

    概述: XXL-JOB是一个轻量级分布式任务调度平台,其核心设计目标是开发迅速.学习简单.轻量级.易扩展. 现已开放源代码并接入多家公司线上产品线,开箱即用. 官方地址中文版:http://www.x ...

  6. hyperf从零开始构建微服务(二)——构建服务消费者

    阅读目录 构建服务消费者 安装json rpc依赖 安装JSON RPC客户端 server配置 编写业务代码 编写服务消费者类 consumer配置 配置 UserServiceInterface ...

  7. shell中的$0 $n $# $* $@ $? $$

    $0当前脚本的文件名 $n传递给脚本或函数的参数.n 是一个数字,表示第几个参数.例如,第一个参数是$1,第二个参数是$2. $#传递给脚本或函数的参数个数. $*传递给脚本或函数的所有参数. $@传 ...

  8. scrum项目冲刺_day11 第一阶段总结

    "智能垃圾分类APP"第一阶段总结 总任务: 一.appUI页面(已完成) 二.首页功能: 1.图像识别功能(已完成) 2.语音识别功能(已完成) 3.垃圾搜索功能(基本完成) 4 ...

  9. Django学习day15BBS项目开发3.0

    每日测验 """ 今日考题 1.django admin作用及用法 2.media配置如何实现,基于该配置能够做到什么以及需要注意什么 3.阐述博客园为何支持用户自定义个 ...

  10. jenkins自动构建前端项目(window,vue)

    我们把一个多人协作的vue前端项目发布服务器,一般要经过以下步骤: git更新最新的代码 构建项目 把构建后的代码上传到服务器 如果用jenkins来构建的话,只需要点击一次构建按钮,就可以自动完成以 ...