小程序基础

1、生命周期

小程序生命周期和全局变量

App({
  onLaunch: function(options) {},
  onShow: function(options) {},
  onHide: function() {},
  onError: function(msg) {},
  globalData: 'I am global data'
})


onLaunch:初次进入小程序的时候,微信客户端初始化好宿主环境,同时从网络下载或者从本地缓存中拿到小程序的代码包,把它注入到宿主环境,初始化完毕后,微信客户端就会给App实例派发onLaunch事件,App构造器参数所定义的onLaunch方法会被调用,全局只触发一次。
onShow:当再次回到微信或者再次打开小程序时,微信客户端会把“后台”的小程序唤醒,小程序进入前台状态,触发App构造器参数所定义的onShow方法。
在微信客户端中打开小程序有很多途径:从群聊会话里打开,从小程序列表中打开,通过微信扫一扫二维码打开,从另外一个小程序打开当前小程序等,针对不同途径的打开方式,小程序有时需要做不同的业务处理,所以微信客户端会把打开方式带给onLaunchonShow的调用参数options
onHide:进入小程序之后,点击右上角的关闭,或者按手机设备的Home键离开小程序,此时小程序并没有被直接销毁,而是小程序进入后台状态,此时触发App构造器参数所定义的onHide方法。
App的生命周期是由微信客户端根据用户操作主动触发的。为了避免程序上的混乱,避免从其他代码里主动调用App实例的生命周期函数。

当需要使用全局变量时,通过使用全局函数 getApp() 获取全局的实例,并设置相关属性值,来达到设置全局变量的目的

var global = getApp()
global.globalValue = 'globalValue' // 设置全局变量
var globalValue = global.globalValue // 获取全局变量

页面生命周期

Page({
  data: { text: "This is page data." },
  onLoad: function(options) { },
  onReady: function() { },
  onShow: function() { },
  onHide: function() { },
  onUnload: function() { },
  onPullDownRefresh: function() { },
  onReachBottom: function() { },
  onShareAppMessage: function () { },
  onPageScroll: function() { }
})

组件生命周期

所有页面的脚本逻辑都跑在同一个JsCore线程,页面使用setTimeout或者setInterval的定时器,然后跳转到其他页面时,这些定时器并没有被清除,需要开发者自己在页面离开的时候进行清理。

2、wx.request接口

url参数是当前发起请求的服务器接口地址,小程序宿主环境要求request发起的网络请求必须是https协议请求,因此开发者服务器必须提供HTTPS服务的接口,同时为了保证小程序不乱用任意域名的服务,wx.request请求的域名需要在小程序管理平台进行配置,如果小程序正式版使用wx.request请求未配置的域名,在控制台会有相应的报错。
开发者服务器必须提供HTTPS服务的接口,wx.request请求的域名需要在小程序管理平台进行配置。需要留意的是GET请求的url是有长度限制的,其最大长度是1024字节,同时url上的参数需要拼接到字符串里,参数的值还需要做一次urlEncode。向服务端发送的数据超过1024字节时,就要采用HTTP POST的形式,此时传递的数据就必须要使用data参数,基于这个情况,一般建议需要传递数据时,使用data参数来传递。
小程序发出一个HTTPS网络请求,有时网络存在一些异常或者服务器存在问题,在经过一段时间后仍然没有收到网络回包,我们把这一段等待的最长时间称为请求超时时间。小程序request默认超时时间是60秒,一般来说,我们不需要这么长的一个等待时间才收到回包,可能在等待3秒后还没收到回包就需要给用户一个明确的服务不可用的提示。在小程序项目根目录里边的app.json可以指定request的超时时间。

{
  "networkTimeout": {
    "request": 3000
  }
}

3、本地缓存

小程序宿主环境会管理不同小程序的数据缓存,不同小程序的本地缓存空间是分开的,每个小程序的缓存空间上限为10MB,如果当前缓存已经达到10MB,再通过wx.setStorage写入缓存会触发fail回调。
可以利用本地缓存存储首页数据,先在onload的时候加载缓存数据,等后端请求结束之后,再替换缓存数据重新渲染,可以避免白屏

有不少开发者在发布小程序后发现正式版小程序无法正常使用,所以我们罗列一下开发者在发布前常常遗漏的点:
1、如果小程序使用到Flex布局,并且需要兼容iOS8以下系统时,请检查上传小程序包时,开发者工具是否已经开启“上传代码时样式自动补全”。
2、小程序使用的服务器接口应该走HTTPS协议,并且对应的网络域名确保已经在小程序管理平台配置好。
3、在测试阶段不要打开小程序的调试模式进行测试,因为在调试模式下,微信不会校验域名合法性,容易导致开发者误以为测试通过,导致正式版小程序因为遇到非法域名无法正常工作。
4、发布前请检查小程序使用到的网络接口已经在现网部署好,并且评估好服务器的机器负载情况。
还需要留意一点,并非全量发布之后,用户就会立即使用到最新版的小程序,这是因为微信客户端存有旧版本小程序包缓存。用户在使用小程序时会优先打开本地的小程序包,微信客户端在某些特定的时机异步去更新最新的小程序包。一般我们认为全量发布的24小时后,所有用户才会真正使用到最新版的小程序。

4、微信登录


拿到code必须5分钟之内发到后端服务器,向微信服务器换取微信用户id,否则code作废。
业务侧用户还没绑定微信侧身份时,会让用户填写业务侧的用户名密码,这两个值会和微信登录凭证一起请求开发者服务器的登录接口,此时开发者后台通过校验用户名密码就拿到了业务侧的用户身份id,通过code到微信服务器获取微信侧的用户身份openid。微信会建议开发者把这两个信息的对应关系存起来,我们把这个对应关系称之为“绑定”。
有了这个绑定信息,小程序在下次需要用户登录的时候就可以不需要输入账号密码,因为通过wx.login()获取到code之后,可以拿到用户的微信身份openid,通过绑定信息就可以查出业务侧的用户身份id,这样静默授权的登录方式显得非常便捷。

5、小程序框架

双线程模型:渲染层和逻辑层
技术选型:类似于微信js-sdk的Hybrid技术,即界面主要由成熟的 Web 技术渲染,辅之以大量的接口提供丰富的客户端原生能力
安全管控:通过沙箱环境禁止了所有浏览器相关接口,只提供纯JS的解释执行环境(JS引擎:IOS使用内置js框架,安卓使用腾讯x5内核的JsCore环境)
渲染延迟:渲染层需要等待逻辑层执行完毕才渲染;渲染层和逻辑层在与客户端交互时也存在延迟,因为注册给各层的客户端能力也在与微信主线程通信
组件运行原理:
在初始化页面时,Exparser会创建出页面根组件的一个实例,用到的其他组件也会响应创建组件实例(这是一个递归的过程)。组件创建的过程大致有以下几个要点:
1、根据组件注册信息,从组件原型上创建出组件节点的JS对象,即组件的this;
2、将组件注册信息中的data 复制一份,作为组件数据,即this.data;
3、将这份数据结合组件WXML,据此创建出Shadow Tree,由于Shadow Tree中可能引用有其他组件,因而这会递归触发其他组件创建过程;
4、将ShadowTree拼接到Composed Tree上,并生成一些缓存数据用于优化组件更新性能;
5、触发组件的created生命周期函数;
6、如果不是页面根组件,需要根据组件节点上的属性定义,来设置组件的属性值;
7、当组件实例被展示在页面上时,触发组件的attached 生命周期函数,如果Shadw Tree中有其他组件,也逐个触发它们的生命周期函数。

6、原生组件

在原生组件内部,其节点树非常简单,基本上可以认为只有一个div元素。上面这行代码在渲染层开始运行时,会经历以下几个步聚:
1、组件被创建,包括组件属性会依次赋值。
2、组件被插入到DOM树里,浏览器内核会立即计算布局,此时我们可以读取出组件相对页面的位置(x, y坐标)、宽高。
3、组件通知客户端,客户端在相同的位置上,根据宽高插入一块原生区域,之后客户端就在这块区域渲染界面
4、当位置或宽高发生变化时,组件会通知客户端做相应的调整
我们可以看出,原生组件在WebView这一层的渲染任务是很简单,只需要渲染一个占位元素,之后客户端在这块占位元素之上叠了一层原生界面。因此,原生组件的层级会比所有在WebView层渲染的普通组件要高。
引入原生组件主要有3个好处:
1、扩展Web的能力。比如像输入框组件(input, textarea)有更好地控制键盘的能力。
2、体验更好,同时也减轻WebView的渲染工作。比如像地图组件(map)这类较复杂的组件,其渲染工作不占用WebView线程,而交给更高效的客户端原生处理。
3、绕过setData、数据通信和重渲染流程,使渲染性能更好。比如像画布组件(canvas)可直接用一套丰富的绘图接口进行绘制。

这段代码可以创建WXML中对应canvas节点的context,通过调用context中的方法,在画布上绘制一个矩形。
类似于canvas,video、map等原生组件都可以创建context,context中提供的方法非常丰富,这里就不一一列举了。
原生组件脱离在WebView渲染流程外,这带来了一些限制。最主要的限制是一些CSS样式无法应用于原生组件,例如,不能在父级节点使用overflow:hidden来裁剪原生组件的显示区域;不能使用transform:rotate让原生组件产生旋转等。
开发者最为常见的问题是,原生组件会浮于页面其他组件之上(相当于拥有正无穷大的z-index值)使其它组件不能覆盖在原生组件上展示。想要解决这个问题,可以考虑使用cover-view和cover-image组件。这两个组件也是原生组件,同样是脱离WebView的渲染流程外,而原生组件之间的层级就可以按照一定的规则控制。

7、数据通信

不同组件实例间的通信有WXML属性值传递、事件系统、selectComponent和relations等方式。其中,WXML属性值传递是从父组件向子组件的基本通信方式,而事件系统是从子组件向父组件的基本通信方式。
Exparser的事件系统完全模仿Shadow DOM的事件系统。在通常的理解中,事件可以分为冒泡事件和非冒泡事件,但在ShadowDOM体系中,冒泡事件还可以划分为在Shadow Tree上冒泡的事件和在Composed Tree上冒泡的事件。如果在Shadow Tree上冒泡,则冒泡只会经过这个组件Shadow Tree上的节点,这样可以有效控制事件冒泡经过的范围。

用上面的例子来说,当在 button 上触发一个事件时:

  • 如果事件是非冒泡的,那只能在 button 上监听到事件;
  • 如果事件是在 Shadow Tree 上冒泡的,那 button 、 input-with-label 、view 可以依次监听到事件;
  • 如果事件是在 Composed Tree 上冒泡的,那 button 、 slot 、label 、 input-with-label 、 view 可以依次监听到事件。
    在自定义组件中使用triggerEvent触发事件时,可以指定事件的bubbles、composed和capturePhase属性,用于标注事件的冒泡性质。
Component({
  methods: {
    helloEvent: function() {
      this.triggerEvent('hello', {}, {
        bubbles: true,      // 这是一个冒泡事件
        composed: true,     // 这个事件在Composed Tree 上冒泡
        capturePhase: false // 这个事件没有捕获阶段
      })
    }
  }
})

小程序基础库自身也会通过这套事件系统提供一些用户事件,如tap、touchstart和form组件的submit等。其中,tap等用户触摸引发的事件是在ComposedTree上的冒泡事件,其他事件大多是非冒泡事件。

小程序与客户端通信原理

视图层组件:内置组件中有部分组件是利用到客户端原生提供的能力,这类组件基本都是前一个章节描述的原生组件。既然需要客户端原生提供的能力,那就会涉及到视图层与客户端的交互通信。这层通信机制在 iOS 和安卓系统的实现方式并不一样,iOS 是利用了WKWebView 的提供 messageHandlers 特性,而在安卓则是往 WebView 的 window 对象注入一个原生方法,最终会封装成 WeiXinJSBridge 这样一个兼容层,主要提供了调用(invoke)和监听(on)这两种方法。
实际上,在视图层与客户端的交互通信中,开发者只是间接调用的,真正调用是在组件的内部实现中。开发者插入一个原生组件,一般而言,组件运行的时候被插入到 DOM 树中,会调用客户端接口,通知客户端在哪个位置渲染一块原生界面。在后续开发者更新组件属性时,同样地,也会调用客户端提供的更新接口来更新原生界面的某些部分。
逻辑层接口:逻辑层与客户端原生通信机制与渲染层类似,不同在于,iOS平台可以往JavaScripCore框架注入一个全局的原生方法,而安卓方面则是跟渲染层一致的。
同样地,开发者也是间接地调用到与客户端原生通信的底层接口。一般我们会对逻辑层接口做层封装后才暴露给开发者,封装的细节可能是统一入参、做些参数校验、兼容各平台或版本问题等等。

更新数据通信

在数据传输时,逻辑层会执行一次JSON.stringify来去除掉setData数据中不可传输的部分,之后将数据发送给视图层。同时,逻辑层还会将setData所设置的数据字段与data合并,使开发者可以用this.data读取到变更后的数据。
因此,为了提升数据更新的性能,开发者在执行setData调用时,最好遵循以下原则:
1.不要过于频繁调用setData,应考虑将多次setData合并成一次setData调用;
2.数据通信的性能与数据量正相关,因而如果有一些数据字段不在界面中展示且数据结构比较复杂或包含长字符串,则不应使用setData来设置这些数据;
3.与界面渲染无关的数据最好不要设置在data中,可以考虑设置在page对象的其他字段下。

用户事件通信

视图层会接受用户事件,如点击事件、触摸事件等。用户事件的通信比较简单:当一个用户事件被触发且有相关的事件监听器需要被触发时,视图层会将信息反馈给逻辑层。因为这个通信过程是异步的,会产生一定的延迟,延迟时间同样与传输的数据量正相关,数据量小于64KB时在30ms内。降低延迟时间的方法主要有两个。
1.去掉不必要的事件绑定(WXML中的bind和catch),从而减少通信的数据量和次数;
2.事件绑定时需要传输target和currentTarget的dataset,因而不要在节点的data前缀属性中放置过大的数据。

原生组件通信

原生组件通过context来更新组件。不同于setData,使用context来更新组件并不会涉及到重渲染过程,数据通信过程也不同。在setData的数据通信流程中,数据从逻辑层经过native层转发,传入视图层的WebView,再经过一系列渲染步骤之后传入组件。而使用context时,数据从逻辑层传到native层后,直接传入组件中,这样可以显著降低传输延迟。在具体通信过程上,因为context的方法繁多,通信方式相对于setData更复杂。不过基础库会对context方法调用时的通信进行封装优化,通常开发者不需要关心这个问题。

8、代码包以及代码分包

在某个小程序第一次启动时,微信需要下载小程序代码包。此后,如果小程序代码包未更新且还被保留在缓存中,则下载小程序代码包的步骤会被跳过。从开发者的角度看,控制代码包大小有助于减少小程序的启动时间。对低于1MB的代码包,其下载时间可以控制在929ms(iOS)、1500ms(Android)内。
以下是一些常规的控制代码包大小的方法。
1、精简代码,去掉不必要的WXML结构和未使用的WXSS定义。
2、减少在代码包中直接嵌入的资源文件。
3、压缩图片,使用适当的图片格式。
如果小程序比较复杂,优化后的代码总量可能仍然比较大,此时可以采用分包加载的方式进行优化。采用分包时,小程序的代码包可以被划分为几个:一个是“主包”,包含小程序启动时会马上打开的页面代码和相关资源;其余是“分包”,包含其余的代码和资源。这样,小程序启动时,只需要先将主包下载完成,就可以立刻启动小程序,可以显著降低小程序代码包的下载时间。

app.json示例:

{
  "pages":[
    "pages/index",
    "pages/logs"
  ],
  "subPackages": [
    {
      "root": "packageA",
      "pages": [
        "pages/cat",
        "pages/dog"
      ]
    }, {
      "root": "packageB",
      "pages": [
        "pages/apple",
        "pages/banana"
      ]
    }
  ]
}

一个支持分包的小程序目录结构可以组织成上图的形式。代码根目录下有“packageA”和“packageB”两个子目录(它们的名字需要在app.json中声明),这两个子目录就构成了两个分包,每个分包下都可以有自己的页面代码和资源文件。而除掉这两个目录的部分就是小程序的主包。在小程序启动时,“packageA”和“packageB”两个子目录的内容不会马上被下载下来,只有主包的内容才会被下载。利用这个特性就可以显著降低初始启动时的下载时间。
使用分包时需要注意代码和资源文件目录的划分。启动时需要访问的页面及其依赖的资源文件应放在主包中。

9、代码加载

小程序的代码包被下载(或从缓存中读取)完成后,小程序的代码会被加载到适当的线程中执行。此时,所有app.js、页面所在的JS文件和所有其他被require的JS文件会被自动执行一次,小程序基础库会完成所有页面的注册。在小程序代码调用Page构造器的时候,小程序基础库会记录页面的基础信息,如初始数据(data)、方法等。需要注意的是,如果一个页面被多次创建,并不会使得这个页面所在的JS文件被执行多次,而仅仅是根据初始数据多生成了一个页面实例(this),在页面JS文件中直接定义的变量,在所有这个页面的实例间是共享的

10、页面渲染

尽量减少节点数,从而降低页面初始渲染和重渲染时间
每次setData数据时,都会重渲染更新界面。重渲染时会将setData合并到data并套用在WXML上得到一个新的节点树,用于替换原有节点树。所以如果不是用于渲染的数据,最好不要放在data中或通过setData设置,以减少重渲染的次数。

posted @ 2021-06-21 14:27  木-鱼  阅读(406)  评论(0)    收藏  举报