稍稍程序组件化框架 WePY 在性质调优上做出的追

作者:龚澄

导语

性能调优是一个以来不变换的话题,无论是以风俗H5上或者稍序中。因为实现机制不同,可能致传统H5中之某些优化措施以聊序上连无适用。因此必须另开辟蹊径找来适合小程序的调估方式。

正文旨在介绍两触及于稍序开发进程当中碰到的片段特性问题和 WePY
的片段优化方案。

多少序组件化框架 WePY
介绍请阅读:《打造“微信小程序”组件化开发框架》

事先加载

原理

风土H5中吗得以透过预加载来提升用户体验,但每当小程序中成就这或多或少其实是可以还简明方便可同时还易受忽略的。

风土H5在启动时,page1.html 只会加载 page1.html
的页面和逻辑代码,当page1.html 跳反至 page2.html 时,page1 所有的
Javascript 数据以见面从内存中消灭。page1 与 page2 之间的数目通信只能通过
URL 参数传递或者浏览器的 cookie,localStorge 存储处理。

些微程序在起步时,会一直加载所有页面逻辑代码进内存,即便 page2
可能都非会见叫采用。在 page1 跳反到 page2 时,page1 的逻辑代码 Javascript
数据也不见面从内存中消失。page2 甚至足以一直看 page1 中的多寡。

尽简单易行的证明办法尽管是在 page1 中入一个
setInterval(function () {console.log('exist')}, 1000)。传统H5中跳转后定时器会活动消失,小序中跳转后定时器仍然工作。

稍序的这种机制差异正好可以再好的兑现预加载。通常状态下,我们习惯将数据拉取写以
onLoad 事件备受。但是有些序的 page1 跳反到 page2,到 page2 的 onLoad
是是一个 300ms ~ 400ms 的延时的。如下图:

图片 1

为有点序的性状,完全可以当 page1 中先行将得到多少,然后以 page2
中一直采用数据,这样尽管可以规避 redirecting 的 300ms ~ 400ms了。如下图:

图片 2

试验

于法定demo中进入两独页面:page1,page2

// page1.js 点击事件中记录开始时间
bindTap: function () {
  wx.startTime = +new Date();
  wx.navigateTo({
    url: '../page2/page2'
  });
}


// page2.js 中假设从服务器拉取数据需要500ms
fetchData: function (cb) {
  setTimeout(function () {
    cb({a:1});
  }, 500);
},
onLoad: function () {
  wx.endTime = +new Date();
  this.fetchData(function () {
    wx.endFetch = +new Date();
    console.log('page1 redirect start -> page2 onload invoke -> fetch data complete: ' + (wx.endTime - wx.startTime) + 'ms - ' + (wx.endFetch - wx.endTime) + 'ms');
  });
}

重试10不善,得到的结果如下:

图片 3

优化

对上述问题,WePY 中查封装了有限种植概念去化解:

  • 预加载数据
    用于 page1 主动传递数据给 page2,比如 page2
    需要加载一卖耗时坏丰富之数据。我得在 page1 闲时先加载好,进入 page2
    时直就是可利用。
  • 先期查询数据
    用于避免给 redirecting 延时,在跳转时调用 page2 预查询。
    扩大了生命周期,添加了onPrefetch事件,会在 redirect
    之常叫主动调用。同时受onLoad事件上加了一个参数,用于吸纳预加载或者是预先查询的数额:

    // params
    // data.from: 来源页面,page1
    // data.prefetch: 预查询数据
    // data.preload: 预加载数据
    onLoad (params, data) {}

预加载数据示例:

// page1.wpy 预先加载 page2 需要的数据。

methods: {
  tap () {
    this.$redirect('./page2');
  }
},
onLoad () {
  setTimeout(() => {
    this.$preload('list', api.getBigList())
  }, 3000)
}

// page2.wpy 直接从参数中拿到 page1 中预先加载的数据
onLoad (params, data) {
  data.preload.list.then((list) => render(list));
}

优先查询数据示例:

// page1.wpy 使用封装的 redirect 方法跳转时,会调用 page2 的 onPrefetch 方法
methods: {
  tap () {
    this.$redirect('./page2');
  }
}

// page2.wpy 直接从参数中拿到 onPrefetch 中返回的数据
onPrefetch () {
  return api.getBigList();
}
onLoad (params, data) {
  data.prefetch.then((list) => render(list));
}

数据绑定

原理

于对数据绑定做优化时,需要先了解小程序的运行机制。因为视图层与逻辑层的通通分离,所以二者之间的通信全都依赖让
WeixinJSBridge 实现。如:

  • 开发者工具被凡是依据 window.postMessage
  • iOS中基于 window.webkit.messageHandlers.invokeHandler.postMessage
  • Android中基于WeixinJSCore.invokeHandler

从而数据绑定方法this.setData为如此,频繁之数码绑定就长了通信的基金。再来看看this.setData究竟开了什么事情。基于开发者工具的代码,单步调试大致还原出一体化的流程,以下是尚原后的代码:

/*
setData 主流程精简还原,并非完整主流程,内有注释
*/
function setData (obj) {
    if (typeof(obj) !== 'Object') {
        console.log('类型错误'); // 并没有预期中的return;
    }
    let type = 'appDataChange';

    // u.default.emit(e, this.__wxWebviewId__) 代码还原
    let e = [type, {
                data: {data: list}, 
                options: {timestamp: +new Date()}
            },
            [0] // this.__wxWebviewId__
    }];

    // WeixinJSBridge.publish.apply(WeixinJSBridge, e); 代码还原
    var datalength = JSON.stringify(e.data).length;  // 第一次 JSON.stringify
    if (datalength > AppserviceMaxDataSize) { // AppserviceMaxDataSize === 1048576
        console.error('已经超过最大长度');
        return;
    }

    if (type === 'appDataChange' || type === 'pageInitData' || type === '__updateAppData') {

        // sendMsgToNW({appData: __wxAppData, sdkName: "send_app_data"}) 代码还原
        __wxAppData = {
            'pages/page1/page1': alldata
        }
        e = { appData: __wxAppData, sdkName: "send_app_data" }

        var postdata = JSON.parse(JSON.stringify(e)); // 第二次 JSON.stringify 第一次 JSON.parse
        window.postMessage({
            postdata
        }, "*");
    }


    // sendMsgToNW({appData: __wxAppData, sdkName: "send_app_data"}) 代码还原
    e = {
        eventName: type,
        data: e[1],
        webviewIds: [0],
        sdkName: 'publish'
    };

    var postdata = JSON.parse(JSON.stringify(e));  // 第三次 JSON.stringify 第二次 JSON.parse
    window.postMessage({
        postdata
    }, "*");
}

setData 运行的流程如下:

图片 4

自从上面代码和流程图中可见见,在平等差setData({a: 1})作时,会开展三软
JSON.stringify,二次JSON.parse暨个别软window.postMessage操作。并且于率先浅window.postMessage时常,并无是单独就就处理传递的{a:1},而是处理当下页面的保有
data
数据。因此可想而知每次setData操作的出是异常坏之,只能通过减数据量,以及减少setData操作来规避。

setData 相近的是 ReactsetState 方法,同样是行使 setState
去更新视图的,可以由此源码 React:L199 看到 setState 的重中之重代码如下:

function enqueueUpdate(component) {
  ensureInjected();
  if (!batchingStrategy.isBatchingUpdates) {
    batchingStrategy.batchedUpdates(enqueueUpdate, component);
    return;
  }
  dirtyComponents.push(component);
}

setState的工作流程如下:

图片 5

可见见,setState 加入了一个缓冲列队,在同样执行流程中展开反复 setState
之后也未会见再渲染视图,这就算是一律种很好之优化措施。

实验

为验证setData的属性问题,可以写简单的测试例子去测试:

动态绑定1000长数的列表进行性测试,这里测试了三栽情况:

  • 无限优良绑定:在内存中补充加完后最终执行setData操作。
  • 最差绑定:在长相同长达记下执行同样次于setData操作。
  • 顶智能绑定:不管中间展开了呀操作,在运行了时实施同一软污染检查,对需安装的数据开展setData操作。

参考代码如下:

// page1.wxml
<view bindtap="worse">
  <text class="user-motto">worse数据绑定测试</text>
</view>
<view bindtap="best">
  <text class="user-motto">best数据绑定测试</text>
</view>
<view bindtap="digest">
  <text class="user-motto">digest数据绑定测试</text>
</view>

<view class="list">
  <view wx:for="{{list}}" wx:for-index="index"wx:for-item="item">
      <text>{{item.id}}</text>---<text>{{item.name}}</text>
  </view>
</view>


// page1.js
worse: function () {
   var start = +new Date();
   for (var i = 0; i < 1000; i++) {
     this.data.list.push({id: i, name: Math.random()});
     this.setData({list: this.data.list});
   }
   var end = +new Date();
   console.log(end - start);
},
best: function () {
  var start = +new Date();
  for (var i = 0; i < 1000; i++) {
    this.data.list.push({id: i, name: Math.random()});
  }
  this.setData({list: this.data.list});
  var end = +new Date();
  console.log(end - start);
},
digest: function () {
  var start = +new Date();
  for (var i = 0; i < 1000; i++) {
    this.data.list.push({id: i, name: Math.random()});
  }
  var data = this.data;
  var $data = this.$data;
  var readyToSet = {};
  for (k in data)  {
    if (!util.$isEqual(data[k], $data[k])) {
      readyToSet[k] = data[k];
      $data[k] = util.$copy(data[k], true);
    }
  }
  if (Object.keys(readyToSet).length) {
    this.setData(readyToSet);
  }
  var end = +new Date();
  console.log(end - start);
},
onLoad: function () {
  this.$data = util.$copy(this.data, true);
}

当经十次刷新运行测试后得出以下结果:

图片 6

实现均等的逻辑,性能数据却离40加倍左右。由此可看来,在出过程遭到,一定要是避与一流程内多次
setData 操作。

优化

在支付时,避免以同一流程内多次动setData自然是超级实践。采取人工维护得是能落实之,就吓于会为此原生
js
能写来比较许多框架还迅速的性质相同。但当页面逻辑负责起来之后,花大可怜的精力去维护还未必然能够保证每个流程就在同样潮setData,而且可维护性也未赛。因此,WePY选择用污染检查去举行多少绑定优化。用户不用还担心在自家的流程里,数据给涂改了多少坏,只见面在流水线最后做同次等污染检查,并且以需要实施setData

污迹检测机制借鉴自AngularJS,多数人数同一听到脏检查都见面觉得是不如效率的一样栽作法,认为使
Vue.js 中之
getter,setter更便捷。其实不然,两种体制还是指向同项事之两样实现方式。各有上下,取决于使用的人头于使过程遭到是不是正好放大了体制被之劣势面。

WePY 中的 setData 就好比是一个
setter,在历次调用时都见面失去渲染视图。因此要再包一叠 getter、setter
就全盘没意义,没有外优化可言。这也就是怎一个类 Vue.js
的多少程序框架却选择了同之相反的另外一栽多少绑定方式。

再度返回看脏检查的题目在乌,从者实验的代码可以望,脏检查的属性问题在每次进行水污染检查时,需要遍历所以数据以作值的坏于,性能在遍历以及比较数据的分寸。WePY
中颇于是以的 underscore 的 isEqual
方法。为了验证效率问题,使用不同之可比艺术对一个 16.7 KB 的扑朔迷离 JSON
数据开展大于,测试用例请看这里:deep-compare-test-case
(https://jsperf.com/deep-compare/4)

取的结果如下:

图片 7

自打结果来拘禁,对于一个 16.7 KB 的数码异常于是了不足以产生性能问题的。那
AngularJS 1.x 脏检查的习性问题是怎出现的为?

AngularJS 1.x 中从未组件的概念,页面数据就是在 controller 的 $scope
当中。每一样涂鸦污染检查都是于 $rootScope 开始,随后遍历至具备子
$scope。参考这里 angular.js:L1081。对于一个大型的单页应用来说,所有
$scope
中之数或许达成了好多甚至上千独还来或。那时,脏检查的每次遍历就可能确实会成为了性的瓶颈了。

回顾 WePY,使用类似于 Vue.js
的组件化开发,在抛开父子组件双向绑定通信的动静下,组件的肮脏检查只有对组件本身的多寡进行,一个零件的数据一般不见面尽多,数据最多时方可细化组件划分的粒度。因此在这种状况下,脏检查并无会见造成性问题。

事实上,在很多情况下,框架封装的化解方案都非是性质优化的不过优良解决方案,使用原生肯定能够优化来重新快的代码。但她之所以在以有价,那还是因其是以性能、开发效率、可维护性上摸到一个平衡点,这吗是怎
WePY 选择下污染检查作为数据绑定的优化。

其余优化

除此之外上述两接触是因性达到做出的优化以外,WePY
也作出了一样密密麻麻开发效率及之优化。因为于自己前的篇章里还产生详尽说明,所以于这边虽大概列举一下,不做深入探讨。详情可参见
WePY 文档。

组件化开发

支撑组件循环、嵌套,支持组件 Props 传值,组件事件通信等等。

parent.wpy
<child :item.sync="myitem" />

<repeat for="{{list}}" item="item" index="index">
   <item :item="item" />
</repeat>

支撑添加的编译器

js 可以挑选用 Babel 或者 TypeScript 编译。
wxml 可以选取以 Pug(原Jade)。
wxss 可以选下 Less、Sass、Styus。

支持添加的插件处理

得经过安排插件对转移的js进行削减混淆,压缩图片,压缩 wxml 和 json
已节省空间等等。

支持 ESLint 语法检查

丰富一行配置就得支持 ESLint
语法检查,可以避免低级语法错误以及联合路代码的风格。

生命周期优化

补加了 onRoute 的生命周期。用于页面跳反后点。
以并无存在一个页面跳转事件(onShow
事件可以作为页面跳转事件,但以为存在负作用,比如以 HOME
键后切回来,或者拉于出后收回,拉于分享后取消且见面触发 onShow 事件)。

支持 Mixin 混合

好活的进行不同组件之间的同等效果的复用。参考 Vue.js 官方文档: 混合

优化事件,支持从定义事件

bindtap=”tap” 简写为 @tap=”tap”,catchtap=”tap”简写为@tap.stop=”tap”。
对组件还提供组件自定义事件

<child @myevent.user="someevent" />

优化事件传参

官方版如下:

<view data-alpha-beta="1" data-alphaBeta="2" bindtap="bindViewTap"> DataSet Test </view>
Page({
  bindViewTap:function(event){
    event.target.dataset.alphaBeta === 1 // - 会转为驼峰写法
    event.target.dataset.alphabeta === 2 // 大写会转为小写
  }
})

优化后:

<view @tap="bindViewTap("1", "2")"> DataSet Test </view>

methods: {
  bindViewTap(p1, p2, event) {
    p1 === "1";
    p2 === "2";
  }
}

结语

稍加程序还在重重值得开发者去追优化的地方,欢迎大家和自己探讨交流开发心得。若本文存在未可靠的地方,欢迎批评指正。


再次多精彩内容欢迎关注腾讯 Bugly的微信公众账号:

图片 8

腾讯
Bugly举凡一模一样慢慢悠悠专为走开发者打造的色监督工具,帮助开发者快速,便捷的定位线上应用崩溃的气象及解决方案。智能合并功能帮助开发同学把每天上报的数千久
Crash
根据根因合并分类,每日日报会列有影响用户数最多之夭折,精准定位功能帮助开发同学定位及有问题的代码行,实时举报可以当通告后很快的垂询下之质量情况,适配最新的
iOS, Android 官方操作系统,鹅厂的工程师还于利用,快来加盟我们吧!

相关文章