美文网首页
小程序开发指南要点整理

小程序开发指南要点整理

作者: 南慕瑶 | 来源:发表于2021-03-07 13:40 被阅读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 进行界面调试

    相关文章

      网友评论

          本文标题:小程序开发指南要点整理

          本文链接:https://www.haomeiwen.com/subject/fjxvfltx.html