小程序简介
小程序是一个不需要下载安装就可使用的应用,它实现了应用触手可及的梦想,用户扫一扫或搜一下即可打开应用。目前小程序已经应用于电商、视频、教育、音乐、新闻资讯等众多领域中,而它与App和公众号有什么区别呢?下面将做一个简单的比较。
小程序与App的区别
- 小程序无需安装,用户通过微信扫描二维码、搜索、分享等途径即可打开小程序,而
APP
则需要提前安装,相对来说小程序的推广成本较低; App
的开发经常需要适配各种主流机型,并且需要开发安卓和IOS两套,开发成本较高,而小程序的适配较简单,开发周期相对较短;- 小程序可以实现的功能受到微信平台开放能力的限制,而App对手机资源的利用更加淋漓尽致,可以满足更多的功能场景和更流畅的交互体验,但小程序的交互体验也可以接近原生
App
。
小程序与公众号的区别
- 小程序依赖微信提供的运行环境,而公众号基于
H5
,尽管小程序不是原生App
,但可以借助微信封装的一系列接口能力去实现更丰富的功能; - 公众号服务于营销与信息传递,而小程序面向产品与服务,因此小程序在对营销功能(如消息推送)进行了严格控制;
- 在交互体验上公众号操作延时相对较大,而小程序体验接近原生
App
,比公众号更流畅。
整体对比
运行环境 | 功能性 | 便捷性 | 交互体验 | 开发成本 | 推广难度 | 消息推送 | |
---|---|---|---|---|---|---|---|
公众号 | H5 | 简单 | 无需安装 | 一般 | 低 | 低 | 支持 |
小程序 | 微信 | 轻应用 | 无需安装 | 接近原生App | 中 | 低 | 受限 |
App | 原生系统 | 丰富 | 需要安装 | 最流畅 | 高 | 高 | 支持 |
小程序的架构设计
小程序的技术选型
小程序在进行技术选型时考虑到以下3种页面渲染的方式:
- 纯客户端原生技术来渲染,意味着小程序要与微信代码一起编包和发版本,开发节奏无法像Web那样快;
- 纯 Web 技术来渲染,因为UI渲染和JS脚本执行都在同一线程,容易导致业务逻辑和UI渲染抢占资源,从而在一些复杂的页面上面临性能问题;
- 介于客户端原生技术与 Web 技术之间,互相结合各自特点(又可称为
Hybrid
),可像Web一样支持在线快速更新,在另一方面也比Web更接近原生体验。
因此,小程序最终选择了 Hybrid
的渲染方式,界面主要由成熟的 Web 技术渲染,辅之以大量的接口提供丰富的客户端原生能力,同时通过双线程模型分离界面渲染和逻辑处理,提高渲染性能和管控安全。
双线程模型
小程序的架构主要可分为3层:
- 逻辑层(AppService)
- 视图层(View)
- 系统层(Native)
逻辑层
- 在
JsCore
线程中运行,主要进行数据请求和业务逻辑处理; - 通过JS引擎(如
JavaScriptCore
、X5 JSCore
)提供一个沙箱环境来执行JS,与浏览器相比开发者无法直接操作DOM
和BOM
,从而阻止开发者使用一些浏览器暴露的接口(如跳转页面、动态执行脚本),提高管控和安全性;
视图层
- 在
WebView
线程中执行界面渲染相关的任务,并且小程序中存在多个界面,故渲染层也存在多个WebView
线程; - 通过
Virtual DOM
减少渲染开销,提高局部更新数据和重渲染的效率,让页面更流畅; - 视图层中描述语言使用的
WXML
、WXSS
与Web开发中html
、css
类似,降低开发者的学习成本。
系统层
- 通过
JSBridge
构建了js
和native
之间的通信,以便上层可以间接调用客户端原生底层接口; - 提供网络请求、数据缓存、本地文件、媒体等基础能力;
- 为逻辑层与视图层的通信做中转,逻辑层发送网络请求也经由
Native
转发。
在双线程模型下把界面渲染和逻辑处理分离和并行处理,可以加速渲染速度,避免单线程模型下,js 运算时间过长而导致UI 出现卡顿, 并且采用数据驱动的方式,开发者无法直接操作 DOM
,加强管控和安全。但在另一方面双线程模型也意味着逻辑层与渲染层之间的通信、各层与客户端原生交互都有一定的延时。
双线程下的渲染过程
由微信官方的图可以看出,在渲染层把WXML
转化成对应的JS对象模拟虚拟DOM
树,在逻辑层更新数据的时候,通过小程序基础库提供的setData
方法把需要更新的数据经过Native
层转发传递到视图渲染层
,再对比两棵虚拟DOM
树的前后差异,因为虚拟DOM
和实际的WXML
节点一一对应的关系,最后把差异应用到原来的DOM
树,只会渲染被更新的部分。
小程序的启动加载和生命周期
小程序启动有两种情况:
- 冷启动:用户首次打开或小程序被微信主动销毁后再次打开的情况,此时小程序需要重新加载启动;
- 热启动:用户在打开过小程序后的一定时间内再次打开,此时小程序在后台还未被销毁,只需将后台态的小程序切换到前台。
在冷启动的时候如果小程序发现有新版本,则会异步下载新版本的代码包,并同时用客户端本地的包进行启动,这意味着当前打开的依然不是最新版本,需要等下一次冷启动才会应用上新版本。 但考虑到可能存在需要用户马上应用新版本的情况,开发者可以通过wx.getUpdateManager
API 来实现。
- 小程序启动后首次加载页面,会触发页面的
onLoad
,每个页面只有在首次加载才会触发一次; - 页面显示的时候触发
onShow
,如果是页面首次渲染完成则会接着触发onReady
- 小程序被切换到后台,页面被隐藏,触发
onHide
,下次从后台切换到前台,会再触发onShow
- 页面被回收销毁时触发
onUnload
小程序开发Demo
下面通过一个简单的demo为大家展示下小程序开发界面和代码目录结构。
1. 官方开发者工具界面
官方提供的微信开发者工具让我们可以很方便地完成小程序的 API 和页面的开发调试、代码查看和编辑、预览和发布等功能。
2. 目录结构
小程序的目录主要包含一个描述整体程序的app
和多个描述各自页面的page
:
- 小程序主体
app.js
:小程序逻辑,例如小程序的启动监听处理app.json
:公共配置,例如页面文件的路径、窗口表现、设置网络超时等app.wxss
:公共样式表
- 小程序页面
{page}.wxml
:页面结构,如index.wxml
{page}.json
:页面配置,独立定义每个页面的一些属性,为可选文件{page}.wxss
:页面样式表{page}.js
:页面逻辑,例如网络请求、页面事件处理等
- 公共库和组件
utils目录
:存放自己写的库lib目录
:存放外部引入的库components
:自定义的组件
cloudfunctions目录
:如果有使用小程序云进行开发,则可在此存放自己的云函数
3. 页面描述例子
-
wxml 例子
<view class="container"> <view class="userinfo"> <button open-type="getUserInfo" bindgetuserinfo="onGetUserInfo" class="userinfo-avatar" style="background-image: url({{avatarUrl}})"></button> <text open-type="getUserInfo" bindgetuserinfo="onGetUserInfo" class="userinfo-nickname">您好,{{userInfo.nickName}}</text> </view> <view class="uploader"> <view class="uploader-text" bindtap="gotoStatistics"> <text>统计打卡记录</text> </view> </view> ... </view>
-
wxss 例子
.userinfo, .uploader, .tunnel { margin-top: 40rpx; height: 140rpx; width: 100%; background: #fff; border: 1px solid rgba(0, 0, 0, 0.1); border-left: none; border-right: none; display: flex; flex-direction: row; align-items: center; transition: all 300ms ease; } ...
可以看出在构建页面时候类似写前端页面,可以让初学者很快地学习和上手。开发者通过wxml
来完成列表&条件渲染、模版、事件绑定等操作,小程序框架也为开发者提供一系列基础组件以便快速组合开发:
绑定事件的时候可以在wxml
文件中通过bind + 事件类型
或catch+事件类型
来定义,后面跟上在{page}.js
同名的处理函数(例如bindtap="gotoStatistics"
),其中bind
的方式绑定事件不会阻止事件冒泡,catch
的方式绑定会阻止事件冒泡。
4. 代码入口
-
每新建一个页面都需要在
app.json
中配置页面的路径,如{ "pages": [ "pages/index/index", "pages/countRes/countRes", ... ], ... }
-
小程序的注册,在
app.js
中进行小程序注册、生命周期回调的处理App({ onLaunch: function(options) { //监听小程序初始化 }, onShow: function(options) { //监听小程序显示 }, onHide: function() { //监听小程序隐藏 }, onError: function(msg) { //监听错误 }, //全局数据对象 globalData: {...} })
-
页面的注册
Page({ //与页面相关的数据 data: { avatarUrl: './user-unlogin.png', userInfo: {}, requestResult: '', needInit: true, ismask: 'none' }, onLoad: function(options) { //页面加载 ... //发请求 wx.request({ url: 'abc.php', data: {...}, header: {'content-type': 'application/json'}, success(res) { //更新数据 this.setData({ avatarUrl: res.userInfo.avatarUrl, userInfo: res.userInfo, needInit: false, ismask: 'none' }) } }) ... }, onReady: function() { //页面初次渲染完成 }, onShow: function() { //页面显示 }, onHide: function() { //页面隐藏 }, onUnload: function() { //页面卸载 }, //自定义事件处理函数 gotoStatistics: function() { if (this.checkIsNeedInit()) { return } wx.navigateTo({ url: '../statistics/statistics' }) }, ... })
在
{page}.js
中进行页面的生命周期回调、数据更新和事件处理等操作,例如监听页面加载完成时请求用户信息,通过setData
更新于页面绑定的数据,监听按钮点击等,其中涉及到网络请求、媒体、文件、缓存、界面等相关操作都有小程序提供API来支持:例如Demo中存在选择图片并上传的场景:
//上传图片打卡 doUploadPic: function() { // 选择图片 wx.chooseImage({ count: 1, sizeType: ['compressed'], sourceType: ['album', 'camera'], success: function(res) { const filePath = res.tempFilePaths[0] // 上传图片 const cloudPath = filePathPre + filePath.match(/\.[^.]+?$/)[0] wx.cloud.uploadFile({ cloudPath, filePath, success: res => { console.log('[上传文件] 成功:', res) ... }, fail: e => { console.error('[上传文件] 失败:', e) }, complete: () => {} }) }, fail: e => { console.error(e) } }) }