快速开始
注册账号
前往微信公众平台:https://mp.weixin.qq.com/
下载微信开发者工具
https://developers.weixin.qq.com/miniprogram/dev/devtools/stable.html
创建第一个项目
使用js基础模板
项目结构概述

JSON文件总结
app.json
是当前小程序的全局配置,包括了小程序的所有页面路径、窗口外观、界面表现、底部 tab等。
1 | { |
页面文件创建:只需要在 app.json-> pages中新增页面的存放路径并保存,小程序开发者工具即可帮我们自动创建对应的页面文件
修改首页:只需要调整app.json-> pages字符串数组中页面路径的前后顺序,即可修改项目的首页。小程序会把排在第一位的页面,当作项目首页进行渲染。
window
1 | //app.json |
小程序窗口的组成部分包括:导航栏区域,背景区域,页面主体区域
navigationBar(导航栏)
navigationBarTitleText:修改导航栏的标题navigationBarBackgroundColor:修改导航栏的背景色,只支持16进制颜色,不能设置为red,blue等navigationBarTextStyle:设置标题颜色,可选值只能是black和white(为什么这里又能直接写颜色了?)
background系列(下拉页面才会显示的部分的背景配置,前提是页面开启了下拉刷新)
backgroundColor:指定16进制的颜色值比如#efefef。backgroundTextStyle:只支持dark和light。用于修改下拉刷新的加载样式(颜色)
onReachBottomDistance
触底加载的距离,默认为50px,当滚动条距离底部50px的时候触发触底刷新
在页面中配置onReachBottom就能指定触底后的回调函数
enablePullDownRefresh
设置为true,表示允许下拉刷新,然后在页面中配置onPullDownRefresh,就能指定下拉刷新的回调函数。调用wx.stopPullDownRefresh就能关闭下拉刷新的加载样式。
要注意的是onPullDownRefresh和onReachBottom是 Page 的生命周期函数,不属于 Component。
tabBar
1 | //app.json |
- selectedlconPath:选中时显示的图标路径
- backgroundColor:tabbar的背景颜色
- iconPath:未选中时显示的图标路径
- color:tab上文字的默认(未选中)颜色
- selectedColor: tab 上的文字选中时的颜色
- pagePath:tab对应的页面路径,点击tab会跳转到指定页面。
注意:
tabBar中只能配置最少2个、最多5个tab页签(list数组里的元素数目等于tab数目)
当渲染顶部 tabBar时,不显示 icon,只显示文本
自定义tabbar
在app.json的tabBar字段中新增custom:true属性

然后默认的tabbar就会消失,此时需要开发者提供一个自定义组件来渲染 tabBar,所有 tabBar 的样式都由该自定义组件渲染。与 tabBar 样式相关的接口,如wx.setTabBarItem 等将失效。
在项目根目录下新建一个custom-tab-bar组件,包含以下文件
1 | custom-tab-bar/index.js |
然后在所有 tab 页的 json 里需声明 usingComponents 项,但是不需要实际注册这个组件
1 | { |
这个组件不需要手动在每个tabbar页面中引入和注册,会自动使用。
每个 tab 页下的自定义 tabBar 组件实例是不同的,在每个tabbar页面中调用this.getTabBar(),就能获取到这个页面下的自定义tabbar组件实例
1 | this.getTabBar().setData({ |
页面的json文件
1 | { |
小程序中的每一个页面,可以使用json文件,来对本页面的窗口外观进行配置,页面中的配置项会覆盖app.json的 window属性中相同的配置项。
project.config.json
通常大家在使用一个工具的时候,都会针对各自喜好做一些个性化配置,例如界面颜色、编译配置等等,当你换了另外一台电脑重新安装工具的时候,你还要重新配置。
考虑到这点,小程序开发者工具在每个项目的根目录都会生成一个 project.config.json,你在工具上做的任何配置都会写入到这个文件,当你重新安装工具或者换电脑工作时,你只要载入同一个项目的代码包,开发者工具就自动会帮你恢复到当时你开发项目时的个性化配置,其中会包括编辑器的颜色、代码上传时自动压缩等等一系列选项。
简单的来说这个文件就是用来保存,恢复个性化配置的。
sitemap.json
站点地图,微信现已开放小程序内搜索,效果类似于PC网页的SEO(搜索引擎优化)。该文件用来配置小程序页面是否允许微信索引。
JSON 语法
这里说一下小程序里JSON配置的一些注意事项。
JSON文件都是被包裹在一个大括号中 {},通过key-value的方式来表达数据。JSON的Key必须包裹在一个双引号中,在实践中,编写 JSON 的时候,忘了给 Key 值加双引号或者是把双引号写成单引号是常见错误。
JSON的值只能是特定的格式。
还需要注意的是 JSON 文件中无法使用注释,试图添加注释将会引发报错
js文件总结
app.js
是整个小程序项目的入口文件,通过调用App()函数来启动整个小程序
1 | createApp().app.mount("#app"); |
页面的js文件
是页面的入口文件,页面的脚本文件,存放页面的数据、事件处理函数等
通过调用Page()函数来创建并运行页面
1 | Page({ |
这么看来微信小程序的代码风格不也是选项式风格。
框架接口
小程序App
App
注册小程序。接受一个 Object 参数,其指定小程序的生命周期回调等。
App() 必须在 app.js 中调用,必须调用且只能调用一次
1 | // app.js |
getApp
整个小程序只有一个 App 实例,是全部页面共享的。开发者可以通过 getApp 方法获取到全局唯一的 App 实例,获取App上的数据或调用开发者注册在 App 上的函数。
1 | // xxx.js |
页面
Page
注册小程序中的一个页面。接受一个 Object 类型参数,其指定页面的初始数据(data)、生命周期回调、事件处理函数等。
data 是页面第一次渲染使用的初始数据。页面加载时,data 将会以JSON字符串的形式由逻辑层传至渲染层,因此data中的数据必须是可以转成JSON的类型:字符串,数字,布尔值,对象,数组
getCurrentPages
用来获取当前页面栈, 返回一个页面实例数组,按栈顺序排列(栈底 → 栈顶)
pages[0]:栈底页面(通常是首页)pages[pages.length - 1]:当前页面(栈顶)
1 | const currentPage = getCurrentPages().pop(); // 或 [length - 1] |
自定义组件
Behavior
注册一个 behavior,接受一个 Object 类型的参数
1 | module.exports = Behavior({ |
Component
创建自定义组件,接受一个 Object 类型的参数
模块化
在微信小程序中使用的是CommonJS模块化语法
require
引入模块。返回模块通过 module.exports 或 exports 暴露的接口。需要引入其他分包的模块的时候,可以通过配置 callback 回调函数来异步获取指定模块。异步获取失败的时候,将会触发 error 回调函数。
| 名称 | 类型 | 必填 | 说明 |
|---|---|---|---|
| path | string | 是 | 需要引入模块文件相对于当前文件的相对路径,或npm模块名,或npm模块路径。默认不支持绝对路径,可通过配置 resolveAlias 自定义路径映射。 |
| callback | function | 否 | 异步加载成功回调函数,该回调函数参数为成功加载的模块。 |
| error | function | 否 | 异步加载失败回调函数,该回调函数参数为错误信息和模块名 |
同一包内调用
1 | // common.js |
跨分包异步调用
1 | // subpackage/common.js 分包 common 文件 |
module:当前模块对象
exports:module.exports 的引用
事件
什么是事件
事件是视图层到逻辑层的通讯方式。事件可以绑定在组件上,当达到触发事件,就会执行逻辑层中对应的事件处理函数。
事件对象可以携带额外信息,如 id, dataset, touches
事件分类
事件分为冒泡事件和非冒泡事件:
冒泡事件:当一个组件上的事件被触发后,该事件会向父节点传递。
非冒泡事件:当一个组件上的事件被触发后,该事件不会向父节点传递。
在微信小程序中,事件流被简化了,只有目标阶段和冒泡阶段,但是后续触摸类事件支持捕获阶段
事件绑定
使用bind或者catch关键字来绑定事件,比如bindtap或者bind:tap,catch:tap。与 bind 不同, catch 会阻止事件向上冒泡
例如在下边这个例子中,点击 inner view 会先后调用handleTap3和handleTap2(因为tap事件会冒泡到 middle view,而 middle view 阻止了 tap 事件冒泡,不再向父节点传递),点击 middle view 会触发handleTap2,点击 outer view 会触发handleTap1
1 | <view id="outer" bindtap="handleTap1"> |
要注意的是,在微信小程序中绑定事件回调的时候,不能传参,比如下面的写法就是错误的:
1 | <view id="outer" bindtap="handleTap(1)"> |
想要传递参数,可以通过在标签上添加data-xxx属性值,这些属性值在事件对象的e.target.dataset中可以访问到。
1 | <view bind:tap="tip" data-a="{{1}}" data-b='2'> i love you</view> |
其中e.target.dataset.a的值是数字,e.target.dataset.b的值是字符串。
常见的事件
tap
手指触摸后马上离开,类似于HTML中的click 事件
绑定方式:bindtap 或 bind:tap,
1 | <view class="container"> |
1 | Page({ |
input
文本框输入事件
绑定方式:bindinput或 bind:input
通过e.detail.value获得最新输入的值,不同于js的e.target.value
1 | <view class="container"> |
1 | Page({ |
实现数据的双向绑定,参考vue中v-model的实现即可。
change
状态改变时触发,绑定方式同上
1 | <view class="container"> |
1 | Page({ |
事件对象
当组件触发事件时,逻辑层绑定该事件的处理函数,会收到一个事件对象
基础事件对象属性列表
| 属性 | 类型 | 说明 |
|---|---|---|
| type | String | 代表事件的类型,比如点击事件的事件类型就是tap |
| timeStamp | Integer | 页面打开到触发事件所经过的毫秒数 |
| target | Object | 触发事件的源组件的一些信息。target.id表示源组件的id,target.dataset表示事件源组件上由data-开头的自定义属性组成的集合 |
| currentTarget | Object | 当前组件的一些属性值集合 |
| mark | Object | 事件标记数据 |
在组件节点中可以附加一些自定义数据。这样,在事件中可以获取这些自定义的节点数据,用于事件的逻辑处理。
在 WXML 中,这些自定义数据以 data- 开头,多个单词由连字符 - 连接。这种写法中,连字符写法会转换成驼峰写法,而大写字符会自动转成小写字符。如:
data-element-type,最终会呈现为event.currentTarget.dataset.elementType;data-elementType,最终会呈现为event.currentTarget.dataset.elementtype。
detail
一般的事件对象都有的属性,补充这次事件的更多信息
WXS响应事件
有频繁用户交互的效果在小程序上表现是比较卡顿的,例如页面有 2 个元素 A 和 B,用户在 A 上做 touchmove 手势,要求 B 也跟随移动。一次 touchmove 事件的响应过程为:
a、touchmove 事件从视图层(Webview)抛到逻辑层(App Service)
b、逻辑层(App Service)处理 touchmove 事件,再通过 setData 来改变 B 的位置
一次 touchmove 的响应需要经过 2 次的逻辑层和渲染层的通信以及一次渲染,通信的耗时比较大。
本方案基本的思路是减少通信的次数,让事件在视图层(Webview)响应,也就是将事件处理写在WXS中,目前只能响应内置组件的事件,不支持自定义组件事件。WXS 函数的除了纯逻辑的运算,还可以通过封装好的ComponentDescriptor 实例来访问以及设置组件的 class 和样式,对于交互动画,设置 style 和 class 足够了。WXS 函数的例子如下:
1 | var wxsFunction = function(event, ownerInstance) { |
其中入参 event ,在小程序基础事件对象上多了 event.instance ,来表示触发事件的组件的 ComponentDescriptor 实例。ownerInstance 表示的是触发事件的组件,所在的组件的 ComponentDescriptor 实例,如果触发事件的组件是在页面内的,ownerInstance 表示的是页面实例。
1 | <wxs module="test" src="./test.wxs"></wxs> |
模板语法
数据绑定
页面数据写在js文件中,具体在配置对象的data属性中
1 | Page({ |
wxml文件中使用:
1 | <image src="{{imgSrc}}"></image> |
绑定数据和属性需要使用{{}},但是进行事件回调绑定时不需,如 bindtap, bindscroll直接写函数名,不加 {{}}。
微信数据绑定{{}}中只支持简单数据绑定和三元运算符,不支持函数调用、方法执行、复杂表达式,比如<view>{{ formatDate(date) }}</view> 这种语法是错误的。但是模板中能调用wxs中的函数。所以wxs中的函数通常用作过滤器。
1 | <view>{{m.sum(1,2)}}</view> |
但是绑定事件的时候,又不能使用wxs中的函数,比如下面这种写法是错误的:
1 | <button bindtao="m2.toLower"></button> |
条件渲染
wx:if/wx:elif/wx:else
1 | <view wx:if="{{view == 'WEBVIEW'}}"> WEBVIEW </view> |
1 | // page.js |
block组件
类似vue中的template,不会被渲染出来,可以避免渲染一些不必要的结点,可以用来包裹部分结构,从而批量控制显示隐藏。
hidden
类似vue中的v-show,不一样的是这里值为true的话是隐藏,v-show是显示
列表渲染
wx:for:传入一个数组
wx:for-item:自定义当前项的变量名,这个变量名默认是item,在实际开发中用的不多
wx:for-index:自定义当前循环项的索引的变量名,这个变量名默认是index,在实际开发中用的不多
wx:key:类似vue中的key,如果数组item中有
id属性,直接令wx:key="id"即可,不需要写成{{item.id}},否则会报错,后者是模仿vue的语法
示例
1 | <view wx:for="{{arr}}"> |
1 | <view wx:for="{{arr1}}" wx:for-index="idx" wx:for-item="itemName" wx:key="idx"> |
数据同步setData
通过调用this.setData方法,可以修改data中的数据,并通知视图更新。需要注意的是this.setData进行的是更新操作,不是替换操作。
修改某个对象的某个属性:
1 | Component({ |
wxml与html的区别
标签名称不同
1 | HTML ( div, span, img, a) |
往往写 HTML 的时候,经常会用到的标签是 div, p, span,开发者在写一个页面的时候,可以根据这些基础的标签组合出不一样的组件,例如日历、弹窗等等。换个思路,既然大家都需要这些组件,为什么我们不能把这些常用的组件包装起来,大大提高我们的开发效率。从上边的例子可以看到,小程序的 WXML 用的标签是 view, button, text 等等,这些标签其实就是一个个封装好的组件,我们还提供了地图、视频、音频等等组件能力。
多了一些 wx:if 这样的属性以及 双大括号 这样的表达式
在网页的一般开发流程中,我们通常会通过 JS 操作 DOM ,以引起界面的一些变化响应用户的行为。例如,用户点击某个按钮的时候,JS 会记录一些状态到 JS 变量里边,同时通过 DOM API 操控 DOM 的属性或者行为,进而引起界面一些变化。当项目越来越大的时候,你的代码会充斥着非常多的界面交互逻辑和程序的各种状态变量,显然这不是一个很好的开发模式,因此就有了 MVVM 的开发模式(例如 React, Vue),提倡把渲染和逻辑分离。简单来说就是不要再让 JS 直接操控 DOM,JS 只需要管理状态即可,然后再通过一种模板语法,来描述状态和界面结构的关系即可。
仅仅通过数据绑定,还不够完整的描述状态和界面的关系,还需要 if/else, for等控制能力,在小程序里边,这些控制能力都用 wx: 开头的属性来表达。
wxss与css的区别
新增了
rpx尺寸单位CSS中需要手动进行像素单位换算,例如rem,而wxss在底层支持新的尺寸单位rpx,在不同大小的屏幕上小程序会自动进行换算。
鉴于不同设备屏幕的大小不同,为了实现屏幕的自动适配,rpx把所有设备的屏幕,在宽度上等分为750份(即:当前屏幕的总宽度为750rpx)。小程序在不同设备上运行的时候,会自动把 rpx的样式单位换算成对应的像素单位(px)来渲染,从而实现屏幕适配。
wxss仅支持部分CSS选择器,目前支持的选择器有:
选择器 样例 样例描述 .class .intro选择所有拥有 class=”intro” 的组件 #id #firstname选择拥有 id=”firstname” 的组件 元素选择器 view选择所有 view 组件 element, element view, checkbox选择所有文档的 view 组件和所有的 checkbox 组件 ::after view::after在 view 组件后边插入内容 ::before view::before在 view 组件前边插入内容 提供了全局的样式和局部样式。项目根目录中的
app.wxss会作用于所有小程序页面,而且不需要手动在每个页面导入这个文件,而页面的wxss样式仅对当前页面生效,并会覆盖app.wxss中相同的选择器。
框架组件上支持使用 style、class 属性来控制组件的样式,就好像普通的html标签一样。静态的样式统一写到 class 中。style 接收动态的样式,在运行时会进行解析,尽量避免将静态的样式写进 style 中,以免影响渲染速度。
wxs与js的区别
在微信小程序项目中,核心文件还是JS文件,而不是wxs 文件
限制
- 只支持js的部分语法,不支持es6语法,WXS 是一个受限的 JavaScript 子集
- wxs不能调用js中定义的函数,不能调用小程序提供的API,WXS 运行在视图层的独立 JavaScript 引擎中,小程序 API(wx.xxx)只在逻辑层可用
特点
wxs 代码可以编写在wxml文件中的
<wxs>标签内wxml文件中的每个
<wxs></wxs>标签,必须提供module属性,用来指定当前wxs 的模块名称,方便在wxml中访问模块中的成员。必须提供
src属性,用来指定要引入的脚本的路径,且必须是相对路径wxs代码还可以编写在以
.wxs为后缀名的文件内绑定事件不能使用wxs中的函数
wxs遵守cjs规范
1 | <wxs module="m1"> |
组件
视图组件
view
普通视图区域,类似于HTML中的div,是一个块级元素常用来实现页面的布局效果
scroll-view
可滚动的视图区域,常用来实现滚动列表效果;添加scroll-y属性,允许y轴滚动。添加scroll-x属性,允许x轴滚动
但是view+overflow: auto;不也能实现滚动效果吗,为什么还要借助scroll-view?因为scroll-view提供了许多其他的关键能力。
比如可以通过 scroll-top或者sroll-left属性设置滚动条的位置,通过refresher-enabled控制是否下拉刷新,通过 bindscroll监听滚动事件。
swiper和swiper-item
swiper的子元素是一个个swiper-item组件,swiper-item组件宽高自动设置为100%
轮播图组件,常用属性:
- indicator-dots:添加导航条,默认值false
- indicator-color:指示点颜色
- indicator-active-color:当前选中的指示点颜色
- autoplay:是否自动切换,默认为false
- interval:自动切换时间间隔(单位ms)
- circular:是否采用衔接滑动,默认为false
text
添加了selectable属性才能实现长按选择复制等操作
rich-text
通过rich-text组件的nodes属性节点,把HTML字符串渲染为对应的结构,就像vue里的v-html。
这段代码会将 <h1> 元素的内容渲染到 <rich-text> 组件内部,而不是替换整个 <rich-text> 标签
1 | <rich-text nodes="<h1 style='color: red; '>标题</h1>"></rich-text> |
其他组件
button
功能比HTML中的button 按钮丰富
属性:
type:指定按钮的类型,颜色,有点像前端框架里的语法了
size:指定按钮的大小
plain:添加了就具有镂空样式
open-type:通过这个属性可以调用微信提供的各种功能(客服、转发、获取用户授权、获取用户信息等),比如1
<button open-type="getUserInfo" bindgetuserinfo="tip">我是谁</button>
点击按钮后在tip函数回调中就能拿到用户信息。
image
组片组件,有默认宽高(300*150)
mode属性(前3个修改图片大小,后2个修改image容器大小)
- scaleToFill:(默认值)缩放模式,不保持纵横比缩放图片,使图片的宽高完全拉伸至填满image元素。没有空隙,没有裁剪,但是会失真)
- aspectFit:缩放模式,保持纵横比缩放图片,使图片的长边能完全显示出来。也就是说,可以完整地将图片显示出来。(就是contain)。没有裁剪,没有失真,但是可能有空隙。
- aspectFill:缩放模式,保持纵横比缩放图片,只保证图片的短边能完全显示出来。也就是说图片通常只在水平或垂直方向是完整的,另一个方向将会发生截取。(就是cover)。有裁剪,但是没有失真,没有空隙。
- widthFix:缩放模式,容器的宽度不变,高度自动变化,保持原图宽高比不变
- heightFix:缩放模式,容器的高度不变,宽度自动变化,保持原图宽高比不变
与img区别
微信中的image更像一个容器,src指定的图片是背景图片,前3个属性值在修改背景图片大小,后2个属性在修改**容器(image元素)**的大小。
而img的大小就是src指定的图片的大小。其实img就相当于mode=widthFix的image标签
获取界面上的节点信息
节点信息查询 API (createSelectorQuery) 可以用于获取节点属性、样式、在界面上的位置等信息。
最常见的用法是使用这个接口来查询某个节点的当前位置,以及界面的滚动位置。
示例代码:
1 | const query = this.createSelectorQuery() |
上述示例中, #the-id 是一个节点选择器,与 CSS 的选择器相近但略有区别
在自定义组件或包含自定义组件的页面中,推荐使用 this.createSelectorQuery (作用域是当前组件/页面的作用域内)来代替 wx.createSelectorQuery(作用域是整个页面) ,这样可以确保在正确的范围内选择节点。
WXML节点布局相交状态
在web中有IntersectionObserver API ,在微信小程序中也有类似的API,叫做createIntersectionObserver。
这一组API涉及的主要概念如下:
- 参照节点:监听的参照节点,取它的布局区域作为参照区域。如果有多个参照节点,则会取它们布局区域的 交集 作为参照区域。页面显示区域也可作为参照区域之一
- 目标节点:监听的目标,默认只能是一个节点
- 相交区域:目标节点的布局区域与参照区域的相交区域。
- 相交比例:相交区域占目标节点的布局区域的比例。
- 阈值:相交比例如果达到阈值,则会触发监听器的回调函数。阈值可以有多个
以下示例代码可以在目标节点(用选择器 .target-class 指定)每次进入或离开页面显示区域时
1 | Page({ |
以下示例代码可以在目标节点(用选择器 .target-class 指定)与参照节点(用选择器 .relative-class 指定)在页面显示区域内相交或相离,且相交或相离程度达到目标节点布局区域的20%和50%时,触发回调函数。
1 | Page({ |
在自定义组件或包含自定义组件的页面中,推荐使用 this.createIntersectionObserver 来代替 wx.createIntersectionObserver ,这样可以确保在正确的范围内选择节点
导航
open-type
跳转到tabBar页面,需要指定open-type属性为
1 | <navigator url=" /pages/message/message" open-type="switchTab">导航到消息页面</navigator> |
- 如果页面栈中存在tabbr页面,则这个tabbar页面一定在栈底
- 调用
switchTab方法会关闭所有非tabbar页面,跳转到一个tabbar页面 - 页面栈中同时只能存在一个tabbar页面,其他tabbar页面会处于悬垂状态,tabBar 页面实例会被保留(不销毁),但不在当前页面栈中
跳转到非tabBar页面:
open-type = navigate,不会关闭当前页面,页面栈长度+1open-type = redirectTo:弹出并销毁栈顶页面,在此过程中,这个栈顶页面的onUnload生命周期将被触发;之后框架将创建新的页面,并将其推入页面栈作为新的栈顶,页面栈长度不变。
后退导航:如果要后退到上一页面或多级页面,则需要指定open-type属性为navigateBack({ delta:1 }),delta表示后退层级,将页面栈当前的栈顶的若干个页面依次弹出并销毁。如果页面栈中当前只有一个页面,navigateBack 调用请求将失败(无论指定的 delta 是多少)
重加载:
重加载路由 reLaunch 表示销毁当前所有的页面,并载入一个新页面,无论它是否是 tabBar 页
导航传参
通过查询参数来传参
1 | <navigator url="/pages/infolinfo?name=zs&age=20">跳转到info页面</navigator> |
接收参数
传递的查询参数会被收集为一个对象,可以直接在onLoad事件中直接获取到
1 | onLoad(options){ |
页面路由监听
由于每次路由可能触发多个页面的多个页面生命周期,因此当某个页面的某个生命周期被触发时,小程序往往比较难判断它被触发的原因,从而难以做出一些针对路由(而非针对页面)的响应,所以推出了一些路由监听API,这些 API 是 全局监听器,在 App() 中或任意 JS 文件中注册,一次注册,全程生效
wx.onBeforeAppRoute(cb):开始执行跳转逻辑前wx.onAppRoute(cb):路由逻辑已执行完毕(页面栈已更新,新页面实例已创建)但动画尚未开始wx.onAppRouteDone(cb):页面切换动画完全结束(用户看到最终页面)- 等等…
路由事件id:为了在多次监听回调中识别同一个路由事件,框架会为每一次独立的路由事件生成一个在小程序实例中唯一的 ID,称为路由事件 ID。在所有页面路由监听函数中,事件参数中都将携带一个字符串 routeEventId,表示这个路由事件 ID。小程序可以通过读取回调中的 routeEventId,来将同一个路由在不同时间节点触发的不同回调进行关联
路由处理规则
当多次路由被连续发起时,如果当前的路由事件还未处理完毕,后续的路由事件将等待当前路由处理,并排队依次执行,直到所有待处理的路由都被执行完毕。
一个简单的例子:用户点击返回按钮触发了
navigateBack,小程序在页面栈的栈顶页的onUnload中调用wx.redirectTo,并不能 将当前正在被销毁的页面,重定向为一个新页面,而是会先完成页面返回,再将页面返回后的新栈顶页重定向到新的页面。
页面的生命周期
小程序页面的生命周期函数有以下几种:
onLoad
页面加载时触发,一个页面只会调用一次,可以在这个函数的参数中拿到当前页面中的查询参数。
onShow
页面显示的时候触发
onReady
页面初次渲染完成时触发,一个页面只会调用一次,代表页面已经准备妥当,可以和视图层进行交互。设置导航栏的标题,应该在这个时间之后进行。类似Vue中的mounted
onHide
页面隐藏/切入后台时触发
onUnload
页面写在时触发
自定义组件
开发者可以将页面内的功能模块抽象成自定义组件,以便在不同的页面中重复使用;也可以将复杂的页面拆分成多个低耦合的模块,有助于代码维护。自定义组件在使用时与基础组件非常相似
创建自定义组件
在根目录下的components文件夹上,右键选择新建组件。类似于页面,一个自定义组件由 json wxml wxss js 4个文件组成。要编写一个自定义组件,首先需要在 json 文件中进行自定义组件声明(将 component 字段设为 true )
1 | { |
同时,还要在 wxml 文件中编写组件模板,在 wxss 文件中加入组件样式,它们的写法与页面的写法类似
在自定义组件的 js 文件中,需要使用 Component() 来注册组件(页面调用的是Page()函数),并提供组件的属性定义、内部数据和自定义方法。
1 | Component({ |
使用自定义组件
使用组件前需要先进行注册,注册的方式分为全局注册和局部注册,全局注册就是在app.json文件的usingComponents属性中进行配置,局部注册就是在某个页面的json文件的usingComponents属性中进行配置。
需要提供每个自定义组件的标签名,和对应的自定义组件文件路径。
1 | { |
上述例子中路径”/components/navbar/navbar” 具体到了navbar文件,而不是目录
这样,在页面的 wxml 中就可以像使用基础组件一样,使用自定义组件。节点名即自定义组件的标签名,节点属性即传递给组件的属性值
一些需要注意的细节
- 因为 WXML 节点标签名只能是小写字母、中划线和下划线的组合,所以自定义组件的标签名也只能包含这些字符。
- 自定义组件也是可以引用自定义组件的,引用方法类似于页面引用自定义组件的方式(使用
usingComponents字段)
样式隔离
- 默认情况下,自定义组件的样式只对当前组件生效,不会影响到组件之外的UI结构,同时组件所在页面的样式也不会影响组件的样式
- app.wxss中的样式也不会影响组件中的样式,前提是这些样式不是标签选择器
能实现样式隔离的本质,其实和Vue一样,就是给标签添加自定义属性,然后重写组件内的选择器,额外添加上一个属性选择器,这样组件内的样式就只能对组件内的结构生效了。
组件传值
小程序组件中,properties是组件的对外属性,用来接收外界传递到组件中的数据。
1 | 页面 (Page) |
在以上例子中,组件的属性 propA 和 propB 将收到页面传递的数据。
页面可以通过 setData 来改变绑定的数据字段。当页面调用 this.setData({ dataFieldA: 'new value' }),子组件的 propA 会自动更新
在小程序的组件中,properties属性和data数据的用法相同,它们都是可读可写的,只不过:
- data更倾向于存储组件的私有数据
- properties更倾向于存储外界传递到组件中的数据
实际上小程序组件中的this.data和this.properties完全是两个相同的对象
数据监听器
批量监听基本类型的数据
1 | Component({ |
监听对象的属性变化
1 | Component({ |
监听对象所有属性的变化
1 | observers:{ |
纯数据字段
纯数据字段指的是那些不用于界面渲染的data字段。
应用场景:例如有些情况下,某些data中的字段既不会展示在界面上,也不会传递给其他组件,仅仅在当前组件内部使用。带有这种特性的data字段适合被设置为纯数据字段。
1 | Component({ |
组件通信
父传子:父组件向子组件的指定属性设置数据,仅能设置 JSON 兼容数据,不能传递方法。传递给子组件的值只是拷贝,可以在子组件内修改父组件传递过来的数据,但是父组件中的数据并不会改变,修改父组件中的数据,会重新给子组件传值并更新页面。
子传父:在子组件内部触发自定义事件,向父组件传递数据。监听自定义组件事件的方法,与监听基础组件事件的方法完全一致
注册事件
1 | <!-- 当自定义组件触发“myevent”事件时,调用“onMyEvent”方法 --> |
1 | Page({ |
触发事件
自定义组件触发事件时,需要使用 triggerEvent 方法,指定事件名、detail对象和事件选项
1 | <button bindtap="onTap">点击这个按钮将触发“myevent”事件</button> |
1 | Component({ |
如果以上两种方式不足以满足需要,父组件还可以通过 this.selectComponent 方法获取子组件实例对象,这样就可以直接访问组件的任意数据和方法。调用时需要传入一个匹配选择器 selector,比如this.selectComponent(".my-component"),selector类似于 CSS 的选择器,但是只支持部分选择器。
组件的生命周期
组件的生命周期,指的是组件自身的一些函数,这些函数在特殊的时间点,或遇到一些特殊的框架事件时被自动触发。
其中,最重要的生命周期是 created attached detached ,包含一个组件实例生命流程的最主要时间点。
created
此时组件实例刚刚被创建好时,组件数据 this.data 就是在 Component 构造器中定义的数据 data 。 此时还不能调用 setData 。 通常情况下,这个生命周期只应该用于给组件 this 添加一些自定义属性字段。
attached
在组件完全初始化完毕、进入页面节点树后, attached 生命周期被触发。此时组件还没渲染到页面, this.data 已被初始化为组件的当前值。这个生命周期很有用,绝大多数初始化工作可以在这个时机进行。
detached
在组件离开页面节点树后, detached 生命周期被触发。退出一个页面时,如果组件还在页面节点树中,则 detached 会被触发,著作做一些清理工作。
生命周期方法可以直接定义在 Component 构造器的第一级参数中,但是最好定义在 lifetimes 字段内,优先级更高。
1 | Component({ |
组件所在页面的生命周期
还有一些特殊的生命周期,它们并非与组件有很强的关联,但有时组件需要获知,以便组件内部处理。这样的生命周期称为“组件所在页面的生命周期”,在 pageLifetimes 定义段中定义
其中可用的生命周期包括:
| 生命周期 | 参数 | 描述 |
|---|---|---|
| show | 无 | 组件所在的页面被展示时执行 |
| hide | 无 | 组件所在的页面被隐藏时执行 |
| resize | Object Size | 组件所在的页面尺寸变化时执行 |
1 | Component({ |
插槽
在组件模板中可以提供一个 <slot> 节点,用于承载组件引用时提供的子节点
1 | <!-- 组件模板 --> |
1 | <!-- 引用组件的页面模板 --> |
默认情况下,一个组件的 wxml 中只能有一个 slot 。需要使用多 slot 时,可以在组件 js 中声明启用。
1 | Component({ |
至于多插槽的使用方法,参考官方文档中指南的部分
代码复用behaviors
微信小程序中的behaviors,类似于Vue中的 “mixins”
定义behavior
使用Behavior函数创建一个behavior对象
1 | // behaviors/log.js |
在组件中使用 behavior
1 | // components/my-button/my-button.js |
查看自定义组件的数据
wxml 面板中可以查看自定义组件在渲染时的 Data 数据。 在 wxml 中先选中需要查看的自定义组件,然后切换到 Component Data 即可实时查看当前自定义组件的数据。
组件和页面的区别
- 组件的JSON文件中需要添加
"component": true - 组件的js文件中调用的是Component函数,而页面中调用的是Page函数。
- 页面中的函数可以直接定义到和data平级的位置,而组件中的函数必须定义在methods属性中
基础能力
网络
域名设置
每个微信小程序需要事先设置通讯域名,小程序只可以跟指定的域名进行网络通信。包括普通 HTTPS 请求(wx.request)、上传文件(wx.uploadFile)、下载文件(wx.downloadFile) 和 WebSocket 通信。
- 而且域名只支持
https协议或者wss协议。 - 域名不能使用 IP 地址或 localhost
- 不支持配置父域名,使用子域名
- 域名必须经过 ICP 备案
在开启阶段,可以在微信开发者工具中勾选**”不校验合法域名和HTTPS证书”**的选项来跳过域名检查。
小程序中不存在跨域问题,因为小程序的宿主环境不是浏览器,小程序中的请求不叫做ajax请求,因为ajax请求是基于xhr或者fetch的。
DNS预解析域名
DNS预解析域名,是框架提供的一种在小程序启动时,提前解析业务域名的技术,从而提高请求的速度。
DNS域名配置请求「小程序后台-开发-开发设置-服务器域名」 中进行配置,配置时需要注意:
- 预解析域名无需填写协议头
- 预解析域名最多可添加 5 个
- 其他安全策略同服务器域名配置策略
超时时间
- 默认超时时间是 60s
- 超时时间可以在
app.json或game.json中通过networktimeout配置 - 也可以在接口调用时指定超时时间,如
wx.request({ timeout: 5000 }),单位为ms。接口调用的timeout配置优先级高于app.json中的配置
存储
每个微信小程序都可以有自己的本地缓存,可以通过 wx.setStorage/wx.setStorageSync等API进行操作。本地缓存的清理时机跟代码包一样,只有在代码包被清理的时候本地缓存才会被清理。
文件系统
通过 wx.getFileSystemManager() 可以获取到全局唯一的文件系统管理器,所有文件系统的管理操作通过 FileSystemManager来调用。
1 | var fs = wx.getFileSystemManager() |
文件主要分为两大类:
- 代码包文件:代码包文件指的是在项目目录中添加的文件。由于代码包文件大小限制,代码包文件适用于放置首次加载时需要的文件,对于内容较大或需要动态替换的文件,不推荐用添加到代码包中,推荐在小游戏启动之后再用下载接口下载到本地
- 本地文件:通过调用接口本地产生,或通过网络下载下来,存储到本地的文件。
其中本地文件又分为三种:
本地临时文件:临时产生,随时会被回收的文件。本地临时文件只能通过调用特定接口产生,不能直接写入内容。本地临时文件产生后,仅在当前生命周期内保证有效,重启之后不一定可用。如果需要保证在下次启动时无需下载,可通过
FileSystemManager.saveFile()或FileSystemManager.copyFile()接口把本地临时文件转换成本地缓存文件或本地用户文件。1
2
3
4
5wx.chooseImage({
success: function (res) {
var tempFilePaths = res.tempFilePaths // tempFilePaths 的每一项是一个本地临时文件路径
}
})本地缓存文件:小程序通过接口把本地临时文件缓存后产生的文件,不能自定义目录和文件名。本地缓存文件产生后,重启之后仍可用。本地缓存文件只能通过
FileSystemManager.saveFile()接口将本地临时文件保存获得1
2
3
4
5
6fs.saveFile({
tempFilePath: '', // 传入一个本地临时文件路径
success(res) {
console.log(res.savedFilePath) // res.savedFilePath 为一个本地缓存文件路径
}
})注意:本地缓存文件是最初的设计,
1.7.0版本开始,提供了功能更完整的本地用户文件,可以完全覆盖本地缓存文件的功能,如果不需要兼容低于1.7.0版本,可以不使用本地缓存文件。本地用户文件:小程序通过接口把本地临时文件缓存后产生的文件,允许自定义目录和文件名。本地用户文件是从
1.7.0版本开始新增的概念。我们提供了一个用户文件目录给开发者,开发者对这个目录有完全自由的读写权限。通过wx.env.USER_DATA_PATH可以获取到这个目录的路径。1
2
3// 在本地用户文件目录下创建一个文件 hello.txt,写入内容 "hello, world"
const fs = wx.getFileSystemManager()
fs.writeFileSync(`${wx.env.USER_DATA_PATH}/hello.txt`, 'hello, world', 'utf8')
清理策略
- 本地临时文件只保证在小程序当前生命周期内,一旦小程序被关闭就可能被清理,即下次冷启动不保证可用。
- 本地缓存文件和本地用户文件的清理时机跟代码包一样,只有在代码包被清理的时会被清理。
分包
定义
分包指的是把一个完整的小程序项目,按照需求划分为不同的子包,在构建时打包成不同的分包,用户在使用时按需进行加载,类似webpack中的代码分割。可以优化小程序首次启动的下载时间,在多团队共同开发时可以更好的解耦协作。
结构
一个主包
一般只包含项目的启动页面或TabBar页面、以及所有分包都需要用到的一些公共资源
多个分包
只包含和当前分包有关的页面和私有资源
分包的加载规则
- 在小程序启动时,默认会下载主包并启动主包内页面(tabbar页面)
- 非tabBar页面可以按照功能的不同,划分为不同的分包之后,进行按需下载
- 当用户进入分包内某个页面时,客户端会把对应分包下载下来,下载完成后再进行展示
体积限制
整个小程序所有分包大小不超过16M(主包+所有分包),单个分包/主包大小不能超过2M
配置规则
根目录下的
pages一般放的是所有主包页面在根目录下新建
subpkg文件(自定义名称),创建一个分包,内部可以有多个页面,就如同pages文件夹。
还要在
app.json文件里声明分包结构。可以看到subPackages的值是一个对象数组,说明可以设置多个分包,一个分包对应一个单独的文件夹,内部可以有多个页面。1
2
3
4
5
6
7
8
9
10
11
12
13
14{
"subPackages": [
{
"root": "subpkg",
"name":'pkgA',//分包别名
//当前分包下所有页面的相对路径
"pages": [
"detail/detail",
"goods_list/goods_list",
"search/search"
]
}
]
}
打包原则
- subpackages之外的目录将被打包到主包中
- tabBar 页面必须在主包内
subPackages的根目录不能是另外一个subPackages内的子目录
引用原则
packageA无法 requirepackageBJS 文件,但可以 require 主包、packageA内的 JS 文件;使用 [分包异步化]时不受此条限制packageA无法 requirepackageB的 template,但可以 require 主包、packageA内的 templatepackageA无法使用packageB的资源,但可以使用主包、packageA内的资源
独立分包
- 普通分包的打开依赖于主包,独立分包可以不依赖主包打开,可以独立于主包和其他分包运行
- 开发者可以按需将某些具有一定功能独立性的页面,配置到独立分包中。当小程序从普通的分包页面启动时,需要首先下载主包;而独立分包不依赖主包即可运行,可以很大程度上提升分包页面的启动速度
- 独立分包在配置的时候,需要额外添加
independent:true属性。
1 | { |
- 独立分包和普通分包以及主包之间,是相互隔绝的,不能相互引用彼此的资源,独立分包中不能引用主包内的公共资源

- 主包中的
app.wxss对独立分包无效,应避免在独立分包页面中使用app.wxss中的样式
分包预下载
在进入小程序的某个页面时,由框架自动预下载可能需要的分包,从而提高进入后续分包的启动速度
在app.json中配置:
1 | "preloadRule":{ //分包预下载规则 |
preloadRule 中,key 是页面路径,value 是进入此页面的预下载配置,每个配置有以下几项:
| 字段 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
| packages | StringArray | 是 | 无 | 进入页面后预下载分包的 root 或 name。__APP__ 表示主包。 |
| network | String | 否 | wifi | 在指定网络下预下载,可选值为: all: 不限网络 wifi: 仅wifi下预下载 |
同一个分包中的页面享有共同的预下载大小限额2M,如,页面 A 和 B 都在同一个分包中,A 中预下载总大小 0.5M 的分包,B中最多只能预下载总大小 1.5M 的分包。
开放能力
权限处理
小程序中部分接口需要经过用户授权同意才能调用。我们把这些接口按使用范围分成多个 scope ,用户选择对 scope 来进行授权,当授权给一个 scope 之后,其对应的所有接口都可以直接使用。
开发者可以使用 wx.getSetting获取用户当前的授权状态
1 | wx.getSetting({ |
在回调函数中可以判断用户是否授予了某个权限
开发者可以调用 wx.openSetting() 打开设置界面,引导用户开启授权,但是必须由用户手动触发。
开发者可以使用 wx.authorize 在调用需授权 API 之前,提前向用户发起授权请求。在真正需要使用授权接口时,才向用户发起授权申请,并在授权申请中说明清楚要使用该功能的理由。
1 | wx.getSetting({ |
一旦用户明确同意或拒绝过授权,其授权关系会记录在后台,直到用户主动删除小程序。
用户信息
小程序登录
小程序可以通过微信官方提供的登录能力,方便地获取微信提供的用户身份标识,快速建立小程序内的用户体系。
使用微信一键登录的功能,需要小程序,开发者服务器,微信接口服务器这三者协调配合。
- 在小程序端先调用
wx.login方法,获取临时登录凭证code,这个code是本地生成的,不需会产生请求。再调用开发者服务器的登录接口,并携带这个登录凭证code - 然后开发者的服务器会调用微信的登录凭证校验接口,并携带appid,appsecret和code
- 微信接口服务器会返回用户在该小程序下的唯一标识openid和会话密钥 session_key
- 开发者服务器拿到
openid和session_key,生成自定义登录态,用于后续业务逻辑中前后端交互时识别用户身份
注意:
- 会话密钥
session_key是对用户数据进行加密签名的密钥。为了应用自身的数据安全,开发者服务器不应该把会话密钥下发到小程序,也不应该对外提供这个密钥。 - 临时登录凭证 code 只能使用一次
头像昵称填写
当小程序需要让用户完善个人资料时,可以通过微信提供的头像昵称填写能力快速完善
头像选择:需要将 button 组件 open-type 的值设置为 chooseAvatar(没有对应的api),当用户选择需要使用的头像之后,可以通过 bindchooseavatar="onChooseAvatar" 事件回调获取到头像信息的临时路径。
1 | <button open-type="chooseAvatar" bindchooseavatar="onChooseAvatar">修改头像</button> |
1 | onChooseAvatar(e) { |
从基础库2.24.4版本起,若用户上传的图片未通过安全监测,不触发bindchooseavatar 事件。
昵称填写:需要将 input 组件 type 的值设置为 nickname,当用户在此input进行输入时,键盘上方会展示微信昵称。
1 | <input type="nickname" value="微信用户"></input> |
分享页面
onShareAppMessage,用于监听用户点击页面内转发按钮(button组件 open-type="share")或右上角菜单“转发”按钮的行为,并自定义转发内容。
注意:只有定义了此事件处理函数,右上角菜单才会显示“转发”按钮
此事件处理函数需要 return 一个 Object,用于自定义转发内容,返回内容如下
| 字段 | 说明 | 默认值 |
|---|---|---|
| title | 转发标题 | 当前小程序名称 |
| path | 转发路径 | 当前页面 path ,必须是以 / 开头的完整路径 |
| imageUrl | 自定义图片路径,可以是本地文件路径、代码包文件路径或者网络图片路径。支持PNG及JPG。显示图片长宽比是 5:4。 | 使用默认截图 |
1 | Page({ |
分享到朋友圈
onShareTimeline(),监听右上角菜单“分享到朋友圈”按钮的行为,并自定义分享内容。
注意:只有定义了此事件处理函数,右上角菜单才会显示“分享到朋友圈”按钮
| 字段 | 说明 | 默认值 |
|---|---|---|
| title | 自定义标题,即朋友圈列表页上显示的标题 | 当前小程序名称 |
| query | 自定义页面路径中携带的参数,如 path?a=1&b=2 的 “?” 后面部分 | 当前页面路径携带的参数 |
| imageUrl | 自定义图片路径,可以是本地文件或者网络图片。支持 PNG 及 JPG,显示图片长宽比是 1:1。 | 默认使用小程序 Logo |
1 | Page({ |
小程序页面默认不可被分享到朋友圈,开发者需主动设置“分享到朋友圈”。页面允许被分享到朋友圈,需满足两个条件:
- 首先,页面需设置允许“发送给朋友”。即注册
onShareAppMessage监听 - 然后,页面需设置允许“分享到朋友圈,即注册
onShareTimeline()监听
微信支付
创建订单
- 请求创建订单的API接口:把(订单金额、收货地址、订单中包含的商品信息)发送到服务器
- 服务器响应的结果:订单编号
订单预支付
- 请求订单预支付的API接口:把(订单编号)发送到服务器
- 服务器响应的结果:订单预支付的参数对象,里面包含了订单支付相关的必要参数
发起微信支付
调用uni.requestPayment这个API,发起微信支付,把步骤2得到的“订单预支付对象”作为参数传递给uni.requestPayment()方法
监听uni.requestPayment()这个API 的success,fail,complete回调函数。
小程序的生命周期
小程序从启动到最终被销毁,会经历很多不同的状态,小程序在不同状态下会有不同的表现

小程序启动
从用户认知的角度看,广义的小程序启动可以分为两种情况,一种是冷启动,一种是热启动。
- 冷启动:如果用户首次打开,或小程序销毁后被用户再次打开,此时小程序需要重新加载启动,即冷启动。
- 热启动:如果用户已经打开过某小程序,然后在一定时间内再次打开该小程序,此时小程序并未被销毁,只是从后台状态进入前台状态,这个过程就是热启动。
从小程序生命周期的角度来看,我们一般讲的「启动」专指冷启动,热启动一般被称为后台切前台
前台与后台
小程序启动后,界面被展示给用户,此时小程序处于「前台」状态。
当用户「关闭」小程序时,小程序并没有真正被关闭,而是进入了「后台」状态,此时小程序还可以短暂运行一小段时间,但部分 API 的使用会受到限制
挂起
小程序进入「后台」状态一段时间后(目前是 5 秒),微信会停止小程序 JS 线程的执行,小程序进入「挂起」状态。此时小程序的内存状态会被保留,但开发者代码执行会停止,事件和接口回调会在小程序再次进入「前台」时触发。
当开发者使用了后台音乐播放、后台地理位置等能力时,小程序可以在「后台」持续运行,不会进入到「挂起」状态
生命周期函数
小程序应用的生命周期函数有3个
onLaunch
小程序初始化完成时触发,全局只触发一次
onShow
小程序启动后,从后台进入前台
onHide
小程序启动后,从前台进入到后台
1 | App({ |
发布上线
一个小程序从开发完到上线一般要经过 预览-> 上传代码 -> 提交审核 -> 发布等步骤。
预览
使用开发者工具可以预览小程序,帮助开发者检查小程序在移动客户端上的真实表现。
点击开发者工具顶部操作栏的预览按钮,开发者工具会自动打包当前项目,并上传小程序代码至微信的服务器,成功之后会在界面上显示一个二维码。使用当前小程序开发者的微信扫码,即可看到小程序在手机客户端上的真实表现。
上传代码
同预览不同,上传代码是用于提交体验或者审核使用的。
点击开发者工具顶部操作栏的上传按钮,填写版本号以及项目备注,需要注意的是,这里版本号以及项目备注是为了方便管理员检查版本使用的,开发者可以根据自己的实际要求来填写这两个字段。
上传成功之后,登录小程序管理后台 - 版本管理 - 开发版本 就可以找到刚提交上传的版本了。
可以将这个版本设置 体验版 或者是 提交审核
提交审核
为了保证小程序的质量,以及符合相关的规范,小程序的发布是需要经过审核的。
在开发者工具中上传了小程序代码之后,登录 小程序管理后台 - 版本管理 - 开发版本 找到提交上传的版本。
在开发版本的列表中,点击 提交审核 按照页面提示,填写相关的信息,即可以将小程序提交审核。
需要注意的是,请开发者严格测试了版本之后,再提交审核, 过多的审核不通过,可能会影响后续的时间。
发布
审核通过之后,管理员的微信中会收到小程序通过审核的通知,此时登录 小程序管理后台 - 版本管理 - 审核版本中可以看到通过审核的版本。
点击发布后,即可发布小程序。小程序提供了两种发布模式:全量发布和分阶段发布。全量发布是指当点击发布之后,所有用户访问小程序时都会使用当前最新的发布版本。分阶段发布,是指分不同时间段,来控制部分用户使用最新的发布版本,分阶段发布我们也称为灰度发布。一般来说,普通小程序发布时采用全量发布即可,当小程序承载的功能越来越多,使用的用户数越来越多时,采用分阶段发布是一个非常好的控制风险的办法。
说说你对微信小程序的理解?优缺点?
是什么
2017年,微信正式推出了小程序,允许外部开发者在微信内部运行自己的代码,开展业务
截至目前,小程序已经成为国内前端的一个重要业务,跟 Web 和手机 App 有着同等的重要性
小程序是一种不需要下载安装即可使用的应用(但实际上还是存在一个轻量级的下载和安装流程,不过用户感知不到),它实现了应用“触手可及”的梦想,用户扫一扫或者搜一下即可打开应用
也体现了“用完即走”的理念,用户不用关心是否安装太多应用的问题。应用将无处不在,随时可用,但又无需安装卸载
注意的是,除了微信小程序,还有百度小程序、微信小程序、支付宝小程序、抖音小程序,都是每个平台自己开发的,都是有针对性平台的应用程序
背景
小程序并非凭空冒出来的⼀个概念,当微信中的 WebView 逐渐成为移动 Web的⼀个重要入口时,微信就有相关的 JS-SDK
JS-SDK 解决了移动网页能⼒不⾜的问题,通过暴露微信的接⼝使得 Web 开发者能够拥有更多的能⼒,然而在更多的能⼒之外,JS-SDK的模式并没有解决使⽤移动网页遇到的体验不良的问题
因此需要设计⼀个比较好的系统,使得所有开发者在微信中都能获得⽐较好的体验:
- 快速的加载
- 更强⼤的能⼒
- 原⽣的体验
- 易⽤且安全的微信数据开放
- 高效和简单的开发
这些是JS-SDK做不到的,需要设计一个全新的小程序系统
其中相比H5,小程序与其的区别有如下:
- 运⾏环境:⼩程序基于浏览器内核重构的内置解析器
- 系统权限:⼩程序能获得更多的系统权限,如⽹络通信状态、数据缓存能⼒等
- 渲染机制:⼩程序的逻辑层和渲染层是分开的
微信小程序可以视为只能用微信打开和浏览的H5,小程序和网页的技术模型是一样的,用到的 JavaScript 语言和 CSS 样式也是一样的,只是网页的 HTML 标签被稍微修改成了 WXML 标签
因此可以说,小程序页面本质上就是网页
优缺点
优点:
- 随搜随用,用完即走:使得小程序可以代替许多APP,或是做APP的整体嫁接,或是作为阉割版功能的承载体
- 流量大,易接受:小程序借助自身平台更加容易引入更多的流量
- 安全,开发门槛低,降低兼容性限制
缺点:
- 用户留存:及相关数据显示,小程序的平均次日留存在13%左右,但是双周留存骤降到仅有1%
- 体积限制:微信小程序只有2M的大小,这样导致无法开发大型一些的小程序
- 受控微信:比起APP,尤其是安卓版的高自由度,小程序要面对很多来自微信的限制,从功能接口,甚至到类别内容,都要接受微信的管控
说说微信小程序的实现原理?
背景
网页开发,渲染线程和脚本是互斥的,这也是为什么长时间的脚本运行可能会导致页面失去响应的原因,本质就是我们常说的 JS 是单线程的。而在小程序中,将视图层和逻辑层分开的,双线程同时运行,视图层的界面使用 WebView 进行渲染,逻辑层运行在 JSCore 中。
其中 WXML 模板和 WXSS 样式工作在渲染层,JS 脚本工作在逻辑层。

渲染层:
负责界面展示,包括布局、样式等。 实际上是通过WebView 来解析和渲染的。界面渲染相关的任务全都在WebView线程里执行。一个小程序存在多个界面,所以渲染层存在多个 WebView 线程
逻辑层:
主要使用 JavaScript 编写。负责处理业务逻辑、数据获取、API 请求等,其中网络请求由微信客户端进行转发,运行在一个基于 V8 或者 JSCore 的 JavaScript 引擎之上(视操作系统而定)
小程序的逻辑层和渲染层是分开的,逻辑层运行在不同于渲染层的独立 JS 运行时中,因此并不能直接使用 DOM API 和 BOM API。这一区别导致了前端开发非常熟悉的一些库,例如 jQuery,在小程序中是无法运行的。同时逻辑层的 JS 运行时与 NodeJS 环境也不尽相同,所以一些 NPM 的包在小程序中也是无法运行的。
Native
Native 层指的是微信客户端本身的原生代码,它负责:
- 提供基础库,包含所有可用的
API。 - 处理逻辑层和渲染层之间的高效通信。
- 实现性能优化,确保小程序能够流畅运行。提供安全机制,保护用户数据和隐私。
WebView 是什么
WebView 是一个允许在应用程序内部展示网页内容的组件,它依赖于特定平台上的浏览器内核来工作。
在不同的平台上有不同实现:
- Android:使用
android.webkit.WebView。 - iOS:使用
WKWebView(较新的推荐使用方式)或UIWebView(已废弃)。
相当于 Web 开发中的 浏览器渲染引擎
通信
逻辑层和渲染层之间的通信,是通过微信客户端(Native,或者说是宿主环境)提供的 API 实现的,比如逻辑层可以通过 setData 方法将数据传递给渲染层,触发页面更新:
1 | this.setData({ |
在逻辑层发生数据变更的时候,通过宿主环境(native)提供的setData方法,把数据从逻辑层传递到渲染层,再经过对比前后差异,把差异应用在原来的Dom树上(小程序在渲染层,宿主环境会把wxml转化成对应的JS对象,类似web开发中的dom对象),渲染出正确的视图
运行环境
| 运行环境 | 逻辑层 | 渲染层 |
|---|---|---|
| iOS | JavaScriptCore | WKWebView |
| 安卓 | V8 | chromium定制内核 |
| 小程序开发者工具 | NWJS | Chrome WebView |
扩展:微信小程序
微信小程序的一键登录怎么做
调用
uni.login接口需要用户已经完成小程序验证,否则无法调用,完成小程序验证要300元/年
先调用
uni.login方法,这会发送一个请求给微信服务器,得到一个临时登录凭证code(临时登录凭证,有效期 5 分钟,只能使用一次,这个登录凭证和微信用户信息是绑定的)然后调用
uni.request方法将这个临时登录凭证,发送到小程序的后端服务器,小程序后端服务器将这个临时登录凭证code,连同小程序的APPID和APPSECRET(小程序密钥,只能在后端使用,不能暴露在前端!)发送给微信服务器,换取openid, session_key,然后返回一个token给小程序,用户的后续请求都会携带这个token,返回的token中就包含openid。openid表示用户在当前小程序下的唯一标识(每个用户 + 每个小程序,唯一),计算方式为:openid = hash(用户微信号 + 小程序 AppID),同一个用户在同一个微信小程序中,无论登录多少次,得到的openid都是完全一致的
1 | async wechatLogin() { |
如果还需要用户昵称、头像等信息,则需要调用:
1 | // 获取用户信息(需用户点击按钮授权) |
注意:getUserProfile 已被限制使用,推荐使用 <button open-type="getUserInfo"> 组件方式触发。
后端代码如下
1 | // server.js |
在微信小程序上怎么做微信支付
想在微信小程序中实现支付功能,需要先满足一下条件:
- 小程序已经上线,个人小程序不支持支付
- 个人类小程序上线,不需要支付 300 元认证费,但必须完成微信实名认证(绑定身份证)完成后可以: 提交代码审核
, 发布上线, 使用基础功能但个人小程序功能受限,不支持:微信支付,获取用户手机号,广告组件,附近的小程序 - 在
pay.weixin.qq.com申请微信商户号
然后执行如下步骤:
- 前端请求后端创建订单,后端调用微信统下单 API,生成
requestPayment所需参数并返回 - 前端调用
uni.requestPayment调起支付,用户输入密码完成支付 - 支付成功后,微信服务器异步调用小程序后端的支付回调接口,后端更新订单状态,返回 SUCCESS
- 前端跳转支付成功页
1 | <template> |
后端代码如下
1 | // Node.js 示例(Express) |
