逻辑层
一、概述
逻辑层是事务逻辑处理的地方。对于小程序而言,逻辑层就是.js脚本文件的集合。逻辑层对数据进行处理后发送给视图层,同时接收视图层的事件反馈。
微信小程序开发框架的逻辑层是由JavaScript编写的。在JavaScript的基础上,微信团队做了一些适当的修改,以便提高小程序的开发效率。主要修改包括:
- 增加App和page函数,进行程序和页面的注册。
- 提供丰富的API,如扫一扫、支付等微信特有的功能。
- 每个页面有独立的作用域,并提供模块化功能。
逻辑层的实现就是编写各个页面的.s脚本文件。但由于小程序并非运行在浏览器中,所以JavaScript在Web中的一些功能无法使用,如document、window等。
小程序开发编写的所有代码最终会打包成一份JavaScript,并在小程序启动的时候运行,直到小程序销毁。
二、页面数据
2.1、页面数据的定义
页面JS文件page函数中第一项为data属性,在data中定义本页面逻辑处理需要用到的数据,其中很大一部分数据将用于WXL文件的数据渲染。因为小程序JS文件是基于
JavaScript编写的,所以在JS文件中可以定义字符串、数字、布尔值、对象和数组等类型的数据。
js
Page({
data:{
msg1: "hello",
msg2: 2000
}
})
2.2、使用setData()修改数据
除了使用数据的初始化,还可以使用page原型实例的setData()函数修改数据的取值,这种方法能够将相关数据异步更新到WXML页面上。setData()函数用于将数据从逻辑层
发送到视图层(异步),同时改变对应的this.data的值(同步)。
Object以key:value的形式表示,将this.data中的key对应的值改变成value.。其参数说明如下表所示。
| 属性 | 类型 | 说明 |
|---|---|---|
data |
object | 这次要改变的数据 |
callback |
function | setData引起的界面更新渲染完毕后的回调函数 |
js
Page({
data: {
date: "2000-01-01"
},
changeData: function() {
this.setData({
date: "2010-01-01"
})
}
})
setData()函数修改数据的取值经常用于WXML文件数据绑定和用户的交互场景。
2.3、页面事件处理函数
在page()函数中默认产生一系列页面事件处理函数,用于响应用户对页面执行某一动作而执行的处理。函数的说明如下。
- onPullDownRefresh() :监听用户下拉刷新事件。需要在app.json的window选项中或页面配置中开启
enablePullDownRefresh。可以通过wx.startPullDownRefresh()触发下拉刷新,调用后触发下拉刷新动画,效果与用户手动下拉刷新一致。当处理完数据刷新后,wx.stopPullDownRefresh()可以停止当前页面的下拉刷新。 - onReachBottom() :监听用户上拉触底事件。可以在app.json的window选项中或页面配置中设置触发距离
onReachBottomDistance。在触发距离内滑动期间,本事
件只会被触发一次。 - onPageScroll(Object):监听用户滑动页面。其参数Object具有唯一属性scrollTop,为Number类型,表示页面在垂直方向己滚动的距离(单位为px)。
- onShareAppMessage(Object) :监听用户单击页面内转发按钮
<button>组件(opentype="share'")或右上角菜单"转发"按钮的行为,并自定义转发内容。需要注意的是只有定义了此事件处理函数,右上角菜单才会显示"转发"按钮。Object的参数如下表所示。
| 名称 | 类型 | 说明 |
|---|---|---|
| from | String | 转发事件来源button:页面内转发按钮;menu:右上角转发菜单 |
| target | Object | 如果 from 值是 button,则 target 是触发这次转发事件的 button,否则为 undefined |
| webViewUrl | String | 页面中包含web-view组件时,返回当前web-view的url |
此事件处理函数需要 return 一个 Object,用于自定义转发内容,返回内容如下:
| 字段 | 说明 | 默认值 |
|---|---|---|
| title | 转发标题 | 当前小程序名称 |
| path | 转发路径 | 当前页面 path ,必须是以 / 开头的完整路径 |
| imageUrl | 自定义图片路径,可以是本地文件路径、代码包文件路径或者网络图片路径。支持PNG及JPG。显示图片长宽比是 5:4。 | 使用默认截图 |
| promise | 如果该参数存在,则以 resolve 结果为准,如果三秒内不 resolve,分享会使用上面传入的默认参数 |
js
Page({
onShareAppMessage() {
const promise = new Promise(resolve => {
setTimeout(() => {
resolve({
title: '自定义转发标题'
})
}, 2000)
})
return {
title: '自定义转发标题',
path: '/page/user?id=123',
promise
}
}
})
-
onResize(Object object)
页面尺寸改变时触发。基础库 2.4.0 开始支持,低版本需做兼容处理。页面尺寸改变时触发。
-
onTabItemTap(Object object)
点击 tab 时触发。基础库 1.9.0 开始支持,低版本需做兼容处理。点击 tab 时触发,参数如下:
| 参数 | 类型 | 说明 |
|---|---|---|
| index | String | 被点击tabItem的序号,从0开始 |
| pagePath | String | 被点击tabItem的页面路径 |
| text | String | 被点击tabItem的按钮文字 |
js
Page({
onTabItemTap(item) {
console.log(item.index)
console.log(item.pagePath)
console.log(item.text)
}
})
2.4、页面跳转
页面跳转在小程序中被称为页面路由,所有页面的路由全部由框架进行管理。框架以栈的形式维护当前的所有页面。当发生路由切换的时候,页面栈的表现如下表所示。
| 路由方式 | 页面栈表现 |
|---|---|
| 初始化 | 新页面入栈 |
| 打开新页面 | 新页面人栈 |
| 页面重定向 | 当前页面出栈,新页面入栈 |
| 页面返回 | 页面不断出栈,直到返回目标页 |
| Tab切换 | 页面全部出栈,只留下新的Tab页面 |
| 重加载 | 页面全部出栈,只留下新的页面 |
Router
页面路由器对象。可以通过 this.pageRouter 或 this.router 获得当前页面或自定义组件的路由器对象。
页面路由器有switchTab reLaunch redirectTo navigateTo navigateBack 五个方法,与 wx 对象向同名的五个方法 switchTab reLaunch redirectTo navigateTo navigateBack 功能相同;唯一的区别是,页面路由器中的方法调用时,相对路径永远相对于 this 指代的页面或自定义组件。
例如,对于下面这段示例代码:
js
// index/index.js
Page({
wxNavAction: function () {
wx.navigateTo({
url: './new-page'
})
},
routerNavAction: function () {
this.pageRouter.navigateTo({
url: './new-page'
})
}
})
页面 index/index 的 js 代码如上所示。如果此时已经跳转到了一个新页面 pack/index ,然后才调用到上面的 wxNavAction 方法,跳转的新页面路径将是 pack/new-page ;而如果调用的是 routerNavAction 方法,跳转的新页面路径仍然是 index/new-page 。
换而言之, this.pageRouter 获得的路由器对象具有更好的基路径稳定性。通常情况下,使用 this.pageRouter.navigateTo 代替 wx.navigateTo 是更优的。
开发者可以使用getCurrentPages()函数获取当前页面栈,以数组形式按栈的顺序给出,修改页面栈会导致路由和页面状态发生错误。路由方式与生命周期函数的对应关系如下表所示。
| 路由方式 | 触发时机 | 路由前页面调用函数 | 路由后页面调用函数 |
|---|---|---|---|
| 初始化 | 小程序打开的第一个页面 | onLoad()、onShow() | |
| 打开新页面 | 调用wx.navigateTo或使用组件<navigatoropen-type="navigateTo/> |
onHide() | onLoad()、onShow() |
| 页面重定向 | 调用wx.redirectTo或使用组件<navigatoropen-type="redirectTo/> |
onUnload() | onLoad()、onShow() |
| 页面返回 | 调用wx.navigateBack或使用组件<navigatoropen-type="navigateBack/>用户按左上角返回按钮 |
onUnload() | onShow() |
| Tab切换 | 调用wx.switchTab或使用组件<navigatoropen-type="switchTab/> |
||
| 重启动 | 调用wx.reLaunch或使用组件<navigatoropen-type="reLaunch/> |
onUnload() | onLoad()、onShow() |
假设A、B页面为tabBar页面,C页面是从A页面打开的页面,D页面是从C页面打开的页面,Tab切换对应的生命周期如表所示。
| 当前页面 | 路由后页面 | 触发的生命周期(按顺序) |
|---|---|---|
| A | A | 无 |
| A | B | A.onHide()、B.onLoad()、B.onShow() |
| A | B(再次打开) | A.onHide()B.onShow() |
| C | A | C.onUnload()A.onShow() |
| C | B | C.onUnload()、B.onLoad()、B.onShow() |
| D | B | D.onUnload)、C.onUnload()、B.onLoad)、B.onShow() |
| D(从转发进入) | A | D.onUnload()、A.onLoad()、A.onShow() |
| D(从转发进入) | B | D.onUnload()、B.onLoad()、B.onShow() |
路由方式存在不同点:navigateTo和redirectTo只能打开非tabBar页面;switchTab只能打开tabBar页面;reLaunch可以打开任意页面;调用页面路由传递的参数可以在目标页面的onLoad中获取。
示例:
pages/pageturn/pageturnn.js
js
Page({
topageone() {
wx.navigateTo({
url: '/pages/pageone/pageone'
})
},
topagetwo() {
wx.redirectTo({
url: '/pages/pagetwo/pagetwo'
})
},
totabpage() {
wx.switchTab({
url: '/pages/index/index'
})
}
})
pages/pageturn/pageturn.wxss
css
view{
margin: 100rpx;
border: 1px solid gray;
}
pages/pageturn/pageturn.wxml
html
<view bind:tap="topageone">1. 点击这里通过wx.navigateTo进行页面跳转</view>
<view bind:tap="topagetwo">2. 点击这里通过wx.redirectTo进行页面跳转</view>
<view bind:tap="totabpage">3. 点击这里通过wx.switchTab进行tabBar页面跳转</view>




本例创建4个页面,pageturn页面是项目首页,pageone、pagetwo和index页面是pageturn页面中3种不同的跳转方式的着陆页。在pageturn...wxml文件中放置3组<view>组件,并且分别绑定了点击事件topageone()函数、topagetwo()函数和switchTab()函数,而这3个点击事件使用了小程序的3种不同的跳转接口wx.navigate To()、wx.redirectTo()和wx.switchTab()。
图2页面由wx.navigateTo()方式跳转而来,着陆页左上角的返回图标可以返回上一页;图3页面由wx.redirectTo()方式跳转而来,着陆页左上角没有返回图标;图4页面由wx.switchTab()方式跳转而来,着陆页页面为tabBar页面。
2.5、页面间传递参数
在页面跳转中,当前页可将数据传递到着陆页。示例代码如下:
js
//A页面跳转代码
goB(){
wx.navigateTo({
url: '/pages/B/index?id=value',
})
},
//B页面接收代码
onLoad: function (options) {
console.log('id', options.id)
}
示例:
transfervalue.wxml:
html
<view bind:tap="toreceive">
点击这里进行页面跳转,传递参数的数值为:{{number}}
</view>
transfervalue.js
js
Page({
data: {
number: 10
},
toreceive() {
wx.navigateTo({
url: '/pages/receive/receive?id=' + this.data.number
})
}
})
receive.wxml
html
<view>
上一个页面传递过来的数值为:{{receiveData}}
</view>
receive.js
js
Page({
data: {
receiveData: null
},
onLoad(options) {
var receiveData = options.id;//接收参数赋值
this.setData({
receiveData: receiveData
})
}
})


三、模块化
模块化是软件工程中的一个重要概念。模块化程序设计是指在进行程序设计时将一个大程序按照功能划分为若干小程序模块,每个小程序模块完成一个确定的功能,并在这些模
块之间建立必要的联系,通过模块的互相协作完成整个功能的程序设计方法。使用模块化思想可以有效提高软件开发和维护的效率,小程序开发也需要遵循这一原则。
根据软件工程"耦合"和"内聚"的要求,小程序开发过程中可以把一些内聚性不强的功能代码写在外部JS文件中,小程序提供了module.exports和exports接口对外部JS代码进行定义和引用。exports是module.exports的一个引用,因此在模块中随意更改exports的指向会造成未知的错误,所以更推荐开发者采用module.exports来定义模块接口。
公共JS文件common.js中定义公用函数的示例代码如下:
js
function sayHello(name) {
console.log(`Hello ${name}!`)
}
function sayGoodbye(name) {
console.log(`Goodbye ${name}!`)
}
module.exports.sayHello = sayHello
module.exports.sayGoodbye = sayGoodbye
页面JS文件中调用公共函数示例:
js
var common = require("../../utils/common.js")
Page({
onLoad(options) {
console.log("page执行onLoad函数")
common.sayHello('MINA')
common.sayGoodbye('MINA')
}
})
3.1、require
引入模块。返回模块通过 module.exports 或 exports 暴露的接口。需要引入其他分包的模块的时候,可以通过配置 callback 回调函数来异步获取指定模块。异步获取失败的时候,将会触发 error 回调函数。
| 名称 | 类型 | 必填 | 说明 |
|---|---|---|---|
path |
string | 是 | 需要引入模块文件相对于当前文件的相对路径,或npm模块名,或npm模块路径。默认不支持绝对路径,可通过配置 resolveAlias 自定义路径映射。 |
callback |
function | 否 | 异步加载成功回调函数,该回调函数参数为成功加载的模块。 |
error |
function | 否 | 异步加载失败回调函数,该回调函数参数为错误信息和模块名。 |
require.async 链式调用
js
require
.async('path/to/mod')
.then((mod) => {
console.log(mod)
})
.catch(({ errMsg, mod }) => {
console.error(`path: ${mod}, ${errMsg}`)
})
示例:同一包内调用:
js
// common.js
function sayHello(name) {
console.log(`Hello ${name} !`)
}
function sayGoodbye(name) {
console.log(`Goodbye ${name} !`)
}
module.exports.sayHello = sayHello
exports.sayGoodbye = sayGoodbye
js
var common = require('common.js')
Page({
helloMINA: function() {
common.sayHello('MINA')
},
goodbyeMINA: function() {
common.sayGoodbye('MINA')
}
})
示例:跨分包异步调用:
js
// subpackage/common.js 分包 common 文件
export const sayHello = () => console.log("hello")
js
// pages/index.js 主包页面
let common;
require('../../subpackage/common.js', (mod) => {
common = mod
}, ({ errMsg, mod }) => {
console.error(`path: ${mod}, ${errMsg}`)
})
Page({
sayHello() {
common && common.sayHello()
}
})
3.2、module
当前模块对象
| 属性 | 类型 | 说明 |
|---|---|---|
exports |
Object | 模块向外暴露的对象,使用require引用该模块时可以获取 |
js
// common.js
function sayHello(name) {
console.log(`Hello ${name} !`)
}
function sayGoodbye(name) {
console.log(`Goodbye ${name} !`)
}
module.exports.sayHello = sayHello
exports.sayGoodbye = sayGoodbye
3.3、exports
module.exports 的引用
js
// common.js
function sayHello(name) {
console.log(`Hello ${name} !`)
}
function sayGoodbye(name) {
console.log(`Goodbye ${name} !`)
}
module.exports.sayHello = sayHello
exports.sayGoodbye = sayGoodbye
3.4、requirePlugin
引入插件。返回插件通过 main 暴露的接口。
| 名称 | 类型 | 说明 |
|---|---|---|
module |
string | 需要引入的插件的 alias。基础库 2.14.0 起,也可以是插件的 AppID |
js
var myPluginInterface = requirePlugin('myPlugin');
myPluginInterface.hello();
var myWorld = myPluginInterface.world;
js
var myPluginInterface = requirePlugin('wxIDxxxxxxxxxx'); // 2.14.0 起
3.5、requireMiniProgram
插件引入当前使用者小程序。返回使用者小程序通过插件配置中 export 暴露的接口。参考 使用插件 - 导出到插件该接口仅在插件中存在。
js
// in plugin
var mp = requireMiniProgram()
console.log(mp.whoami) // 'Wechat MiniProgram'
四、页面自定义事件函数
4.1、事件的定义和特点
事件是视图层到逻辑层的通信方式,特点如下:
- 事件可以将用户行为反馈到逻辑层进行处理;
- 事件可以绑定在组件上,当达到触发事件,就会执行逻辑层中对应的事件处理函数;
- 事件对象可以携带额外信息,如id、dataset、touches。
4.2、事件的使用方式
事件的使用需要在WXML页面中某一个组件绑定事件函数名,在JS文件中的page中自定义相应的事件处理函数。
WMXL绑定事件的示例代码如下:
html
<button id="buttonTest" bindtap="tapName">我是按钮组件</button>
JS文件中定义事件的示例代码如下:
js
Page({
tapName: function(){
console.log("你点击了按钮)
}
})
4.3、事件的分类
- 冒泡事件:当一个组件上的事件被触发后,该事件会向父节点传递。
- 非冒泡事件:当一个组件上的事件被触发后,该事件不会向父节点传递。
| 类型 | 说明 |
|---|---|
touchstart |
手指触摸动作开始 |
touchmove |
手指触摸后移动 |
touchcancel |
手指触摸动作被打断,如来电提醒,弹窗 |
touchend |
手指触摸动作结束 |
tap |
手指触摸后马上离开 |
longpress |
手指触摸后,超过350ms再离开,如果指定了事件回调函数并触发了这个事件,tap事件将不被触发 |
longtap |
手指触摸后,超过350ms再离开(推荐使用longpress事件替代) |
transitionend |
会在wxss transition或wx.createAnimation动画结束后触发 |
animationstart |
会在一个wxss animation动画开始时触发 |
annimationiteration |
会在一个wxss animation一次迭代结束时触发 |
animationend |
会在一个wxss animation动画完成时触发 |
touchforcechange |
在支持3D Touch的IPhone设备,重按时会触发 |
除表之外的其他组件自定义事件如无特殊声明都是非冒泡事件,如form的submit事件、input的input事件、scroll--view的scroll事件等。
4.4、事件绑定和冒泡
事件绑定的写法同组件的属性,用key="value"的形式。
key以bind或catch开头,跟上事件的类型,如bindtap、catchtouchstart。从基础库版本1.5.0起,在非原生组件中,bind和catch后可以紧接一个冒号,其含义不变,如
bind:tap,catch:touchstartvalue是一个字符串,需要在对应的Page中定义同名的函数,否则当触发事件的时候会报错。bind事件绑定不会阻止冒泡事件向上冒泡;catch事件绑定可以阻止冒泡事件向上冒泡。
例如有3个<view>组件,其中A包含B、B包含C,示例代码如下:
html
<view id="outer" bindtap="tap1">
外层容器A
<view id="middle" carchtap="tap2">
中间容器B
<view id="inner" bindtap="tap3">
内层容器C
</view>
</view>
</view>
当点击容器C时会触发tap3事件,由于tap3是bindtap类型的冒泡事件,所以其父节点容器B的tap2事件会被触发,而容器B的tap2事件是catchtap类型,catchtap类型阻止事件继续向上冒泡,即容器A的tapl事件不会被触发。当点击容器B和容器A时,只能分别触发自己的tap2和tapl事件。
4.5、事件对象
如无特殊说明,当组件触发事件时,逻辑层绑定该事件的处理函数会收到一个事件对象。事件对象分为基础事件(BaseEvent)、自定义事件(CustomEvent)和触摸事件
(TouchEvent)。基础事件(BaseEvent)对象属性如表所示。
基础事件(BaseEvent)对象属性:
| 属性 | 类型 | 说明 |
|---|---|---|
type |
string | 事件类型 |
timeStamp |
integer | 事件生成时的时间戳 |
target |
object | 触发事件的组件的一些属性值集合 |
currentTarget |
object | 当前组件的一些属性值集合 |
自定义事件(CustomEvent)对象属性:
| 属性 | 类型 | 说明 |
|---|---|---|
detail |
object | 额外的信息 |
触摸事件(TouchEvent)对象属性:
| 属性 | 类型 | 说明 |
|---|---|---|
touches |
array | 触摸事件,当前停留在屏幕中触摸点信息的数组 |
changedTouches |
array | 触摸事件,当前变化的触摸点信息的数组 |
注意:canvas画布组件中的触摸事件不可冒泡,因此没有currentTarget。
各种事件属性的含义如下。
- type:代表事件的类型。
- timeStamp:页面打开到触发事件所经过的毫秒数。
- target:触发事件的源组件。
- currentTarget:事件绑定的当前组件。
- detail:自定义事件所携带的数据,如表单组件的提交事件会携带用户的输入,媒体的错误事件会携带错误信息。
- touches:是一个数组,每个元素为一个Touch对象,表示当前停留在屏幕上的触摸点。
- changedTouches:数据格式同touches,表示有变化的触摸点,如从无变有(touchstart),位置变化(touchmove),从有变无(touchend、touchcancel)。
基础事件对象中的target和currentTarget属性包含的参数相同,如表所示。
| 属性 | 类型 | 说明 |
|---|---|---|
id |
string | 当前组件的id |
tagName |
string | 当前组件的类型 |
dataset |
object | 当前组件上由data开头的自定义属性组成的集合 |
- dataset:在组件节点中可以附加一些自定义数据。这样,在事件中可以获取这些自定义的节点数据,用于事件的逻辑处理。在WXML中,这些自定义数据以data-开头,多个单词由连字符连接。这种写法中,连字符写法会转换成驼峰写法,而大写字符会自动转成小写字符。例如:
- data-element-type会转换为event.currentTarget.dataset.elementType;
- data-elementType会转换为event.current Target.dataset.elementtype.
touches对象属性如下表:
| 属性 | 类型 | 说明 |
|---|---|---|
identifier |
number | 触摸点的标识符 |
pageX,pageY |
number | 距离文档左上角的距离,文档的左上角为原点,横向为X轴,纵向为Y轴 |
clientX,clientY |
number | 距离页面可显示区域(屏幕除去导航条)左上角的距离,横向为X轴,纵向为Y轴 |
canvas画布组件的触摸事件中携带的touches是CanvasTouch数组,属性如表所示。
| 属性 | 类型 | 说明 |
|---|---|---|
identifier |
number | 触摸点的标识符 |
x,y |
number | 距离Canvas左上角的距离,Canvas的左上角为原点,横向为X轴,纵向为Y轴 |