# 小程序启动流程

  1. 包拉取
    • 包大小
  2. 代码注入
    • js 到jscore
    • wxml,wxss 到渲染
  3. 首页创建
    • onLaunch - onLoad
  4. 首页渲染
    • onReady - onLoad
  5. 首页可交互
    • 接口返回 - onLoad

# 运行问题

  1. 卡顿,渲染时间长
    • setData
      setData 耗时的起始点是在逻辑层发起调用时,终止点是渲染层完成渲染并通知到逻辑层的时候,所以 setData 耗时可以最直观的反映出页面的渲染情况。
  2. 闪退
    • memoryWarning
  3. 白屏
    • jsError
    • requestError
    • resoureceFail
  4. 加载
    • 接口耗时
    • 页面实现

# 数据收集

通过劫持微信的生命周期方法和部分微信的 api 计算耗时,它的优点是对业务代码几乎零侵入,对业务逻辑影响小,可移植性强。

wx.getperfotmance // 获取启动耗时以及代码注入时间
function sizeof(object) {
  const objectList = [];
  const stack = [object];
  let bytes = 0;

  while (stack.length) {
    const value = stack.pop();
    if (typeof value === 'boolean') {
      bytes += 4;
    } else if (typeof value === 'string') {
      bytes += value.length * 2;
    } else if (typeof value === 'number') {
      bytes += 8;
    } else if (typeof value === 'object' && objectList.indexOf(value) === -1) {
      objectList.push(value);

      for (let i in value) {
        stack.push(value[i]);
      }
    }
  }
  return bytes;
}
console.time('setData');
this.setData({
  key: value,
}, () => {
  console.timeEnd('setData');
});
app.onError
app.onUnhandledRejection
wx.onMemoryWarning
// 在page on ready 时候进行采样
// 假设我们已经获取到了采样点元素和容器元素的 id
let sampleElementId = 'sample-element';
let containerElementId = 'container-element';

// 使用 createSelectorQuery API 获取元素信息
let query = wx.createSelectorQuery();

// 获取采样点元素信息
query.select(`#${sampleElementId}`).boundingClientRect();

// 获取容器元素信息
query.select(`#${containerElementId}`).boundingClientRect();

query.exec((results) => {
  // results 是一个数组,包含了我们查询的所有元素的信息
  let sampleElement = results[0];
  let containerElement = results[1];

  // 判断两个元素是否相同
  if (sampleElement.id === containerElement.id) {
    console.log('采样点元素与容器元素相同');
  } else {
    console.log('采样点元素与容器元素不相同');
  }
});

# 监控用户行为

小程序捕获tap、touchmove事件。
重写Page对象,在用户的 tap 和 touchmove 事件中插入自定义的行为。
遍历页面对象的所有属性,如果属性是一个函数,那么它就用一个新的函数替换这个方法。新的函数会检查第一个参数是否是一个 tap 或 touchmove 事件,如果是,那么它就调用 gestureTrigger 函数,并给事件对象添加一个特殊的标记,以防止事件被无限透传。然后,新的函数调用原始的方法,传入原始的参数。

export function replacePage() {
  const originPage = Page;
  Page = function (pageOptions) {
    function gestureTrigger(e) {
      // 给事件对象增加特殊的标记,避免被无限透传
      e.__miTopProcessed = true;
      // 拿到用户行为回调参数e
      console.log('e', e);
    }

    const listenerTypes = ['touchmove', 'tap'];
    Object.keys(pageOptions).forEach((m) => {
      if (typeof pageOptions[m] === 'function') {
        const originMethod = pageOptions[m];
        pageOptions[m] = function (...args) {
          const e = args[0];
          if (e && e.type && e.currentTarget && !e.__miTopProcessed) {
            if (listenerTypes.indexOf(e.type) !== -1) {
              gestureTrigger(e);
            }
          }
          return originMethod.apply(this, args);
        };
      }
    });

    return originPage.call(this, pageOptions);
  };
}

# 性能优化通用方法

  1. 轻量:需要尽量精简数据大小、资源体积、代码逻辑、接口请求等。
    • 代码懒注入
    • 包体积
    • vuex store 按需
  2. 提前:将页面渲染需要的环境、数据等提前准备好,例如数据预拉取、接口数据缓存等。
    • 初始渲染缓存
      在小程序页面第一次被打开后,将页面初始数据渲染结果记录下来,写入一个持久化的缓存区域(缓存可长时间保留,但可能因为小程序更新、基础库更新、储存空间回收等原因被清除);在这个页面被第二次打开时,检查缓存中是否还存有这个页面上一次初始数据的渲染结果,如果有,就直接将渲染结果展示出来;
            {
                "window": {
                    "initialRenderingCache": "static"
                }
        }
    
  3. 并行:减少没有必要的串行等待过程,例如在 page 创建的时候异步准备页面需要的数据。
  4. 实时:用户操作永远是最高优先级,需要给出实时的 ui 反馈,例如骨架屏、loading 信息等。
    • 分片渲染
      如果 setData 过于频繁或者数据量过大,会导致渲染线程繁忙。而我们能够操控的各种点击、滚动事件将拥堵在 webview js 线程上,得不到响应
    • 图片渲染
      用两个image节点,先展示缩略图,等高清图加载完之后再替换上
    • 虚拟列表
      用空白节点顶掉已经在可视区间的节点