小程序开发指南要点整理

2021-03-07  本文已影响0人  南慕瑶

【导读】

本文是对官方【小程序开发指南】的精简 + 解读,帮助你更好地理解小程序。理解小程序为什么出现,理解小程序的底层原理等等。

【注】

本文仅做笔记式记录,如有侵权,烦请告知。

第一部分:初识小程序

一、微信为什么要推出小程序

最初,在微信中展现 web 页面,是通过 webview。

web 调用原生的一些能力,通过微信团队未对外开放的 WeixinJSBridge。

后来,WeixinJSBridge 演变为对外开放的 JS-SDK。

JS-SDK 是对之前的 WeixinJSBrige 的一个包装,以及新能力的释放。

【存在痛点】

移动网页体验不良。

【方案 v1】

推出 JS-SDK 增强版(未对外开放)。

增加“微信 web 资源离线存储”功能。

方案 v1 的问题

(1)复杂页面依然会有白屏问题。如 CSS、JavaScript 较多,文件执行会占用大量 UI 线程,造成白屏。

(2)缓存方案处理较为繁杂,对开发者的要求较高。

【愿景】

设计一个比较好的系统,使得所有开发者在微信中都能获得比较好的体验。

包括:

(1)快速的加载

(2)更强大的能力(比如使用原生能力)

(3)原生的体验

(4)易用且安全的微信数据开放

(5)高效和简单的开发

【解决方案】

推出小程序。

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

四种主要区别:

UI 渲染和脚本执行是否使用同一线程、是否可进行 DOM 操作、运行环境、开发依赖

1、UI 渲染和脚本执行方式

网页:UI 渲染和脚本执行用同一个线程,脚本执行会阻塞 UI 渲染。

小程序:UI 渲染和逻辑执行使用不同的线程,脚本执行不会阻塞 UI 渲染。

2、DOM 操作

网页:可以使用 DOM、BOM API,执行 DOM、BOM 操作

小程序:逻辑层运行在 JSCore 中,并没有一个完整的浏览器对象,也就没有 DOM、BOM API,无法执行 DOM、BOM 操作。

【注】

JSCore 的环境不同于浏览器,也不同于 NodeJS。因此,除了无法使用 DOM、BOM API,一些 NPM 包在小程序中也无法运行。

3、运行环境

网页:各种浏览器、各式 webview

小程序:iOS、安卓、小程序开发者工具

4、开发依赖

网页:编辑器、浏览器

小程序:小程序帐号、开发者工具


第二部分:小程序代码组成

一、JSON 配置

JSON 文件在小程序代码中扮演静态配置的作用,在小程序运行之前就决定了小程序一些表现。

【注意】

小程序是无法在运行过程中去动态更新 JSON 配置文件从而发生对应的变化的。

二、WXML 模板

1、属性大小写敏感:class 和 Class 是不同的属性。

2、变量名大小写敏感:{{name}} 和 {{Name}} 是不同的变量。

3、wx: for = "array":默认变量名 item,默认下标名 index。

4、wx:key 的两种形式:

wx: key = "uniqueKey" 或 wx:key = "*this"(代表 item 本身,要求 item 是唯一的 string / number)

5、模板:

可以在模板中定义代码片段,然后在不同的地方调用。使用 name 属性,作为模板的名字。然后在 <template/> 内定义代码片段。

定义模板:

<template name="msgItem">

  <view>

    <text> {{index}}: {{msg}} </text>

    <text> Time: {{time}} </text>

  </view>

</template>

使用模板:

<!--

item: {

  index: 0,

  msg: 'this is a template',

  time: '2016-06-18'

}

-->

<template is="msgItem" data="{{...item}}"/>

动态使用模板:

<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>

6、引用:

WXML 提供两种文件引用方式 import 和 include。

(1)import:用于引入 template

在 index.wxml 中 import 其他 template 文件,就可以在 index.wxml 中使用相应 template。

eg:

模板定义:

<!-- item.wxml -->

<template name="item">

  <text>{{text}}</text>

</template>

模板引用与使用:

<import src="item.wxml"/>

<template is="item" data="{{text: 'forbar'}}"/>

【注】

import 有作用域的概念。

即:C 引用 B,B 引用A,在C中可以使用B定义的 template,在B中可以使用A定义的 template ,但是C不能使用A定义的template 。

(2)include:用于引入任意整块代码

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

<!-- header.wxml -->

<view> header </view>

<!-- footer.wxml -->

<view> footer </view>

<!-- index.wxml -->

<include src="header.wxml"/>

<view> body </view>

<include src="footer.wxml"/>

7、所有 wxml 标签都支持的属性:

三、WXSS 样式

1、rpx 单位:对于 750px 宽的设计稿,1px = 2rpx

2、样式引用:@import './test_0.wxss'

【注】

和 css 样式引用的区别:

index.css 中 @import url('./test_0.css'),请求 index.css 的时候,会多一个 test_0.css 的请求。

index.wxss 中 @import './test_0.wxss',请求 index.wxss 的时候不会多一个请求,test_0.wxss 会被编译打包到 index.wxss 中。

3、官方样式库:WeUI

四、JavaScript 脚本

1、概述

理解 JavaScript 与 ECMAScript:

JavaScript 是 ECMAScript 一种实现。小程序中的 JavaScript同浏览器中的 JavaScript 以及 NodeJS 中的 JavaScript 是不相同的。

浏览器中 JavaScript 构成 NodeJS 中 JavaScript 构成 小程序中 JavaScript 构成

【注】

1、相比浏览器环境:小程序无法操作 DOM、BOM

2、相比 NodeJS 环境:小程序中无法加载原生库,也无法直接使用大部分的 NPM 包

2、小程序的执行环境

三大执行环境:iOS平台、Android平台、小程序IDE

【注】

iOS9 和 iOS10 会不支持一些 es6 语法。

开发中,需要用开发者工具勾选“ES6 转 ES5”,才能确保代码在所有的环境都能得到很好的执行。

3、模块化

导出:

// moduleA.js

module.exports = function() {}

使用:

require('./moduleA')

4、 脚本的执行顺序

入口:app.js

执行完 app.js 及其 require 的脚本后,小程序会按照 app.json 中定义的 pages 的顺序,逐一执行。

5、作用域

(1)在文件中声明的变量和函数,只在该文件中有效。

(2)全局变量的声明:

在 app.js 中声明:

// app.js

App({

  globalData: 1

})

使用:

var app = getApp()

console.log('app.globalData:', app.globalData)  // 1

也可以在其他文件中设置全局变量:

var app = getApp()

app.addGlobalData = 'Sherry'

其他文件可访问到 addGlobalData:

var app = getApp()

console.log('app.addGlobalData:', app.addGlobalData) // 'Sherry'


第三部分:理解小程序宿主环境

一、渲染层和逻辑层

wxml、wxss 工作在渲染层,js 脚本工作在逻辑层。

1、通信模型

渲染层:使用 webview 线程渲染界面。一个小程序存在多个页面,所以渲染层存在多个 webview 线程。

逻辑层:使用 JsCore 线程运行 JS 脚本。

webview 线程和 JsCore 线程通信,通过 Native,也就是微信客户端。

2、数据驱动

数据变更,视图自动更新。即:数据驱动视图。

WXML 结构转 JS 对象,再转 Dom 树

数据更新,上图 JS 对象对应的节点就会发生变化。

对比前后两个JS对象得到变化的部分,然后把这个差异应用到原来的Dom树上,从而达到更新UI的目的,这就是“数据驱动”的原理。

【个人解读】

所谓数据驱动,即不关心视图,仅操纵数据。

逻辑层的任务,仅仅是执行用户的 setData 操作,然后把更新后的数据传给渲染层而已。

渲染层拿到最新的数据后,会根据当前的 wxml 结构以及最新数据、生成新的 JS 对象,并与当前的 JS 对象进行对比,得到改动的地方,再把差异更新到原来的 Dom 树(形如:document.getElementById() 获取到需要变更的真实 Dom 节点,然后更改它的属性值 or 子节点等)。

二、程序与页面

1、初始化

1)微信客户端初始化宿主环境,同时从网络下载或者从本地缓存中拿到小程序的代码包,把它注入到宿主环境

2)初始化完成,微信客户端就会给 App 实例派发 onLaunch 事件,onLaunch 被调用

2、退出

点击小程序右上角的关闭,或按手机设备的Home键离开小程序,小程序并没有被直接销毁

这时,只是进入后台状态,App 构造器定义的 onHide 方法会被调用。

3、重新进入

再次回到微信或者再次打开小程序时,小程序进入前台状态,App 构造器定义的 onShow 方法会被调用。

4、全局数据

// app.js

App({

  globalData: 'I am global data' // 全局共享数据

})

由于 JS 逻辑统一运行在逻辑层,仅有一个线程。因此,页面切换,切换 webview 时,逻辑层并没有发生改变,JS 的数据依然可以被访问到。

【注】

也因此,在页面中写的 setTimeout 或者 setInterval 定时器,离开页面时,并没有被删除,需要开发者手动删除。

5、页面生命周期

(1)onLoad => onShow => onReady

onLoad、onReady 在页面销毁之前,仅触发一次。

onLoad:监听页面加载。

onReady:监听页面初次渲染完成。

onReady 触发时,表示页面已经准备妥当,在逻辑层就可以和视图层进行交互了。

(2)setData

this.setData 把数据传递给渲染层,从而达到更新界面的目的。

setData 的第二个参数是一个 callback 回调,在这次 setData 对界面渲染完毕后触发。

【注】

1)我们只要保持一个原则就可以提高小程序的渲染性能:每次只设置需要改变的最小单位数据。

如:

// page.js

Page({

  data: {

    a: 1, b: 2, c: 3,

    d: [1, {text: 'Hello'}, 3, 4]

  }

  onLoad: function(){

      // a需要变化时,只需要setData设置a字段即可

    this.setData({a : 2})

  }

})

this.setData({"d[1].text": 'Goodbye'});

2)不要把 data 中的任意一项的 value 设为 undefined,否则可能会有引起一些不可预料的 bug。

6、小程序页面栈

小程序宿主环境限制了页面栈的最大层级为 10 层。

wx.navigateTo({ url: 'pageD' }):插入新页面。

wx.navigateBack():销毁最上层的页面。

wx.redirectTo({ url: 'pageE' }):替换最上层的页面。

wx.switchTab({ url: 'pageF' }):页面栈先清空,然后 push pageF。变为:[ pageF ]

wx. reLaunch({ url: 'pageH' }) :重启小程序,并且打开 pageH,此时页面栈为 [ pageH ]

【注】

1)wx.navigateTo 和 wx.redirectTo 只能打开非TabBar页面,wx.switchTab 只能打开 Tabbar 页面。

2)Tabbar 页面初始化之后不会被销毁

(这句话是官方给的描述,但看官方的 demo,应该只是,第一个 Tabbar 页面初始化之后不会被销毁)

3)销毁了的页面再次打开,会再次触发 onLoad、onShow、onReady,未销毁的页面再次打开、仅触发 onShow。

三、事件

事件捕获和事件冒泡触发时序:

先执行捕获事件,再执行冒泡事件

eg:点击 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>

bind* 和 catch* 的区别:

bind 事件绑定不会阻止冒泡事件向上冒泡,catch 事件绑定可以阻止冒泡事件向上冒泡。

capture-catch 将中断捕获阶段和取消冒泡阶段。

eg:点击 inner view,只触发 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>

四、兼容

1、wx.getSystemInfo 与 wx.getSystemInfoSync

使用 wx.getSystemInfo 或者 wx.getSystemInfoSync 来获取手机品牌、操作系统版本号、微信版本号以及小程序基础库版本号等,通过这个信息,我们可以针对不同平台做差异化的服务。

2、判断当前 api 是否存在:

if (wx.openBluetoothAdapter) {

  wx.openBluetoothAdapter()

} else {

// 如果希望用户在最新版本的客户端上体验您的小程序,可以这样子提示

  wx.showModal({

    title: '提示',

    content: '当前微信版本过低,无法使用该功能,请升级到最新微信版本后重试。'

  })

}

3、wx.canIUse

// 判断接口及其参数在宿主环境是否可用

wx.canIUse('openBluetoothAdapter')

wx.canIUse('getSystemInfoSync.return.screenWidth')

// 判断组件及其属性在宿主环境是否可用

wx.canIUse('contact-button')

wx.canIUse('text.selectable')


第四部分:场景应用

1、要兼容到iOS8以下版本,需要开启样式自动补全。(项目设置中设置)

2、一些特殊的样式设置方式:

(1)hover 样式(手指触摸时的样式)

/*page.wxss */

.hover {

  background-color: gray;

}

<!--page.wxml -->

<button hover-class="hover"> 点击button </button>

(2)button 的 loading

<!--page.wxml -->

<button loading="{{loading}}" bindtap="tap">操作</button>

(3)弹窗

wx.showToast、wx.showModal

3、HTTPS 网络通信

(1)小程序要求: wx.request 发起的必须是 https 协议请求。并且,请求的域名需要在管理平台进行配置

(2)wx.request url 长度限制:不超过 1024字节

(3)设置超时时间:app.json 👇

{

  "networkTimeout": {

    "request": 3000

  }

}

(4) 排查异常的方法参考:4.4.6 排查异常方法

4、微信登录

(1)AppId 是公开信息,泄露 AppId 不会带来安全风险

(2)code 有效期 5 分钟。但在成功换取一次用户信息之后,code 会立即失效

(3)开发者服务器用 code 去微信服务器换取用户信息,会拿到:

openid、session_key、unionid

其中,session_key 用作开发者服务器后续与微信服务器通信的凭证。

由于 session_key 有效期长于 code,因此,可以减少多次获取 code、换取用户信息的通信成本。

5、本地数据缓存:wx.getStorage / wx.getStorageSync、wx.setStorage / wx.setStorageSync

本地数据缓存是小程序存储在当前设备硬盘上的数据。

(1)缓存空间隔离:

1)不同小程序的本地缓存空间相互隔离

2)不同用户的缓存相互隔离

(2)每个小程序的缓存空间上限为10MB

(3)可以利用缓存 — 提前渲染页面:

比如商品列表数据,在用户第一次进入小程序、请求回列表数据后,缓存到本地。

用户再次进入,直接从缓存读取上一次的数据,显示到页面。同时,wx.request 请求最新列表数据,请求回之后,页面重新渲染最新数据。

注意,这种性能提升方式,仅适合数据实时性要求不高的场景。

(4)可以利用缓存 — 缓存用户登录态 SessionId

按官方文档,建议同步手动缓存一个过期时间。

wx.setStorageSync('EXPIREDTIME',expiredTime)

使用缓存的 SessionId 前,先判断是否过期,如果过期,直接 wx.login 重新登录,减少携带旧 SessionId 的不必要请求。

6、设备能力

(1)wx.scanCode:调起微信扫一扫

(2)wx.getNetworkType:获取网络状态(wifi、2G、3G、4G、5G)

(3)wx.onNetworkStatusChange:动态监听网络状态变化的接口


第五部分:小程序的发布

1、发布正式版后,正式小程序无法使用,问题排查

(1)如果小程序使用到 Flex 布局,并且需要兼容 iOS8 以下系统时,请检查上传小程序包时,开发者工具是否已经开启“上传代码时样式自动补全”

(2)小程序使用的服务器接口应该走 HTTPS 协议,并且对应的网络域名确保已经在小程序管理平台配置好。

(3)在测试阶段不要打开小程序的调试模式进行测试,因为在调试模式下,微信不会校验域名合法性,容易导致开发者误以为测试通过,导致正式版小程序因为遇到非法域名无法正常工作。

(4)发布前请检查小程序使用到的网络接口已经在现网部署好,并且评估好服务器的机器负载情况。

2、发布模式

全量发布和分阶段发布(灰度发布)

【注】

并非全量发布之后,用户就会立即使用到最新版的小程序。

因为微信客户端有旧版本小程序包缓存。

微信客户端在某些特定的时机异步去更新最新的小程序包。

一般我们认为全量发布的24小时后,所有用户才会真正使用到最新版的小程序。

3、数据分析

(1)常规分析

开发网页和 App 应用都需要开发者自己通过编写代码来上报访问数据,小程序平台则直接内置在宿主环境底层,无需开发者新增一行代码。

开发者可以登录小程序管理平台,通过左侧“数据分析”菜单可以进入数据分析查看。

(2)自定义分析:见官网

4、监控与告警

小程序宿主环境已经内置了异常检测的模块,并且上报到小程序平台。

目前只提供了脚本错误告警,如果需要监控异常的访问或者服务接口耗时时,需要开发者自行开发监控系统,并在小程序逻辑代码加上对应的数据上报。

比较推荐的方法是通过运维中心的监控告警功能,开发者设置合理的错误阈值,再通过加入微信告警群,当小程序运行发生大量异常现象时,微信告警群会提醒开发者,此时开发者再登录小程序管理平台查阅错误日志。


第六部分:底层框架

一、双线程模型

1、什么是双线程模型?

渲染层 + 逻辑层。

渲染层负责 UI 渲染,每个页面使用一个 webview 线程来渲染。

逻辑层负责执行 JS 逻辑。

二者通过微信客户端(native)进行通信。

2、为什么是双线程模型?

1)渲染层运行环境:确保性能

由成熟的 Web 技术渲染页面,并以大量的接口提供丰富的客户端原生能力。

与逻辑层分离,页面渲染不被 JS 的执行阻塞。

2)逻辑层运行环境:确保安全

由于 web 技术的灵活性,使用 web 技术渲染页面,外部可以通过 JavaScript 操作页面,并获取到敏感组件的数据信息,缺乏安全性。

因此,需要一个安全的沙箱环境,去运行开发者的 JavaScript 代码。这个沙箱环境,仅仅提供纯 JavaScript 的解释执行环境。

由于客户端系统都具有JavaScript 的解释引擎(iOS 有内置 JavaScriptCore 框架、安卓有腾讯 x5 内核提供的 JsCore 环境),因此,我们可以创建一个单独的线程去执行 JavaScript。这个单独的线程,就是小程序的逻辑层。

二、组件系统:Exparser

Exparser 是微信小程序的组件组织框架,内置在小程序基础库中,为小程序的各种组件提供基础的支持。小程序内的所有组件,包括内置组件和自定义组件,都由Exparser组织管理。


1、运行原理:

以 Component 为例(Page流程大致相仿,只是参数形式不一样)

在小程序启动时,构造器会将开发者设置的properties、data、methods等定义段,写入Exparser 的组件注册表中

这个组件在被其它组件引用时,就可以根据这些注册信息来创建自定义组件的实例

2、组件间通信

父 => 子:WXML 属性值

子 => 父:事件系统

1)事件冒泡:

input-with-label 的 WXML:

<label>

  <input />

  <slot />

</label>

页面 WXML:

<view>

  <input-with-label>

    <button />

  </input-with-label>

</view>

l 如果事件是非冒泡的,那只能在 button 上监听到事件;

l 如果事件是在 Shadow Tree 上冒泡的,那 button 、 input-with-label 、view 可以依次监听到事件;

l 如果事件是在 Composed Tree 上冒泡的,那 button 、 slot 、label 、 input-with-label 、 view 可以依次监听到事件。

【附】

Shadow Tree 对应一个组件,Composed Tree 对应一个页面。

一个  Composed Tree 由多个 Shadow Tree 构成。

2)triggerEvent

在自定义组件中使用 triggerEvent 触发事件时,可以指定事件的 bubbles、composed 和 capturePhase 属性,用于标注事件的冒泡性质。

triggerEvent 事例:

Component({

  methods: {

    helloEvent: function() {

      this.triggerEvent('hello', {}, {

        bubbles: true,      // 这是一个冒泡事件

        composed: true,    // 这个事件在Composed Tree 上冒泡

        capturePhase: false // 这个事件没有捕获阶段

      })

    }

  }

})

三、原生组件:由客户端渲染的组件(非 webview 渲染)

部分内置组件,是由客户端原生渲染的,如 vedio、map、canvas、picker 组件。

原生组件的层级比所有在 WebView 层渲染的普通组件要高。

限制:

一些CSS样式无法应用于原生组件。

四、小程序与客户端通信原理

1、视图层与客户端的通信

iOS:利用了WKWebView 的提供 messageHandlers 特性。

安卓:往 WebView 的 window 对象注入一个原生方法,最终会封装成 WeiXinJSBridge 这样一个兼容层。


2、逻辑层与客户端的通信

iOS:同上。另外,可以往 JavaScripCore 框架注入一个全局的原生方法

安卓:同上。


第七部分:性能优化

一、启动

1、主流程

在小程序启动时,微信会为小程序展示一个固定的启动界面,界面内包含小程序的图标、名称和加载提示图标。

小程序启动过程

2、代码包下载

这里下载的,是经过编译、压缩、打包之后的代码包。

【性能优化点】

控制代码包大小 有助于减少小程序的启动时间。

【具体方式】

(1)精简代码,去掉不必要的 WXML 结构和未使用的 WXSS 定义。

(2)减少在代码包中直接嵌入的资源文件。

(3)压缩图片,使用适当的图片格式。

必要时,进行分包处理。

小程序启动时,只需要先将主包下载完成,就可以立刻启动小程序。

3、代码包加载

微信会在小程序启动前为小程序准备好通用的运行环境

这个运行环境包括几个供小程序使用的线程,并在其中完成小程序基础库的初始化预先执行通用逻辑,尽可能做好小程序的启动准备。

小程序代码包下载(或从缓存中读取)完成后,小程序的代码会被加载到适当的线程中执行。

加载过程👉 此时,所有 app.js、页面所在的 JS 文件和所有其他被 require 的 JS 文件会被自动执行一次,小程序基础库会完成所有页面的注册。

其中,页面注册、即:在小程序代码调用 Page 构造器的时候,基础库会记录页面的基础信息,如初始数据(data)、方法等。

【注意】

如果一个页面被多次创建,并不会使得这个页面所在的 JS 文件被执行多次,而仅仅是根据初始数据多生成了一个页面实例(this),在页面 JS 文件 Page 构造器外定义的变量,在所有这个页面的实例(this)间是共享的。

也就是说,Page 构造器外定义的变量,相当于当前页面的全局变量,无论创建几个页面实例,访问的都是同一个全局变量。

二、 页面层级准备

在小程序启动前,微信会提前准备好一个页面层级用于展示小程序的首页。

除此以外,每当一个页面层级被用于渲染页面,微信都会提前开始准备一个新的页面层级。

页面层级的准备工作(3个阶段):

(1)启动一个 WebView

(2)在 WebView 中初始化基础库,此时还会进行一些基础库内部优化,以提升页面渲染性能

(3)注入小程序 WXML 结构和 WXSS 样式,使小程序能在接收到页面初始数据之后马上开始渲染页面(这一阶段无法在小程序启动前执行)

页面层级的准备工作

【注】

小程序未加载时,微信客户端根本没拿到小程序代码,所以无法进行注入。

三、数据通信

数据通信性能提升原则:

(1)减少通信

(2)减少通信传输的数据量

1、页面初始数据通信

一个新的页面打开,逻辑层将初始 data 发送给 Native,Native 做两件事

1)将数据传给视图层

2)向用户展示一个新的页面层级(视图层在这个页面层级上进行界面绘制)

视图层拿到数据后,根据页面路径来选择合适的 WXML 结构,WXML 结构与初始数据相结合,得到页面的第一次渲染结果。

【性能瓶颈】

1)页面初始数据通信时间

2)初始渲染时间

【性能优化点】

减少页面初始数据通信时间

【具体方式】

减少传输数据量。

2、 更新数据通信

更新数据传输时,逻辑层首先执行 JSON.stringify,去除掉 setData 数据中不可传输的部分,之后将数据发送给视图层。

同时,逻辑层会将 setData 与 data 合并,更新数据。

【性能优化点】

1)setData 的调用频率

2)setData 设置的数据大小

3)data 数据的精简

【具体方式】

1)减少 setData 的调用,将多次 setData 合并成一次

2)精简 setData 设置的数据,界面无关或比较复杂的长字符串、尽量不要用 setData 设置

3)与界面渲染无关的数据最好不要设置在 data 中

3、用户事件通信

用户触发一个事件,且这个事件存在监听函数,视图层会将触发信息反馈给逻辑层。

如果事件没有绑定监听函数,则不反馈给逻辑层。

【性能优化点】

提升视图层与逻辑层的通信性能。

【具体方式】

1)去掉不必要的事件绑定(WXML中的 bind 和 catch),减少通信的数据量和次数

2)事件绑定时需要传输 target 和 currentTarget 的 dataset,因而不要在节点的 data 前缀属性中放置过大的数据

四、视图层渲染

1、初始渲染

将初始数据套用在对应的 WXML 片段上生成节点树。

【性能优化点】

减少初始渲染时间。

【具体方式】

减少 WXML 中节点的数量(时间开销大体上与节点树中节点的总量成正比)

2、重渲染

初始渲染中得到的 data当前节点树保留下来用于重渲染。

【重渲染步骤】

1)将 data 和 setData 数据套用在 WXML 片段上,得到一个新节点树

2)将新节点树与当前节点树进行比较(diff)

3)将 setData 数据合并到 data 中,并用新节点树替换旧节点树

【性能优化点】

提升 diff 过程速度。

【具体方式】

1)去掉不必要设置的数据

2)减少 setData 的数据量

五、原生组件通信

一些原生组件支持使用 context 来更新组件。

与 setData 的不同:

数据从逻辑层传到 native 层后,直接传入组件中(不需要 native => 视图层,视图层再传入组件)

这种通信方式,可以显著降低传输延迟。

六、性能优化方案汇总

主要的优化策略可以归纳为三点:

1)精简代码,降低 WXML 结构和 JS 代码的复杂性

2)合理使用 setData 调用,减少 setData 次数和数据量

3)必要时使用分包优化。


第八部分:小程序基础库的更新迭代

一、什么是基础库

1、职责

包装提供组件、API,处理数据绑定、组件系统、事件系统、通信系统等一系列框架逻辑。

2、载入时机

启动小程序后先载入基础库,接着再载入业务代码。

渲染层 WebView 层注入的称为 WebView 基础库,逻辑层注入的称为 AppService 基础库。

WebView 基础库 + AppService 基础库 == 小程序基础库。

由于所有小程序在微信客户端打开的时候,都需要注入相同的基础库 ,所以,小程序的基础库不会被打包在某个小程序的代码包里边,它会被提前内置在微信客户端。

这样做的好处:

1)降低业务小程序的代码包大小。

2)可以单独修复基础库中的 Bug,无需修改到业务小程序的代码包。

二、基础库的异常捕获

【可选方案】

1、try-catch方案。

2、window.onerror方案(window.addEventListener("error", function(evt){}))

【注】

1)对比 window.onerror 的方案,try-catch 的方案有个缺点:没法捕捉到全局的错误事件。

2)逻辑层不存在 window 对象,因此逻辑层 AppService 侧无法通过 window.onerror 来捕捉异常。

【最终方案】

1)在 WebView 侧使用 window.onerror 方案进行捕捉异常。

2)在逻辑层 AppService 侧通过把 App 实例和 Page 实例的各个生命周期等方法包裹在 try-catch 里进行捕捉异常。

3)在 App 构造器里提供了 onError 的回调,当业务代码运行产生异常时,这个回调被触发,同时能够拿到异常的具体信息。


第九部分:微信开发者工具

一、代码编译

微信开发者工具和微信客户端都无法直接运行小程序的源码,因此我们需要对小程序的源码进行编译。

代码编译过程:

1)本地预处理

2)本地编译

3)服务器编译

微信开发者工具模拟器运行的代码只经过本地预处理、本地编译,没有服务器编译过程

微信客户端运行的代码是额外经过服务器编译的。

1、WXML 编译:WXML => JavaScript

微信开发者工具内置了一个二进制的 WXML 编译器,这个编译器接受 WXML 代码文件列表,处理完成之后输出 JavaScript 代码,这段代码是各个页面的结构生成函数。

编译过程将所有的 WXML 代码最终变成一个 JavaScript 函数,预先注入在 WebView 中。

(注意,是所有的 WXML,所有的,统一变成一个 JavaScript 函数,叫做:“页面结构生成函数”)

这个函数接收页面路径(pagePath)作为参数,返回“页面结构生成函数”,“页面结构生成函数”接受页面数据(pageData)作为参数,输出一段描述页面结构的 JSON。

最终,通过小程序组件系统生成对应的 HTML。

所有的 WXML 代码最终变成一个 JavaScript 函数

页面结构生成函数的使用:

//$gwx 是 WXML 编译后得到的函数

//根据页面路径获取页面结构生成函数

var generateFun = $gwx('name.wxml')

//页面结构生成函数接受页面数据,得到描述页面结构的JSON

var virtualTree = generateFun({

  name:  'miniprogram'

})

/** virtualTree == {

  tag: 'view',

  children: [{

      tag: 'view',

      children: ['miniprogram']

    }]

}**/

//小程序组件系统在虚拟树对比后将结果渲染到页面上

virtualDom.render(virtualTree)

2、WXSS 编译:WXSS => 样式信息数组

微信开发者工具内置了一个二进制的 WXSS 编译器,这个编译器接受 WXSS 文件列表,分析文件之间的引用关系,同时预处理 rpx,输出一个样式信息数组。

3、JavaScript 编译:多个 js => app-service.js

微信客户端在运行小程序的逻辑层的时候只需要加载一个 JS 文件(我们称为 app-service.js)。

也就是,我们小程序代码经过编译(es6 => es5)、打包之后,得到的 bundle.js。

【具体步骤】

1)代码上传之前的编译、压缩(预处理)

在代码上传之前,微信开发者工具会对开发者的 JS 文件做一些预处理,包括 ES6 转 ES5 和代码压缩(开发者可以选择关闭预处理操作),然后上传(上传的还是多个 JS 文件

2)服务器编译、打包

将每个 JS 文件的内容分别包裹在 define 域中,再按一定的顺序合并成 app-service.js 。

同时,添加主动 require app.js 和页面 JS 的代码。

代码上传之前的编译 代码上传之前的压缩 + 服务器编译、打包

二、模拟器

1、 逻辑层模拟

逻辑层的真实运行环境:

iOS:JavaScriptCore 中

安卓:x5 的 JSCore 中

模拟运行环境:

微信开发者工具:采用一个隐藏着的 Webivew 来模拟小程序的逻辑运行环境

微信客户端小程序运行环境模型简图 微信开发者工具小程序运行环境模型简图

在微信开发者工具上 WebView 是一个chrome的 <webview /> 标签。与<iframe />标签不同的是,<webview/> 标签是采用独立的线程运行的。

WebView 在请求开发者 JS 代码时,开发者工具读取 JS 代码进行必要的预处理后,将处理结果返回。

然后,由 WebView 解析执行(这样,逻辑层的 JS 文件,就得到了执行)。

虽然开发者工具上是没有对 JS 代码进行合并的,但是还是按照相同的加载顺序进行解析执行。

【更好的模拟】

WebView 是一个浏览器环境,支持 BOM 操作。但逻辑层本不该支持这种操作,一旦出现,我们需要正确报错才对。

因此,开发者工具将开发者的代码包裹在 define 域的时候,将浏览器的 BOM 对象局部变量化,从而使得在开发阶段就能发现问题。

2、 渲染层模拟

微信开发者工具使用 chrome 的 <webview />标签来加载渲染层页面。

每个渲染层 WebView 加载:

http://127.0.0.1:9973/pageframe/pageframe.html

开发者工具底层搭建的 HTTP 本地服务器在收到这个请求的时候,就会编译 WXML 文件和 WXSS 文件,然后将编译结果作为 HTTP 请求的返回包

当确定加载页面的路径之后,如 index 页面,开发工具会动态注入如下一段脚本:

// 改变当前 webview 的路径,确保之后的图片网络请求能得到正确的相对路径

history.pushState('', '', 'pageframe/index')

// 创建自定义事件,将页面结构生成函数派发出去,由小程序渲染层基础库处理

document.dispatchEvent(new CustomEvent("generateFuncReady", {

  detail: {

    generateFunc: $gwx('./index.wxml')

  }

}))

// 注入对应页面的样式,这段函数由 WXSS 编译器生成

setCssToHead()

3、客户端模拟

在微信开发者工具上,通过借助 BOM(浏览器对象模型)以及 node.js 访问系统资源的能力,同时模拟客户端的 UI 和交互流程,使得大部分的 API 能够正常执行。

4、通讯模拟

我们需要一个有效的通讯方案使得小程序的逻辑层、渲染层和客户端之间进行数据交流,才能将这三个部分串联成为一个有机的整体。

【原理】

微信开发者工具的有一个消息中心底层模块维持着一个 WebSocket 服务器,小程序的逻辑层的 WebView 和渲染层页面的 WebView 通过 WebSocket 与开发者工具底层建立长连,使用 WebSocket 的 protocol 字段来区分 Socket 的来源。

三、调试器:界面调试 + 逻辑调试

nw.js 对 <webview/> 提供打开 Chrome Devtools 调试界面的接口,使得开发者工具具备对小程序的逻辑层和渲染层进行调试的能力。

同时,为了方便调试小程序,开发者工具在 Chrome Devtools 的基础上进行扩展和定制。

【界面调试】

微信小程序团队通过脚本注入的方式、将 Chrome Devtools 的 Element 面板隐藏,同时开发了 Chrome Devtools 插件 WXML 面板。

开发者工具会在每个渲染层的 WebView 中注入界面调试的脚本代码,负责获取 WebView 中的 DOM 树、获取节点样式、监听节点变化、高亮选中节点、处理界面调试命令。并将界面调试信息通过 WebSocket 经由开发者工具转发给 WXML 面板进行处理。

【逻辑调试】

直接使用 Chrome Devtools 的 Sources 面板调试逻辑层 JS 代码。

四、微信开发者工具原理总结

1、渲染层

通过编译过程我们将 WXML 文件和 WXSS 文件都处理成 JS 代码,使用 script 标签注入在一个空的 html文件中(我们称为:page-frame.html)

2、逻辑层

我们将所有的 JS 文件编译成一个单独的 app-service.js

3、小程序运行时

1)逻辑层使用 JsCore 直接加载 app-service.js,渲染层使用 WebView 加载 page-frame.html

2)在确定页面路径之后,通过动态注入 script 的方式调用 WXML 文件和 WXSS 文件生成的 JS 代码,再结合逻辑层的页面数据,最终渲染出指定的页面

4、开发者工具使用一个隐藏着的 <webview/> 标签来模拟 JSCore 作为小程序的逻辑层运行环境

5、开发者工具利用 BOM、node.js 以及模拟的 UI 和交互流程实现对大部分客户端 API 的支持

6、开发者工具底层有一个 HTTP 服务器来处理来自 WebView 的请求,并将开发者代码编译处理后的结果作为 HTTP 请求的返回,WebView 按照普通的网页进行渲染

7、开发者工具底层维护着一个 WebSocket 服务器,用于在 WebView 与开发者工具之间建立可靠的消息通讯链路

8、微信开发者工具使用 webview.showDevTools 打开 Chrome Devtools 调试逻辑层 WebView 的 JS 代码

9、微信小程序团队开发了 Chrome Devtools 插件 WXML 面板对渲染层页面 WebView 进行界面调试

上一篇下一篇

猜你喜欢

热点阅读