近两万字小程序攻略发布了

来源:掘金
2021-02-27
1346
导语:该内容由银科控股融汇研发部曹俊及其团队授权提供。该团队拥有 10 多名小程序开发,深耕小程序领域,总结出了本篇优质长文。同时本篇内容也已经合并入我的 开源项目 中,目前项目内容包含了 JS、网络、浏览器相关、性能优化、安全、框架、Git、数据结构、算法等内容,无论是基础还是进阶,亦或是源码解读,你都能在本图谱中得到满意的答案,希望这个面试图谱能够帮助到大家更好的准备面试。
阅读本文大概需要15分钟

小程序-登录

unionid和openid

了解小程序登陆之前,我们写了解下小程序/公众号登录涉及到两个最关键的用户标识:

  • OpenId 是一个用户对于一个小程序/公众号的标识,开发者可以通过这个标识识别出用户。
  • UnionId 是一个用户对于同主体微信小程序/公众号/APP的标识,开发者需要在微信开放平台下绑定相同账号的主体。开发者可通过UnionId,实现多个小程序、公众号、甚至APP 之间的数据互通了。

关键Api

登录流程设计

以下从笔者接触过的几种登录流程来做阐述:

利用现有登录体系

直接复用现有系统的登录体系,只需要在小程序端设计用户名,密码/验证码输入页面,便可以简便的实现登录,只需要保持良好的用户体验即可。

利用OpenId 创建用户体系

?提过,OpenId 是一个小程序对于一个用户的标识,利用这一点我们可以轻松的实现一套基于小程序的用户体系,值得一提的是这种用户体系对用户的打扰最低,可以实现静默登录。具体步骤如下:

  1. 小程序客户端通过 wx.login 获取 code

  2. 传递 code 向服务端,服务端拿到 code 调用微信登录凭证校验接口,微信服务器返回 openid 和会话密钥 session_key ,此时开发者服务端便可以利用 openid 生成用户入库,再向小程序客户端返回自定义登录态

  3. 小程序客户端缓存 (通过storage)自定义登录态(token),后续调用接口时携带该登录态作为用户身份标识即可

利用 Unionid 创建用户体系

如果想实现多个小程序,公众号,已有登录系统的数据互通,可以通过获取到用户 unionid 的方式建立用户体系。因为 unionid 在同一开放平台下的所所有应用都是相同的,通过 unionid 建立的用户体系即可实现全平台数据的互通,更方便的接入原有的功能,那如何获取 unionid 呢,有以下两种方式:

  1. 如果户关注了某个相同主体公众号,或曾经在某个相同主体App、公众号上进行过微信登录授权,通过 wx.login 可以直接获取 到 unionid

  2. 结合 wx.getUserInfo

注意事项

  1. 需要获取 unionid 形式的登录体系,在以前(18年4月之前)是通过以下这种方式来实现,但后续微信做了调整(因为一进入小程序,主动弹起各种授权弹窗的这种形式,比较容易导致用户流失),调整为必须使用按钮引导用户主动授权的方式,这次调整对开发者影响较大,开发者需要注意遵守微信的规则,并及时和业务方沟通业务形式,不要存在侥幸心理,以防造成小程序不过审等情况。
   wx.login(获取code) ===> wx.getUserInfo(用户授权) ===> 获取 unionid
复制代码
  1. 因为小程序不存在 cookie 的概念, 登录态必须缓存在本地,因此强烈建议为登录态设置过期时间

  2. 值得一提的是如果需要支持风控安全校验,多平台登录等功能,可能需要加入一些公共参数,例如platform,channel,deviceParam等参数。在和服务端确定方案时,作为前端同学应该及时提出这些合理的建议,设计合理的系统。

  3. openidunionid 不要在接口中明文传输,这是一种危险的行为,同时也很不专业。

小程序-图片导出

经常开发和使用小程序的同学对这个功能一定不陌生,这是一种常见的引流方式,一般同时会在图片中附加一个小程序二维码。

基本原理

  1. 借助 canvas 元素,将需要导出的样式首先在 canvas 画布上绘制出来 (api基本和h5保持一致,但有轻微差异,使用时注意即可)

  2. 借助微信提供的 canvasToTempFilePath 导出图片,最后再使用 saveImageToPhotosAlbum (需要授权)保存图片到本地

如何优雅实现

根据上述的原理来看,实现是很简单的,只不过就是设计稿的提取,绘制即可,但是作为一个常用功能,每次都这样写一坨代码岂不是非常的难受。那小程序如何设计一个通用的方法来帮助我们导出图片呢?思路如下:

  1. 绘制出需要的样式这一步是省略不掉的。但是我们可以封装一个绘制库,包含常见图形的绘制,例如矩形,圆角矩形,圆, 扇形, 三角形, 文字,图片减少绘制代码,只需要提炼出样式信息,便可以轻松的绘制,最后导出图片存入相册。笔者觉得以下这种方式绘制更为优雅清晰一些,其实也可以使用加入一个type参数来指定绘制类型,传入的一个是样式数组,实现绘制。

  2. 结合上一步的实现,如果对于同一类型的卡片有多次导出需求的场景,也可以使用自定义组件的方式,封装同一类型的卡片为一个通用组件,在需要导出图片功能的地方,引入该组件即可。

    
  class CanvasKit {
    constructor() {
    }
    drawImg(option = {}) {
      ...
      return this
    }
    drawRect(option = {}) {
      return this
    }
    drawText(option = {}) {
      ...
      return this
    }
    static exportImg(option = {}) {
      ...
    }
  }

  let drawer = new CanvasKit('canvasId').drawImg(styleObj1).drawText(styleObj2)
  drawer.exportImg()

复制代码

注意事项

  1. 小程序中无法绘制网络图片到canvas上,需要通过downLoadFile 先下载图片到本地临时文件才可以绘制
  2. 通常需要绘制二维码到导出的图片上,有一种方式导出二维码时,需要携带的参数必须做编码,而且有具体的长度(32可见字符)限制,可以借助服务端生成 短链接 的方式来解决

小程序-数据统计

数据统计作为目前一种常用的分析用户行为的方式,小程序端也是必不可少的。小程序采取的曝光,点击数据埋点其实和h5原理是一样的。但是埋点作为一个和业务逻辑不相关的需求,我们如果在每一个点击事件,每一个生命周期加入各种埋点代码,则会干扰正常的业务逻辑,和使代码变的臃肿,笔者提供以下几种思路来解决数据埋点:

设计一个埋点sdk

小程序的代码结构是,每一个 Page 中都有一个 Page 方法,接受一个包含生命周期函数,数据的 业务逻辑对象 包装这层数据,借助小程序的底层逻辑实现页面的业务逻辑。通过这个我们可以想到思路,对Page进行一次包装,篡改它的生命周期和点击事件,混入埋点代码,不干扰业务逻辑,只要做一些简单的配置即可埋点,简单的代码实现如下:

  
  代码仅供理解思路
  page = function(params) {
    let keys = params.keys()
    keys.forEach(v => {
        if (v === 'onLoad') {
          params[v] = function(options) {
            stat()   //曝光埋点代码
            params[v].call(this, options)
          }
        }
        else if (v.includes('click')) {
          params[v] = funciton(event) { 
            let data = event.dataset.config
            stat(data)  // 点击埋点
            param[v].call(this)
          }
        }
    })
  }
复制代码

这种思路不光适用于埋点,也可以用来作全局异常处理,请求的统一处理等场景。

分析接口

对于特殊的一些业务,我们可以采取 接口埋点,什么叫接口埋点呢?很多情况下,我们有的api并不是多处调用的,只会在某一个特定的页面调用,通过这个思路我们可以分析出,该接口被请求,则这个行为被触发了,则完全可以通过服务端日志得出埋点数据,但是这种方式局限性较大,而且属于分析结果得出过程,可能存在误差,但可以作为一种思路了解一下。

微信自定义数据分析

微信本身提供的数据分析能力,微信本身提供了常规分析和自定义分析两种数据分析方式,在小程序后台配置即可。借助小程序数据助手这款小程序可以很方便的查看。

小程序-工程化

工程化做什么

目前的前端开发过程,工程化是必不可少的一环,那小程序工程化都需要做些什么呢,先看下目前小程序开发当中存在哪些问题需要解决:

  1. 不支持 css预编译器,作为一种主流的 css解决方案,不论是 less,sass,stylus 都可以提升css效率
  2. 不支持引入npm包 (这一条,从微信公开课中听闻,微信准备支持)
  3. 不支持ES7等后续的js特性,好用的async await等特性都无法使用
  4. 不支持引入外部字体文件,只支持base64
  5. 没有 eslint 等代码检查工具

方案选型

对于目前常用的工程化方案,webpack,rollup,parcel等来看,都常用与单页应用的打包和处理,而小程序天生是 “多页应用” 并且存在一些特定的配置。根据要解决的问题来看,无非是文件的编译,修改,拷贝这些处理,对于这些需求,我们想到基于流的 gulp非常的适合处理,并且相对于webpack配置多页应用更加简单。所以小程序工程化方案推荐使用 gulp

具体开发思路

通过 gulp 的 task 实现:

  1. 实时编译 less 文件至相应目录
  2. 引入支持async,await的运行时文件
  3. 编译字体文件为base64 并生成相应css文件,方便使用
  4. 依赖分析哪些地方引用了npm包,将npm包打成一个文件,拷贝至相应目录
  5. 检查代码规范

上述实现起来其实并不是很难,但是这样的话就是一份纯粹的 gulp 构建脚本和 约定好的目录而已,每次都有一个新的小程序都来拷贝这份脚本来处理吗?显然不合适,那如何真正的实现 小程序工程化 呢? 我们可能需要一个简单的脚手架,脚手架需要支持的功能:

  1. 支持新建项目,创建Page,创建Component
  2. 支持内置构建脚本
  3. 支持发布小程序,也可以想办法接入Jenkins等工具做持续集成 (小程序持续集成后面会提) ...

小程序架构

architecture">

微信小程序的框架包含两部分 View 视图层、App Service逻辑层。View 层用来渲染页面结构,AppService 层用来逻辑处理、数据请求、接口调用。

它们在两个线程里运行。

它们在两个线程里运行。

它们在两个线程里运行。

视图层和逻辑层通过系统层的 JSBridage 进行通信,逻辑层把数据变化通知到视图层,触发视图层页面更新,视图层把触发的事件通知到逻辑层进行业务处理。

补充

one-context

视图层使用 WebView 渲染,iOS 中使用自带 WKWebView,在 Android 使用腾讯的 x5 内核(基于 Blink)运行。

逻辑层使用在 iOS 中使用自带的 JSCore 运行,在 Android 中使用腾讯的 x5 内核(基于 Blink)运行。

开发工具使用 nw.js 同时提供了视图层和逻辑层的运行环境。

在 Mac下 使用 js-beautify 对微信开发工具 @v1.02.1808080代码批量格式化:

cd /Applications/wechatwebdevtools.app/Contents/Resources/package.nw
find . -type f -name '*.js' -not -path "./node_modules/*" -not -path -exec js-beautify -r -s 2 -p -f '{}' \;
复制代码

js/extensions/appservice/index.js 中找到:

	267: function(a, b, c) {
    const d = c(8),
      e = c(227),
      f = c(226),
      g = c(228),
      h = c(229),
      i = c(230);
    var j = window.__global.navigator.userAgent,
      k = -1 !== j.indexOf('game');
    k || i(), window.__global.getNewWeixinJSBridge = (a) => {
      const {
        invoke: b
      } = f(a), {
        publish: c
      } = g(a), {
        subscribe: d,
        triggerSubscribeEvent: i
      } = h(a), {
        on: j,
        triggerOnEvent: k
      } = e(a);
      return {
        invoke: b,
        publish: c,
        subscribe: d,
        on: j,
        get __triggerOnEvent() {
          return k
        },
        get __triggerSubscribeEvent() {
          return i
        }
      }
    }, window.WeixinJSBridge = window.__global.WeixinJSBridge = window.__global.getNewWeixinJSBridge('global'), window.__global.WeixinJSBridgeMap = {
      __globalBridge: window.WeixinJSBridge
    }, __devtoolsConfig.online && __devtoolsConfig.autoTest && setInterval(() => {
      console.clear()
    }, 1e4);
    try {
      var l = new window.__global.XMLHttpRequest;
      l.responseType = 'text', l.open('GET', `http://${window.location.host}/calibration/${Date.now()}`, !0), l.send()
    } catch (a) {}
  }
复制代码

js/extensions/gamenaitveview/index.js 中找到:

  299: function(a, b, c) {
    'use strict';
    Object.defineProperty(b, '__esModule', {
      value: !0
    });
    var d = c(242),
      e = c(241),
      f = c(243),
      g = c(244);
    window.WeixinJSBridge = {
      on: d.a,
      invoke: e.a,
      publish: f.a,
      subscribe: g.a
    }
  },
复制代码

js/extensions/pageframe/index.js中找到:

317: function(a, b, c) {
    'use strict';

    function d() {
      window.WeixinJSBridge = {
        on: e.a,
        invoke: f.a,
        publish: g.a,
        subscribe: h.a
      }, k.a.init();
      let a = document.createEvent('UIEvent');
      a.initEvent('WeixinJSBridgeReady', !1, !1), document.dispatchEvent(a), i.a.init()
    }
    Object.defineProperty(b, '__esModule', {
      value: !0
    });
    var e = c(254),
      f = c(253),
      g = c(255),
      h = c(256),
      i = c(86),
      j = c(257),
      k = c.n(j);
    'complete' === document.readyState ? d() : window.addEventListener('load', function() {
      d()
    })
  },
复制代码

我们都看到了 WeixinJSBridge 的定义。分别都有 oninvokepublishsubscribe 这个几个关键方法。

invoke 举例,在 js/extensions/appservice/index.js中发现这段代码:

f (!r) p[b] = s, f.send({
    command: 'APPSERVICE_INVOKE',
    data: {
        api: c,
        args: e,
        callbackID: b
    }
});
复制代码

js/extensions/pageframe/index.js 中发现这段代码:

g[d] = c, e.a.send({
    command: 'WEBVIEW_INVOKE',
    data: {
        api: a,
        args: b,
        callbackID: d
    }
})

复制代码

简单的分析得知:字段 command 用来区分行为,invoke 用来调用 Native 的 Api。在不同的来源要使用不同的前缀。data 里面包含 Api 名,参数。另外 callbackID 指定接受回调的方法句柄。Appservice 和 Webview 使用的通信协议是一致的。

我们不能在代码里使用 BOM 和 DOM 是因为根本没有,另一方面也不希望 JS 代码直接操作视图。

在开发工具中 remote-helper.js 中找到了这样的代码:

const vm = require("vm");

const vmGlobal = {
    require: undefined,
    eval: undefined,
    process: undefined,
    setTimeout(...args) {
        //...省略代码
        return timerCount;
    },
    clearTimeout(id) {
        const timer = timers[id];
        if (timer) {
            clearTimeout(timer);
            delete timers[id];
        }
    },
    setInterval(...args) {
        //...省略代码
        return timerCount;
    },
    clearInterval(id) {
        const timer = timers[id];
        if (timer) {
            clearInterval(timer);
            delete timers[id];
        }
    },
    console: (() => {
        //...省略代码
        return consoleClone;
    })()
};
const jsVm = vm.createContext(vmGlobal);
// 省略大量代码...
function loadCode(filePath, sourceURL, content) {
    let ret;
    try {
        const script = typeof content === 'string' ? content : fs.readFileSync(filePath, 'utf-8').toString();
        ret = vm.runInContext(script, jsVm, {
            filename: sourceURL,
        });
    }
    catch (e) {
        // something went wrong in user code
        console.error(e);
    }
    return ret;
}
复制代码

这样的分层设计显然是有意为之的,它的中间层完全控制了程序对于界面进行的操作, 同时对于传递的数据和响应时间也能做到监控。一方面程序的行为受到了极大限制, 另一方面微信可以确保他们对于小程序内容和体验有绝对的控制。

这样的结构也说明了小程序的动画和绘图 API 被设计成生成一个最终对象而不是一步一步执行的样子, 原因就是 Json 格式的数据传递和解析相比与原生 API 都是损耗不菲的,如果频繁调用很可能损耗过多性能,进而影响用户体验。

下载小程序完整包

download">

App Service - Life Cylce

lifecycle">

面试题

1.动画需要绑定在 data 上,而绘图却不用。你觉得是为什么呢?

var context = wx.createCanvasContext('firstCanvas')
    
context.setStrokeStyle("#00ff00")
context.setLineWidth(5)
context.rect(0, 0, 200, 200)
context.stroke()
context.setStrokeStyle("#ff0000")
context.setLineWidth(2)
context.moveTo(160, 100)
context.arc(100, 100, 60, 0, 2 * Math.PI, true)
context.moveTo(140, 100)
context.arc(100, 100, 40, 0, Math.PI, false)
context.moveTo(85, 80)
context.arc(80, 80, 5, 0, 2 * Math.PI, true)
context.moveTo(125, 80)
context.arc(120, 80, 5, 0, 2 * Math.PI, true)
context.stroke()
context.draw()
复制代码
Page({
  data: {
    animationData: {}
  },
  onShow: function(){
    var animation = wx.createAnimation({
      duration: 1000,
  	  timingFunction: 'ease',
    })

    this.animation = animation
    
    animation.scale(2,2).rotate(45).step()
    
    this.setData({
      animationData:animation.export()
    })
  }
})
复制代码

2.小程序的 Http Rquest 请求是不是用的浏览器 Fetch API?

知识点考察

  • 知道 Request 是由 Native 实现的
  • JSCore 是不带 Http Request、Websocket、Storage等功能的,那是 Webkit 带的
  • 小程序的 wx.request 是不是遵循 fetch API 规范实现的呢?答案,显然不是。因为没有 Promise

View - WXML

WXML(WeiXin Markup Language)

  • 支持数据绑定
  • 支持逻辑算术、运算
  • 支持模板、引用
  • 支持添加事件(bindtap)

WXML">

Wxml编译器:Wcc 把 Wxml文件 转为 JS

执行方式:Wcc index.wxml

使用 Virtual DOM,进行局部更新

View - WXSS

WXSS(WeiXin Style Sheets)

WXSS">

wxss编译器:wcsc 把wxss文件转化为 js

执行方式: wcsc index.wxss

支持大部分CSS特性

亲测包含但不限于如下内容:

  • Transition
  • Animation
    • Keyframes
  • border-radius
  • calc()
  • 选择器,除了官方文档列出的,其实还支持
    • element>element
    • element+element
    • element element
    • element:first-letter
    • element:first-line
    • element:first-child
    • element:last-child
    • element~element
    • element:first-of-type
    • element:last-of-type
    • element:only-of-type
    • element:only-child
    • element:nth-child(n)
    • element:nth-last-child(n)
    • element:nth-of-type(n)
    • element:nth-last-of-type(n)
    • :root
    • element:empty
    • :not(element)
  • iconfont

建议 Css3 的特性都可以做一下尝试。

尺寸单位 rpx

rpx(responsive pixel): 可以根据屏幕宽度进行自适应。规定屏幕宽为 750rpx。公式:

const dsWidth = 750

export const screenHeightOfRpx = function () {
  return 750 / env.screenWidth * env.screenHeight
}

export const rpxToPx = function (rpx) {
  return env.screenWidth / 750 * rpx
}

export const pxToRpx = function (px) {
  return 750 / env.screenWidth * px
}

复制代码
设备 rpx换算px (屏幕宽度/750) px换算rpx (750/屏幕宽度)
iPhone5 1rpx = 0.42px 1px = 2.34rpx
iPhone6 1rpx = 0.5px 1px = 2rpx
iPhone6 Plus 1rpx = 0.552px 1px = 1.81rpx

可以了解一下 pr2rpx-loader 这个库。

样式导入

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

内联样式

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

全局样式与局部样式

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

iconfont

截止20180810

小程序未来有计划支持字体。参考微信公开课

小程序开发与平时 Web开发类似,也可以使用字体图标,但是 src:url() 无论本地还是远程地址都不行,base64 值则都是可以显示的。

将 ttf 文件转换成 base64。打开这个平台 transfonter.org/。点击 Add fonts 按钮,加载ttf格式的那个文件。将下边的 base64 encode 改为 on。点击 Convert 按钮进行转换,转换后点击 download 下载。

复制下载的压缩文件中的 stylesheet.css 的内容到 font.wxss ,并且将 icomoon 中的 style.css 除了 @font-face 所有的代码也复制到 font.wxss 并将i选择器换成 .iconfont,最后:

<text class="iconfont icon-home" style="font-size:50px;color:red">text>
复制代码

View - Component

小程序提供了一系列组件用于开发业务功能,按照功能与HTML5的标签进行对比如下:

Component">

小程序的组件基于Web Component标准

使用Polymer框架实现Web Component

View - Native Component

目前Native实现的组件有

  • cavnas

  • video

  • map

  • textarea

    Native Component

Native组件层在 WebView 层之上。这目前带来了一些问题:

  • Native 实现的组件会遮挡其他组件
  • WebView 渲染出来的视图在滚动时,Native 实现的组件需要更新位置,这会带来性能问题,在安卓机器上比较明显
  • 小程序原生组件 cover-view 可以覆盖 cavnas video 等,但是也有一下弊端,比如在 cavnas 上覆盖 cover-view,就会发现坐标系不统一处理麻烦

目前小程序的问题或限制

截止20180810

包含但不限于:

  • 小程序仍然使用 WebView 渲染,并非原生渲染。(部分原生)

  • 服务端接口返回的头无法执行,比如:Set-Cookie。

  • 依赖浏览器环境的 JS 库不能使用。

  • 不能使用 npm,但是可以自搭构建工具或者使用 mpvue。(未来官方有计划支持)

  • 不能使用 ES7,可以自己用babel+webpack自搭或者使用 mpvue。

  • 不支持使用自己的字体(未来官方计划支持)。

  • 可以用 base64 的方式来使用 iconfont。

  • 小程序不能发朋友圈(可以通过保存图片到本地,发图片到朋友前。二维码可以使用B接口)。

  • 获取二维码/小程序接口的限制。

    • B 接口 scene 最大32个可见字符。
    • AC 接口总共生成的码数量限制为 100,000,请谨慎调用。
    • 真机扫描二维码只能跳转到线上版本,所以测试环境下只可通过开发者工具的通过二维码编译进行调试。
    • 没有发布到线上版本的小程序页面路径会导致生成二维码失败,需要先将添加了页面的小程序发布到线上版本。
  • 小程序推送只能使用“服务通知” 而且需要用户主动触发提交 formId,formId 只有7天有效期。(现在的做法是在每个页面都放入form并且隐藏以此获取更多的 formId。后端使用原则为:优先使用有效期最短的)

  • 小程序大小限制 2M,分包总计不超过 8M

  • 转发(分享)小程序不能拿到成功结果,原来可以。链接(小游戏造的孽)

  • 拿到相同的 unionId 必须绑在同一个开放平台下。开放平台绑定限制:

    • 50个移动应用
    • 10个网站
    • 50个同主体公众号
    • 5个不同主体公众号
    • 50个同主体小程序
    • 5个不同主体小程序
  • 公众号关联小程序,链接

    • 所有公众号都可以关联小程序。
    • 一个公众号可关联10个同主体的小程序,3个不同主体的小程序。
    • 一个小程序可关联500个公众号。
    • 公众号一个月可新增关联小程序13次,小程序一个月可新增关联500次。
  • 一个公众号关联的10个同主体小程序和3个非同主体小程序可以互相跳转

  • 品牌搜索不支持金融、医疗

  • 小程序授权需要用户主动点击

  • 小程序不提供测试 access_token

  • 安卓系统下,小程序授权获取用户信息之后,删除小程序再重新获取,并重新授权,得到旧签名,导致第一次授权失败

  • 开发者工具上,授权获取用户信息之后,如果清缓存选择全部清除,则即使使用了wx.checkSession,并且在session_key有效期内,授权获取用户信息也会得到新的session_key

小程序HTTP2支持情况

HTTP2支持情况:模拟器与真机均不支持

为了验证小程序对HTTP的支持适配情况,我找了两个服务器做测试,一个是网上搜索到支持HTTP2的服务器,一个是我本地起的一个HTTP2服务器。测试中所有请求方法均使用 wx.request

  1. 网上支持HTTP2的服务器:HTTPs://www.snel.com:443

  2. 在Chrome上查看该服务器为 HTTP2

    WechatIMG11">

  3. 在模拟器上请求该接口,请求头的HTTP版本为HTTP1.1,模拟器不支持HTTP2

    WechatIMG12">

  4. 由于小程序线上环境需要在项目管理里配置请求域名,而这个域名不是我们需要的请求域名,没必要浪费一个域名位置,所以打开不验证域名,TSL 等选项请求该接口,通过抓包工具表现与模拟器相同

    WechatIMG14">

HTTP2服务器需要对小程序做兼容性适配

由上可以看出,在真机与模拟器都不支持 HTTP2,但是都是成功请求的,并且 响应头 里的 HTTP 版本都变成了HTTP1.1 版本,说明服务器对 HTTP1.1 做了兼容性适配。

  1. 本地新启一个 node 服务器,返回 JSON 为请求的 HTTP 版本

    WechatIMG16">

  2. 如果服务器只支持 HTTP2,在模拟器请求时发生了一个 ALPN 协议的错误。并且提醒使用适配 HTTP1

    WechatIMG8">

  3. 当把服务器的 allowHTTP1,设置为 true,并在请求时处理相关相关请求参数后,模拟器能正常访问接口,并打印出对应的 HTTP 请求版本

    WechatIMG15

授权获取用户信息流程

  • session_key 有有效期,有效期并没有被告知开发者,只知道用户越频繁使用小程序,session_key 有效期越长
  • 在调用 wx.login 时会直接更新 session_key,导致旧 session_key 失效
  • 小程序内先调用 wx.checkSession 检查登录态,并保证没有过期的 session_key 不会被更新,再调用 wx.login 获取 code。接着用户授权小程序获取用户信息,小程序拿到加密后的用户数据,把加密数据和 code 传给后端服务。后端通过 code 拿到 session_key 并解密数据,将解密后的用户信息返回给小程序

面试题:先授权获取用户信息再 login 会发生什么?

  • 用户授权时,开放平台使用旧的 session_key 对用户信息进行加密。调用 wx.login 重新登录,会刷新 session_key,这时后端服务从开放平台获取到新 session_key,但是无法对老 session_key 加密过的数据解密,用户信息获取失败
  • 在用户信息授权之前先调用 wx.checkSession 呢?wx.checkSession 检查登录态,并且保证 wx.login 不会刷新 session_key,从而让后端服务正确解密数据。但是这里存在一个问题,如果小程序较长时间不用导致 session_key 过期,则 wx.login 必定会重新生成 session_key,从而再一次导致用户信息解密失败。

性能优化

我们知道view部分是运行在webview上的,所以前端领域的大多数优化方式都有用。

我们知道view部分是运行在webview上的,所以前端领域的大多数优化方式都有用。

我们知道view部分是运行在webview上的,所以前端领域的大多数优化方式都有用。

加载优化

preload">

代码包的大小是最直接影响小程序加载启动速度的因素。代码包越大不仅下载速度时间长,业务代码注入时间也会变长。所以最好的优化方式就是减少代码包的大小。

load-time-series">

小程序加载的三个阶段的表示。

优化方式

  • 代码压缩。
  • 及时清理无用代码和资源文件。
  • 减少代码包中的图片等资源文件的大小和数量。
  • 分包加载。

首屏加载的体验优化建议

  • 提前请求: 异步数据请求不需要等待页面渲染完成。
  • 利用缓存: 利用 storage API 对异步请求数据进行缓存,二次启动时先利用缓存数据渲染页面,在进行后台更新。
  • 避免白屏:先展示页面骨架页和基础内容。
  • 及时反馈:即时地对需要用户等待的交互操作给出反馈,避免用户以为小程序无响应。

使用分包加载优化

sub-package">

在构建小程序分包项目时,构建会输出一个或多个功能的分包,其中每个分包小程序必定含有一个主包,所谓的主包,即放置默认启动页面/TabBar 页面,以及一些所有分包都需用到公共资源/JS 脚本,而分包则是根据开发者的配置进行划分。

在小程序启动时,默认会下载主包并启动主包内页面,如果用户需要打开分包内某个页面,客户端会把对应分包下载下来,下载完成后再进行展示。

优点:

  • 对开发者而言,能使小程序有更大的代码体积,承载更多的功能与服务
  • 对用户而言,可以更快地打开小程序,同时在不影响启动速度前提下使用更多功能

限制:

  • 整个小程序所有分包大小不超过 8M
  • 单个分包/主包大小不能超过 2M

原生分包加载的配置 假设支持分包的小程序目录结构如下:

├── app.js
├── app.json
├── app.wxss
├── packageA
│   └── pages
│       ├── cat
│       └── dog
├── packageB
│   └── pages
│       ├── apple
│       └── banana
├── pages
│   ├── index
│   └── logs
└── utils

复制代码

开发者通过在 app.json subPackages 字段声明项目分包结构:

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

复制代码

分包原则

  • 声明 subPackages 后,将按 subPackages 配置路径进行打包,subPackages 配置路径外的目录将被打包到 app(主包) 中
  • app(主包)也可以有自己的 pages(即最外层的 pages 字段
  • subPackage 的根目录不能是另外一个 subPackage 内的子目录
  • 首页的 TAB 页面必须在 app(主包)内

引用原则

  • packageA 无法 require packageB JS 文件,但可以 require app、自己 package 内的 JS 文件
  • packageA 无法 import packageB 的 template,但可以 require app、自己 package 内的 template
  • packageA 无法使用 packageB 的资源,但可以使用 app、自己 package 内的资源

官方即将推出 分包预加载

preload-sub-package">

独立分包

single-sub-package">

渲染性能优化

render">

  • 每次 setData 的调用都是一次进程间通信过程,通信开销与 setData 的数据量正相关。

  • setData 会引发视图层页面内容的更新,这一耗时操作一定时间中会阻塞用户交互。

  • setData 是小程序开发使用最频繁,也是最容易引发性能问题的。

避免不当使用 setData

  • 使用 data 在方法间共享数据,可能增加 setData 传输的数据量。。data 应仅包括与页面渲染相关的数据。
  • 使用 setData 传输大量数据,**通讯耗时与数据正相关,页面更新延迟可能造成页面更新开销增加。**仅传输页面中发生变化的数据,使用 setData 的特殊 key 实现局部更新。
  • 短时间内频繁调用 setData,**操作卡顿,交互延迟,阻塞通信,页面渲染延迟。**避免不必要的 setData,对连续的setData调用进行合并。
  • 在后台页面进行 setData,**抢占前台页面的渲染资源。**页面切入后台后的 setData 调用,延迟到页面重新展示时执行。

one-context">

避免不当使用onPageScroll

  • 只在有必要的时候监听 pageScroll 事件。不监听,则不会派发。
  • 避免在 onPageScroll 中执行复杂逻辑
  • 避免在 onPageScroll 中频繁调用 setData
  • 避免滑动时频繁查询节点信息(SelectQuery)用以判断是否显示,部分场景建议使用节点布局橡胶状态监听(inersectionObserver)替代

使用自定义组件

在需要频繁更新的场景下,自定义组件的更新只在组件内部进行,不受页面其他部分内容复杂性影响。

官方小程序技术能力规划

自定义组件2.0

小程序的几个页面间,存在一些相同或是类似的区域,这时候可以把这些区域逻辑封装成一个自定义组件,代码就可以重用,或者对于比较独立逻辑,也可以把它封装成一个自定义组件,也就是微信去年发布的自定义组件,它让代码得到复用、减少代码量,更方便模块化,优化代码架构组织,也使得模块清晰,后期更好地维护,从而保证更好的性能。

但微信打算在原来的基础上推出的自定义组件 2.0,它将拥有更高级的性能:

  • usingComponents 计划支持全局定义和通配符定义:这意味着不用在每个页面反复定义,可以批量导入目录下的所有自定义组件
  • 计划支持类似 Computed 和 watch 的功能,它能使代码逻辑更清晰
  • 计划支持 Component 构造器插件,在实例化一个自定义组件的时候,允许你在构造器的这个阶段,加入一些逻辑,方便进行一些扩展,甚至是可以扩展成 Vue 的语法

npm支持

目前小程序开发的痛点是:开源组件要手动复制到项目,后续更新组件也需要手动操作。不久的将来,小程序将支持npm包管理,有了这

相关标签
免费获取专属 《策划方案 》及报价
免费体验我们的业务系统、OA系统、在线教育、电商系统、智慧办公等产品定制化方案,助力您的信息化发展之路