小程序基础知识

发布时间 2023-10-09 17:21:29作者: songxia777

1. 简介和概述

小程序是一种全新的连接用户与服务的方式,它可以在微信内被便捷地获取和传播,同时具有出色的使用体验

  • 2017年度百度百科十大热词之一
  • 微信小程序,简称小程序,英文名Mini Program,是一种不需要下载安装即可使用的应用 ( 张小龙对其的定义是无需安装,用完即走,实际上是需要安装的,只不过小程序的体积特别小, 下载速度很快,用户感觉不到下载的过程 )
  • 小程序刚发布的时候要求压缩包的体积不能大于1M,,否则无法通过,在2017年4月做了改进,由原来的1M提升到2M;
  • 2017年1月9日0点,万众瞩目的微信第一批小程序正式低调上线

1.1 小程序可以做什么

  • 同App进行互补,提供同app类似的功能,比app操作更加简洁的轻应用
  • 通过扫一扫或者在微信搜索即可下载
  • 用户使用频率不高,但又不得不用的功能软件,目前看来小程序是首选
  • 连接线上线下
  • 开发门槛低, 成本低

1.2 微信 Web 资源离线存储

微信 Web 资源离线存储是面向 Web 开发者提供的基于微信内的 Web 加速方案。

通过使用微信离线存储,Web 开发者可借助微信提供的资源存储能力,直接从微信本地加载 Web 资源而不需要再从服务端拉取,从而减少网页加载时间,为微信用户提供更优质的网页浏览体验。每个公众号下所有 Web App 累计最多可缓存 5M 的资源

1.3 小程序与普通网页开发的区别

小程序的主要开发语言是 JavaScript ,小程序的开发同普通的网页开发相比有很大的相似性。对于前端开发者而言,从网页开发迁移到小程序的开发成本并不高,但是二者还是有些许区别的。

网页开发渲染线程和脚本线程是互斥的,这也是为什么长时间的脚本运行可能会导致页面失去响应,而在小程序中,二者是分开的,分别运行在不同的线程中。网页开发者可以使用到各种浏览器暴露出来的 DOM API,进行 DOM 选中和操作;小程序的逻辑层和渲染层是分开的,逻辑层运行在 JSCore 中,并没有一个完整浏览器对象,因而缺少相关的DOM API和BOM API

同时 JSCore 的环境同 NodeJS 环境也是不尽相同,所以一些 NPM 的包在小程序中也是无法运行的。

网页开发者需要面对的环境是各式各样的浏览器,PC 端需要面对 IE、Chrome、QQ浏览器等,在移动端需要面对Safari、Chrome以及 iOS、Android 系统中的各式 WebView 。而小程序开发过程中需要面对的是两大操作系统 iOS 和 Android 的微信客户端,以及用于辅助开发的小程序开发者工具,小程序中三大运行环境也是有所区别的

image

1.4 小程序的代码构成

  • .json 后缀的 JSON 配置文件
  • .wxml 后缀的 WXML 模板文件
  • .wxss 后缀的 WXSS 样式文件
  • .js 后缀的 JS 脚本逻辑文件

2. 小程序配置

2.1 全局配置 app.json

app.json 是当前小程序的全局配置,包括了小程序的所有页面路径、界面表现、网络超时时间、底部 tab 等;一般小程序配置如下,[更多配置项参考这里]

image

常见的配置项的含义:

字段 含义
pages 用于描述当前小程序所有页面路径,这是为了让微信客户端知道当前你的小程序页面定义在哪个目录
window 定义小程序所有页面的顶部背景颜色,文字颜色定义等
tabBar 底部tab栏的表现
networkTimeout 网络超时时间
debug 是否开启 debug 模式,默认关闭
navigateToMiniProgramAppIdList 需要跳转的小程序列表(打开另外一个小程序),详见 wx.navigateToMiniProgram

2.2 页面配置 page.json

每一个小程序页面也可以使用同名 .json 文件来对本页面的窗口表现进行配置,页面中配置项会覆盖 app.json 的 window 中相同的配置项

如果你整个小程序的风格是蓝色调,那么你可以在 app.json 里边声明顶部颜色是蓝色即可。实际情况可能不是这样,可能你小程序里边的每个页面都有不一样的色调来区分不同功能模块,因此我们提供了 page.json,让开发者可以独立定义每个页面的一些属性,例如刚刚说的顶部颜色、是否允许下拉刷新等等,各配置项详细请参考

image

常见的配置项的含义

字段 含义
navigationBarBackgroundColor 导航栏背景颜色,如 #000000
navigationBarTextStyle 导航栏标题颜色,仅支持 black / white
navigationBatTitleText 导航栏标题文字内容
backgroundColor 窗口的背景色
backgroundTextStyle 下拉 loading 的样式,仅支持 dark / light

2.3 工具配置 project.config.json

通常大家在使用一个工具的时候,都会针对各自喜好做一些个性化配置,例如界面颜色、编译配置等等,当你换了另外一台电脑重新安装工具的时候,你还要重新配置。

考虑到这点,小程序开发者工具在每个项目的根目录都会生成一个 project.config.json,你在工具上做的任何配置都会写入到这个文件,当你重新安装工具或者换电脑工作时,你只要载入同一个项目的代码包,开发者工具就自动会帮你恢复到当时你开发项目时的个性化配置,其中会包括编辑器的颜色、代码上传时自动压缩等等一系列选项.具体请参考开发工具的配置

2.4 sitemap 配置

微信现已开放小程序内搜索,开发者可以通过 sitemap.json 配置,或者管理后台页面收录开关来配置其小程序页面是否允许微信索引

当开发者允许微信索引时,微信会通过爬虫的形式,为小程序的页面内容建立索引。当用户的搜索词条触发该索引时,小程序的页面将可能展示在搜索结果中。

爬虫访问小程序内页面时,会携带特定的 user-agent:mpcrawler 及场景值:1129。需要注意的是,若小程序爬虫发现的页面数据和真实用户的呈现不一致,那么该页面将不会进入索引中

具体详细可以参考这里

2.5 JSON配置的一些注意项

  • JSON文件都是被包裹在一个大括号中 {},通过key-value的方式来表达数据
  • JSON的Key必须包裹在一个 双引号
  • JSON 文件中无法使用注释,试图添加注释将会引发报错
  • JSON的值只能是以下几种数据格式,其他任何格式都会触发报错
    • 数字,包含浮点数和整数
    • 字符串,需要包裹在双引号中
    • Bool值,true 或者 false
    • 数组,需要包裹在方括号中 []
    • 对象,需要包裹在大括号中 {}
    • null

3. WXML 模板

WXML(WeiXin Markup Language)是框架设计的一套标签语言,结合基础组件事件系统,可以构建出页面的结构

网页编程采用的是 HTML + CSS + JS 这样的组合,其中 HTML 是用来描述当前这个页面的结构,CSS 用来描述页面的样子,JS 通常是用来处理这个页面和用户的交互,而在小程序中,WXML 充当的就是类似 HTML 的角色

例如下图中的wxml模板结构

image

小程序的 WXML 用的标签是 view, button, text 等等,这些标签就是小程序给开发者包装好的基本能力

除此之外还提供了地图、视频、音频等等组件能力

3.1 数据绑定

WXML 中的动态数据均来自对应 Page 的 data

3.1.1 简单的绑定

数据绑定使用 Mustache 语法(双大括号)将变量包起来,可以作用于

内容
<view> {{ message }} </view>

Page({
  data: {
    message: 'Hello MINA!'
  }
})

组件属性(需要在双引号 "" 之内 )
<view id="item-{{id}}"> </view>

Page({
  data: {
    id: 0
  }
})

控制属性(需要在双引号之内)
<view wx:if="{{condition}}"> </view>

Page({
  data: {
    condition: true
  }
})

关键字(需要在双引号之内)

true:boolean 类型的 true,代表真值。

false: boolean 类型的 false,代表假值。

<checkbox checked="{{false}}"> </checkbox>

不要直接写 checked="false",其计算结果是一个字符串,转成 boolean 类型后代表真值

3.1.2 常见运算

可以在 {{}} 内进行简单的运算,支持的有如下几种方式

三元运算
<view hidden="{{flag ? true : false}}"> Hidden </view>
算数运算
<view> {{a + b}} + {{c}} + d </view>
逻辑判断
<view wx:if="{{length > 5}}"> </view>
字符串运算
<view>{{"hello" + name}}</view>

Page({
  data:{
    name: 'MINA'
  }
})

数据路径运算
<view>{{object.key}} {{array[0]}}</view>

Page({
  data: {
    object: {
      key: 'Hello '
    },
    array: ['MINA']
  }
})

3.1.3 组合

也可以在 Mustache 内直接进行组合,构成新的对象或者数组

数组
<view wx:for="{{[zero, 1, 2, 3, 4]}}"> {{item}} </view>

Page({
  data: {
    zero: 0
  }
})

// 最终组合成数组[0, 1, 2, 3, 4]

对象
<template is="objectCombine" data="{{for: a, bar: b}}"></template>

Page({
  data: {
    a: 1,
    b: 2
  }
})

// 最终组合成的对象是 {for: 1, bar: 2}

也可以用扩展运算符 ... 来将一个对象展开

<template is="objectCombine" data="{{...obj1, ...obj2, e: 5}}"></template>

Page({
  data: {
    obj1: {
      a: 1,
      b: 2
    },
    obj2: {
      c: 3,
      d: 4
    }
  }
})

// 最终组合成的对象是 {a: 1, b: 2, c: 3, d: 4, e: 5}

如果对象的 key 和 value 相同,也可以间接地表达

<template is="objectCombine" data="{{foo, bar}}"></template>

Page({
  data: {
    foo: 'my-foo',
    bar: 'my-bar'
  }
})

// 最终组合成的对象是 {foo: 'my-foo', bar:'my-bar'}

注意: 花括号和引号之间如果有空格,将最终被解析成为字符串

<view wx:for="{{[1,2,3]}} ">
  {{item}}
</view>

等价于:

<view wx:for="{{[1,2,3] + ' '}}">
  {{item}}
</view>

3.2 列表渲染 wx:for

在组件上使用 wx:for 控制属性绑定一个数组,即可使用数组中各项的数据重复渲染该组件

默认数组的当前项的下标变量名默认为 index,数组当前项的变量名默认为 item

<view wx:for="{{array}}">
  {{index}}: {{item.message}}
</view>

Page({
  data: {
    array: [{
      message: 'foo',
    }, {
      message: 'bar'
    }]
  }
})

指定变量名和下标

使用 wx:for-item 可以指定数组当前元素的变量名

使用 wx:for-index 可以指定数组当前下标的变量名

<view wx:for="{{array}}" wx:for-index="idx" wx:for-item="itemName">
  {{idx}}: {{itemName.message}}
</view>

block wx:for

类似 block wx:if,也可以将 wx:for 用在<block/>标签上,以渲染一个包含多节点的结构块。例如

<block wx:for="{{[1, 2, 3]}}">
  <view> {{index}}: </view>
  <view> {{item}} </view>
</block>

wx:key

如果列表中项目的位置会动态改变或者有新的项目添加到列表中,并且希望列表中的项目保持自己的特征和状态(如 input 中的输入内容,switch 的选中状态),需要使用 wx:key 来指定列表中项目的唯一的标识符

wx:key 的值以两种形式提供:

  1. 字符串,代表在 for 循环的 array 中 item 的某个 property,该 property 的值需要是列表中唯一的字符串或数字,且不能动态改变。
  2. 保留关键字 *this 代表在 for 循环中的 item 本身,这种表示需要 item 本身是一个唯一的字符串或者数字

当数据改变触发渲染层重新渲染的时候,会校正带有 key 的组件,框架会确保他们被重新排序,而不是重新创建,以确保使组件保持自身的状态,并且提高列表渲染时的效率

如不提供 wx:key,会报一个 warning, 如果明确知道该列表是静态,或者不必关注其顺序,可以选择忽略

3.3 条件渲染 wx:if

在框架中,使用 wx:if="" 来判断是否需要渲染该代码块

view wx:if="{{condition}}"> True </view>

// 也可以用 wx:elif 和 wx:else 来添加一个 else 块
<view wx:if="{{length > 5}}"> 1 </view>
<view wx:elif="{{length > 2}}"> 2 </view>
<view wx:else> 3 </view>

block wx:if

因为 wx:if 是一个控制属性,需要将它添加到一个标签上。如果要一次性判断多个组件标签,可以使用一个 <block/> 标签将多个组件包装起来,并在上边使用 wx:if 控制属性

<block wx:if="{{true}}">
  <view> view1 </view>
  <view> view2 </view>
</block>

注意: <block/> 并不是一个组件,它仅仅是一个包装元素,不会在页面中做任何渲染,只接受控制属性

wx:if 和的 hidden 异同

1、 因为 wx:if 之中的模板也可能包含数据绑定,所以当 wx:if 的条件值切换时,框架有一个局部渲染的过程,因为它会确保条件块在切换时销毁或重新渲染。

同时 wx:if 也是惰性的,如果在初始渲染条件为 false,框架什么也不做,在条件第一次变成真的时候才开始局部渲染。

2、 hidden 就简单的多,组件始终会被渲染,只是简单的控制显示与隐藏。

3、 一般来说,wx:if 有更高的切换消耗而 hidden 有更高的初始渲染消耗。因此,如果需要频繁切换的情景下,用 hidden 更好,如果在运行时条件不大可能改变则 wx:if 较好

3.4 模板

WXML提供模板(template),可以在模板中定义代码片段,然后在不同的地方调用

3.4.1 定义模板

使用 name 属性,作为模板的名字。然后在<template/>内定义代码片段,如:

<template name="msgItem">
  <view>
    <text> {{index}}: {{msg}} </text>
    <text> Time: {{time}} </text>
  </view>
</template>

3.4.2 使用模板

使用 is 属性,声明需要的使用的模板,然后将模板所需要的 data 传入,如:

<import src="item.wxml"/> // 使用之前一定要引入模板
<template is="msgItem" data="{{...item}}"/>

Page({
  data: {
    item: {
      index: 0,
      msg: 'this is a template',
      time: '2016-09-15'
    }
  }
})

is 属性可以使用 Mustache 语法,来动态决定具体需要渲染哪个模板

<template name="odd">
  <view> odd </view>
</template>
<template name="even">
  <view> even </view>
</template>

<block wx:for="{{[1, 2, 3, 4, 5]}}">
  <template is="{{item % 2 == 0 ? 'even' : 'odd'}}"/>
</block>

3.4.3 模板的作用域

模板拥有自己的作用域,只能使用 data 传入的数据以及模板定义文件中定义的 <wxs /> 模块

3.5 引用

WXML 提供两种文件引用方式importinclude

3.5.1 import

import可以在该文件中使用目标文件定义的template,如:

在 item.wxml 中定义了一个叫itemtemplate

<!-- item.wxml -->
<template name="item">
  <text>{{text}}</text>
</template>

在 index.wxml 中引用了 item.wxml,就可以使用item模板

<import src="item.wxml"/>
<template is="item" data="{{text: 'forbar'}}"/>

import 的作用域

import 有作用域的概念,即只会 import 目标文件中定义的 template,而不会 import 目标文件 import 的 template。

如:C import B,B import A,在C中可以使用B定义的template,在B中可以使用A定义的template,但是C不能使用A定义的template

3.5.2 include

include 可以将目标文件除了 <template/> <wxs/> 外的整个代码引入,相当于是拷贝到 include 位置,如

<!-- index.wxml -->
<include src="header.wxml"/>
<view> body </view>
<include src="footer.wxml"/>

4. WXSS 样式

4.1 简介和概述

WXSS (WeiXin Style Sheets)是一套样式语言,用于描述 WXML 的组件样式

WXSS 用来决定 WXML 的组件应该怎么显示

为了适应广大的前端开发,WXSS 具有 CSS 大部分的特性,小程序在 WXSS 也做了一些扩充和修改。

  1. 新增了尺寸单位。在写 CSS 样式时,开发者需要考虑到手机设备的屏幕会有不同的宽度和设备像素比,采用一些技巧来换算一些像素单位。WXSS 在底层支持新的尺寸单位 rpx ,开发者可以免去换算的烦恼,只要交给小程序底层来换算即可,由于换算采用的浮点数运算,所以运算结果会和预期结果有一点点偏差。
  2. 提供了全局的样式和局部样式。和前边 app.json, page.json 的概念相同,你可以写一个 app.wxss 作为全局样式,会作用于当前小程序的所有页面,局部页面样式 page.wxss 仅对当前页面生效。
  3. 此外 WXSS 仅支持部分 CSS 选择器

4.2 尺寸单位: rpx

rpx(responsive pixel): 可以根据屏幕宽度进行自适应。规定屏幕宽为750rpx。如在 iPhone6 上,屏幕宽度为375px,共有750个物理像素,则750rpx = 375px = 750物理像素,1rpx = 0.5px = 1物理像素

开发微信小程序时设计师可以用 iPhone6 作为视觉稿的标准;  
在较小的屏幕上不可避免的会有一些毛刺,请在开发时尽量避免这种情况

image

4.3 样式导入

@import语句可以导入外联样式表,@import后跟需要导入的外联样式表的相对路径,用;表示语句结束

image

4.4 内联样式

框架组件上支持使用 style、class 属性来控制组件的样式

style:静态的样式统一写到 class 中。style 接收动态的样式,在运行时会进行解析,请尽量避免将静态的样式写进 style 中,以免影响渲染速度

<view style="color:{{color}};" />

class:用于指定样式规则,其属性值是样式规则中类选择器名(样式类名)的集合,样式类名不需要带上.,样式类名之间用空格分隔

<view class="normal_view" />

4.5 选择器

注意:小程序仅支持部分css选择器,目前支持的选择器有:

image

4.6 全局样式与局部样式

定义在 app.wxss 中的样式为全局样式,作用于每一个页面
在 page 的 wxss 文件中定义的样式为局部样式,只作用在对应的页面,并会覆盖 app.wxss 中相同的选择器

5. WXS:WeiXin Script

WXS是小程序的一套脚本语言,结合 WXML,可以构建出页面的结构

WXS 与 JavaScript 是不同的语言,有自己的语法,并不和 JavaScript 一致

5.1 WXS 模块

WXS 代码可以编写在 wxml 文件中的 <wxs> 标签内,或以 .wxs 为后缀名的文件内

5.1.1 <wxs> 标签

image

module 属性

module 属性是当前 <wxs> 标签的模块名

在单个 wxml 文件内,建议其值唯一。有重复模块名则按照先后顺序覆盖(后者覆盖前者)。不同文件之间的 wxs 模块名不会相互覆盖

src 属性

src 属性可以用来引用其他的 wxs 文件模块。

引用的时候,要注意如下几点:

  • 只能引用 .wxs 文件模块,且必须使用相对路径。
  • wxs 模块均为单例,wxs 模块在第一次被引用时,会自动初始化为单例对象。多个页面,多个地方,多次引用,使用的都是同一个 wxs 模块对象。
  • 如果一个 wxs 模块在定义之后,一直没有被引用,则该模块不会被解析与运行

方式一: 创建单独的 wxs文件,在使用的时候,引用即可

Page({
  data: {
    msg: "'hello wrold' from js",
  }
})

<!-- /pages/index/index.wxml -->

<wxs src="./../comm.wxs" module="some_comms"></wxs>
<!-- 也可以直接使用单标签闭合的写法
<wxs src="./../comm.wxs" module="some_comms" />
-->

<!-- 调用 some_comms 模块里面的 bar 函数,且参数为 some_comms 模块里面的 foo -->
<view> {{some_comms.bar(some_comms.foo)}} </view>
<!-- 调用 some_comms 模块里面的 bar 函数,且参数为 page/index/index.js 里面的 msg -->
<view> {{some_comms.bar(msg)}} </view>

方式二:可以直接写在 wxs 内部,直接引用

<wxs module="m1">
var msg = "hello world";
module.exports.message = msg;
</wxs>

<view> {{m1.message}} </view>
require关键字

.wxs模块中引用其他 wxs 文件模块,可以使用 require 函数。

引用的时候,要注意如下几点:

  • 只能引用 .wxs 文件模块,且必须使用相对路径。
  • wxs 模块均为单例,wxs 模块在第一次被引用时,会自动初始化为单例对象。多个页面,多个地方,多次引用,使用的都是同一个 wxs 模块对象。
  • 如果一个 wxs 模块在定义之后,一直没有被引用,则该模块不会被解析与运行

/pages/tools.wxs

var foo = "'hello world' from tools.wxs";
var bar = function (d) {
  return d;
}
module.exports = {
  FOO: foo,
  bar: bar,
};
module.exports.msg = "some msg";

/pages/logic.wxs

// 引用另外一个 wxs 文件
var tools = require("./tools.wxs");

console.log(tools.FOO);
console.log(tools.bar("logic.wxs"));
console.log(tools.msg);

5.1.2 模板

每一个 .wxs 文件和 <wxs> 标签都是一个单独的模块。

每个模块都有自己独立的作用域。即在一个模块里面定义的变量与函数,默认为私有的,对其他模块不可见。

一个模块要想对外暴露其内部的私有变量与函数,只能通过 module.exports 实现

module 对象

每个 wxs 模块均有一个内置的 module 对象。exports: 通过该属性,可以对外共享本模块的私有变量与函数

/pages/tools.wxs

var foo = "'hello world' from tools.wxs";
var bar = function (d) {
  return d;
}
module.exports = {
  FOO: foo,
  bar: bar,
};
module.exports.msg = "some msg";

<!-- page/index/index.wxml -->

<wxs src="./../tools.wxs" module="tools" />
<view> {{tools.msg}} </view>
<view> {{tools.bar(tools.FOO)}} </view>

5.1.3 注意事项

  • <wxs> 模块只能在定义模块的 WXML 文件中被访问到。使用 <include><import> 时,<wxs> 模块不会被引入到对应的 WXML 文件中。
  • <template> 标签中,只能使用定义该 <template> 的 WXML 文件中定义的 <wxs> 模块

5.2 变量

  • WXS 中的变量均为值的引用。
  • 没有声明的变量直接赋值使用,会被定义为全局变量。
  • 如果只声明变量而不赋值,则默认值为 undefined
  • var表现与javascript一致,会有变量提升
var foo = 1;
var bar = "hello world";
var i; // i === undefined
变量名

变量命名必须符合下面两个规则:

  • 首字符必须是:字母(a-zA-Z),下划线(_)
  • 剩余字符可以是:字母(a-zA-Z),下划线(_), 数字(0-9)
保留标识符

以下标识符不能作为变量名

delete
void
typeof

null
undefined
NaN
Infinity
var

if
else

true
false

require

this
function
arguments
return

for
while
do
break
continue
switch
case
default

5.3 注释

WXS 主要有 3 种注释的方法

<wxs module="sample">
// 方法一:单行注释

/*
   方法二:多行注释
*/

/*
   方法三:结尾注释。即从 /* 开始往后的所有 WXS 代码均被注释

var a = 1;
var b = 2;
var c = "fake";

</wxs>

5.3 数据类型

WXS 语言目前共有以下几种数据类型

number : 数值
string :字符串
boolean:布尔值
object:对象
function:函数
array : 数组
date:日期
regexp:正则

5.4 基础类库

1. console

console.log 方法用于在 console 窗口输出信息。它可以接受多个参数,将它们的结果连接起来输出

2. Math
abs
acos
asin
atan
atan2
ceil
cos
exp
floor
log
max
min
pow
random
round
sin
sqrt
tan

3. JSON
  • stringify(object): 将 object 对象转换为 JSON 字符串,并返回该字符串。
  • parse(string): 将 JSON 字符串转化成对象,并返回该对象

5.5 注意事项

  1. WXS 不依赖于运行时的基础库版本,可以在所有版本的小程序中运行。
  2. WXS 与 JavaScript 是不同的语言,有自己的语法,并不和 JavaScript 一致。
  3. WXS 的运行环境和其他 JavaScript 代码是隔离的,WXS 中不能调用其他 JavaScript 文件中定义的函数,也不能调用小程序提供的API。
  4. WXS 函数不能作为组件的事件回调。
  5. 由于运行环境的差异,在 iOS 设备上小程序内的 WXS 会比 JavaScript 代码快 2 ~ 20 倍。在 android 设备上二者运行效率无差异。

6. 事件系统

6.1 什么是事件

  • 事件是视图层到逻辑层的通讯方式。
  • 事件可以将用户的行为反馈到逻辑层进行处理。
  • 事件可以绑定在组件上,当达到触发事件,就会执行逻辑层中对应的事件处理函数。
  • 事件对象可以携带额外信息,如 id, dataset, touches

6.2 事件的定义和使用

第一步:在组件中绑定一个事件处理函数

bindtap,当用户点击该组件的时候会在该页面对应的Page中找到相应的事件处理函数

<view id="tapTest" data-hi="Weixin" bindtap="tapName"> Click me! </view>

// 方法名:tapName
// 传递参数: 通过  data-XXX=YYY 的方式

第二步:在相应的Page定义中写上相应的事件处理函数,参数是event
Page({
  tapName: function(event) {
    console.log(event)
  }
})

获取参数数据: event.currentTarget.dataset.XXX

{
  "type":"tap",
  "timeStamp":895,
  "target": {
    "id": "tapTest",
    "dataset":  {
      "hi":"Weixin"
    }
  },
  "currentTarget":  {
    "id": "tapTest",
    "dataset": {
      "hi":"Weixin"   //  这是传递过来的参数
    }
  },
  "detail": {
    "x":53,
    "y":14
  },
  "touches":[{
    "identifier":0,
    "pageX":53,
    "pageY":14,
    "clientX":53,
    "clientY":14
  }],
  "changedTouches":[{
    "identifier":0,
    "pageX":53,
    "pageY":14,
    "clientX":53,
    "clientY":14
  }]
}

6.3 使用 WXS 函数响应事件

从基础库版本2.4.4开始,支持使用WXS函数绑定事件,WXS函数接受2个参数,第一个是event,在原有的event的基础上加了event.instance对象,第二个参数是ownerInstance,和event.instance一样是一个ComponentDescriptor对象

第一步:在组件中绑定和注册事件处理的WXS函数
<wxs module="wxs" src="./test.wxs"></wxs>

<view id="tapTest" data-hi="Weixin" bindtap="{{wxs.tapName}}"> Click me! </view>

注:绑定的WXS函数必须用{{}}括起来

第二步:test.wxs文件实现tapName函数
function tapName(event, ownerInstance) {
  console.log('tap Weixin', JSON.stringify(event))
}

module.exports = {
  tapName: tapName
}

6.4 事件详解

6.4.1 事件分类

事件分为冒泡事件和非冒泡事件:

  • 冒泡事件:当一个组件上的事件被触发后,该事件会向父节点传递
  • 非冒泡事件:当一个组件上的事件被触发后,该事件不会向父节点传递

6.4.2 普通事件绑定

绑定方法:handleTap ,当用户点击这个 view ,则页面的 handleTap 会被调用

<view bindtap="handleTap">
    Click here!
</view>

事件绑定函数也可以是一个数据绑定,如:

<view bindtap="{{ handlerName }}">
    Click here!
</view>

页面的 this.data.handlerName 必须是一个字符串,指定事件处理的函数名

如果它是个空字符串,则这个绑定会失效(可以利用这个特性来暂时禁用一些事件)

6.4.3 绑定并阻止事件冒泡 :catch

bind 外,也可以用 catch 来绑定事件。与 bind 不同, catch 会阻止事件向上冒泡

<view id="outer" bindtap="handleTap1">
  outer view3
  <view id="middle" catchtap="handleTap2">
    middle view2
    <view id="inner" bindtap="handleTap3">
      inner view1
    </view>
  </view>
</view>

解释如下:
点击 inner view1 会先后调用handleTap3和handleTap2(因为tap事件会冒泡到 middle view2,而 middle view2 阻止了 tap 事件冒泡,不再向父节点传递)

点击 middle view2 会触发handleTap2,点击 outer view3 会触发handleTap1

7. 互斥事件绑定: mut-bind

自基础库版本 2.8.2 起,除 bind 和 catch 外,还可以使用 mut-bind 来绑定事件

一个 mut-bind 触发后,如果事件冒泡到其他节点上,其他节点上的 mut-bind 绑定函数不会被触发,但 bind 绑定函数和 catch 绑定函数依旧会被触发

换而言之,所有 mut-bind 是“互斥”的,只会有其中一个绑定函数被触发。同时,它完全不影响 bind 和 catch 的绑定效果

<view id="outer" mut-bind:tap="handleTap1">
  outer view3
  <view id="middle" bindtap="handleTap2">
    middle view2
    <view id="inner" mut-bind:tap="handleTap3">
      inner view1
    </view>
  </view>
</view>

解释如下:
点击 inner view1 会先后调用 handleTap3 和 handleTap2 ,点击 middle view2 会调用 handleTap2 和 handleTap1

8. 事件的捕获阶段

  • 自基础库版本 1.5.0 起,触摸类事件支持捕获阶段

  • 捕获阶段位于冒泡阶段之前,且在捕获阶段中,事件到达节点的顺序与冒泡阶段恰好相反

  • 需要在捕获阶段监听事件时,可以采用capture-bind、capture-catch关键字,后者(capture-catch)将中断捕获阶段和取消冒泡阶段

在下面的代码中,点击 inner view 会先后调用handleTap2、handleTap4、handleTap3、handleTap1

<view id="outer" bind:touchstart="handleTap1" capture-bind:touchstart="handleTap2">
  outer view
  <view id="inner" bind:touchstart="handleTap3" capture-bind:touchstart="handleTap4">
    inner view
  </view>
</view>

如果将上面代码中的第一个capture-bind改为capture-catch,将只触发handleTap2

<view id="outer" bind:touchstart="handleTap1" capture-catch:touchstart="handleTap2">
  outer view
  <view id="inner" bind:touchstart="handleTap3" capture-bind:touchstart="handleTap4">
    inner view
  </view>
</view>

9. 事件对象

如无特殊说明,当组件触发事件时,逻辑层绑定该事件的处理函数会收到一个事件对象
BaseEvent 基础事件对象属性列表:
image

CustomEvent 自定义事件对象属性列表(继承 BaseEvent):

image

TouchEvent 触摸事件对象属性列表(继承 BaseEvent)

image

属性介绍

1. type:代表事件的类型
2. timeStamp:页面打开到触发事件所经过的毫秒数
3. target:触发事件的源组件

image

4. currentTarget:事件绑定的当前组件

image

5. dataset

在组件节点中可以附加一些自定义数据。这样,在事件中可以获取这些自定义的节点数据,用于事件的逻辑处理。

在 WXML 中,这些自定义数据以 data- 开头,多个单词由连字符 - 连接。这种写法中,连字符写法会转换成驼峰写法,而大写字符会自动转成小写字符。如

  • data-element-type ,最终会呈现为 event.currentTarget.dataset.elementType
  • data-elementType ,最终会呈现为 event.currentTarget.dataset.elementtype
    如下:
<view data-alpha-beta="1" data-alphaBeta="2" bindtap="bindViewTap"> DataSet Test </view>

Page({
  bindViewTap:function(event){
    event.currentTarget.dataset.alphaBeta === 1 // - 会转为驼峰写法
    event.currentTarget.dataset.alphabeta === 2 // 大写会转为小写
  }
})
6. mark

在基础库版本 2.7.1 以上,可以使用 mark 来识别具体触发事件的 target 节点。此外, mark 还可以用于承载一些自定义数据(类似于 dataset )。

当事件触发时,事件冒泡路径上所有的 mark 会被合并,并返回给事件回调函数。(即使事件不是冒泡事件,也会 mark 。)

<view mark:myMark="last" bindtap="bindViewTap">
  <button mark:anotherMark="leaf" bindtap="bindButtonTap">按钮</button>
</view>

Page({
  bindViewTap: function(e) {
    e.mark.myMark === "last" // true
    e.mark.anotherMark === "leaf" // true
  }
})

如果按钮被点击,将触发 bindViewTap 和 bindButtonTap 两个事件,事件携带的 event.mark 将包含 myMark 和 anotherMark 两项

注意事项如下

mark 和 dataset 很相似,主要区别在于:

  • mark 会包含从触发事件的节点到根节点上所有的 mark: 属性值
  • 而 dataset 仅包含一个节点的 data- 属性值

如果存在同名的 mark ,父节点的 mark 会被子节点覆盖。
在自定义组件中接收事件时, mark 不包含自定义组件外的节点的 mark 。
不同于 dataset ,节点的 mark 不会做连字符和大小写转换

7. detail

自定义事件所携带的数据,如表单组件的提交事件会携带用户的输入,媒体的错误事件会携带错误信息,详见组件定义中各个事件的定义。

点击事件的detail 带有的 x, y 同 pageX, pageY 代表距离文档左上角的距离

8. touches

touches 是一个数组,每个元素为一个 Touch 对象(canvas 触摸事件中携带的 touches 是 CanvasTouch 数组)。 表示当前停留在屏幕上的触摸点。
image

9. changedTouches

changedTouches 数据格式同 touches。 表示有变化的触摸点,如从无变有(touchstart),位置变化(touchmove),从有变无(touchend、touchcancel)

小程序宿主环境

我们称微信客户端给小程序所提供的环境为宿主环境。小程序借助宿主环境提供的能力,可以完成许多普通网页无法完成的功能

1. 渲染层和逻辑层

小程序的运行环境分成渲染层和逻辑层,其中 WXML 模板和 WXSS 样式工作在渲染层,JS 脚本工作在逻辑层

小程序的渲染层和逻辑层分别由2个线程管理:

  • 渲染层的界面使用了WebView进行渲染
  • 逻辑层采用JsCore线程运行JS脚本

一个小程序存在多个界面,所以渲染层存在多个WebView线程,这两个线程的通信会经由微信客户端(下文中也会采用Native来代指微信客户端)做中转,逻辑层发送网络请求也经由Native转发,小程序的通信模型下图所示。

image

2. 程序与页面

微信客户端在打开小程序之前,会把整个小程序的代码包下载到本地
紧接着通过 app.json 的 pages 字段就可以知道你当前小程序的所有页面路径:

{
  "pages":[
    "pages/index/index", //首页
    "pages/logs/logs"
  ]
}

于是微信客户端就把首页的代码装载进来,通过小程序底层的一些机制,就可以渲染出这个首页。

小程序启动之后,在 app.js 定义的 App 实例的 onLaunch 回调会被执行:

App({
  onLaunch: function () {
    // 小程序启动之后 触发
  }
})

整个小程序只有一个 App 实例,是全部页面共享的

3. 组件

小程序提供了丰富的基础组件给开发者,开发者可以像搭积木一样,组合各种组件拼合成自己的小程序

  1. 就像 HTML 的 div, p 等标签一样,在小程序里边,你只需要在 WXML 写上对应的组件标签名字就可以把该组件显示在界面上,例如,你需要在界面上显示地图,你只需要这样写即可<map></map>

  2. 使用组件的时候,还可以通过属性传递值给组件,让组件可以以不同的状态去展现,例如,我们希望地图一开始的中心的经纬度是广州,那么你需要声明地图的 longitude(中心经度) 和 latitude(中心纬度)两个属性

<map longitude="广州经度" latitude="广州纬度"></map>
  1. 组件的内部行为也会通过事件的形式让开发者可以感知,例如用户点击了地图上的某个标记,你可以在 js 编写 markertap 函数来处理:
<map bindmarkertap="markertap" longitude="广州经度" latitude="广州纬度"></map>

4. API

为了让开发者可以很方便的调起微信提供的能力,例如获取用户信息、微信支付等等,小程序提供了很多 API 给开发者去使用

要获取用户的地理位置时,只需要:

wx.getLocation({
  type: 'wgs84',
  success: (res) => {
    var latitude = res.latitude // 纬度
    var longitude = res.longitude // 经度
  }
})

调用微信扫一扫能力,只需要:

wx.scanCode({
  success: (res) => {
    console.log(res)
  }
})
需要注意的是:多数 API 的回调都是异步,你需要处理好代码逻辑的异步问题

在小程序 API 有以下几种类型:事件监听API,同步API,异步API

4.1 事件监听 API

我们约定,以 on 开头的 API 用来监听某个事件是否触发,如:wx.onSocketOpen,wx.onCompassChange 等。

这类 API 接受一个回调函数作为参数,当事件触发时会调用这个回调函数,并将相关数据以参数形式传

wx.onCompassChange(function (res) {
  console.log(res.direction)
})

4.2 同步 API

我们约定,以 Sync 结尾的 API 都是同步 API, 如 wx.setStorageSync,wx.getSystemInfoSync 等。此外,也有一些其他的同步 API,如 wx.createWorker,wx.getBackgroundAudioManager 等

同步 API 的执行结果可以通过函数返回值直接获取,如果执行出错会抛出异常

try {
  wx.setStorageSync('key', 'value')
} catch (e) {
  console.error(e)
}

4.3 异步 API

大多数 API 都是异步 API,如 wx.request,wx.login 等。这类 API 接口通常都接受一个 Object 类型的参数,这个参数都支持按需指定以下字段来接收接口调用结果:

image

异步 API 的执行结果需要通过 Object 类型的参数中传入的对应回调函数获取。部分异步 API 也会有返回值,可以用来实现更丰富的功能,如 wx.request,wx.connectSocket 等

wx.login({
  success(res) {
    console.log(res.code)
  }
})

目录结构

小程序包含一个描述整体程序的 app 和多个描述各自页面的 page。

1. 一个小程序主体部分由三个文件组成,必须放在项目的根目录,如下

image

2. 一个小程序页面由四个文件组成,分别是:

image

为了方便开发者减少配置项,描述页面的四个文件必须具有相同的路径与文件名。