• App之性能优化


    一般来说,浏览器的内存泄漏对于 web 应用程序来说并不是什么问题。用户在页面之间切换,每个页面切换都会引起浏览器刷新。即使页面上有内存泄漏,在页面切换后泄漏就解除了。由于泄漏的范围比较小,因此常常被忽视。

    但在移动端,内存泄漏就成了一个比较严重的问题。在单面应用中,用户不能刷新页面的,整个应用程序构建在一个页面上。在这种情况下泄漏会被累积,导致内存不被回收。

    Javascript中的垃圾回收机制类似于Java/C#这类语言中的回收机制:

    一个对象不再被引用,即将被自动回收

    具体回收时刻是我们无法控制的,我们只需适当地解除对象的引用,剩下的事,让运行时去做吧。

    在我们开发过程中,往往稍不留神,内存泄露了我们可能都不会察觉:

    例1:

    1 function doFn(){
    2    bigString=new Array(1000).join(new Array(2000).join("XXXXX"));
    3 }

    不论是你不小心少写了个var,还是觉得这样写很cool,执行doFn(),即退出函数作用域后,bigString会被回收掉么?

    不会被回收,bigString现在成为了全局对象window的一个属性,在应用的整个生命周期,window都是一直存在的,所以其属性是不会被销毁的。

    例2:

    1 var doFn=(function(){ 
    2 var bigString=new Array(1000).join(new Array(2000).join("XXXXX")); 
    3    return  function(){
    4       console.dir(bigString);
    5    } ; 
    6 })();

    上面代码运行后,bigString会被回收么?

    不会被回收,闭包里的数据是不会被释放的。

    例3:

    <intput type=”button” value=”submit”  id=”submit” />
    1 (function(){
    2   var Zombie=function(){};
    3   var zombie=new Zombie;
    4   var print=function(){
    5      console.dir(zombie);
    6   };
    7   var node=document.getElementById(‘submit’);
    8   node.addEventListener('click',print,false);
    9 })()

    运行代码后,事件处理函数执行正常,会打印zombie到控制台,而且这里会发生内存泄露,zombie一直不能被回收。

    也许有人会说,离开这个页面,zombie就会被释放。在单页应用中,离开当前页面,实质是,移除页面上body内的所有DOM元素,然后再把新的HTML追加至body的DOM树上。

    所以,我们来移除button这个节点:

    1 node.parentNode.removeChild(node);

    执行之后,我们发现页面上按钮被移除了。现在,zombie对象应该被回收了吧?

    我们用chrome浏览器的Heap Profiler来追踪下内存,下面是内存快照:

    发现即使移除DOM节点,内存泄露一样存在。当我们在移除元素的同时移除其上的事件时,发现这次zombie被回收了:

    1 node.parentNode.removeChild(node);
    2 node.removeEventListener(‘click’,print,false);

    再次追踪内存,已经没有在Zombie类型的对象遗留在内存中了。

    所以,我们得出一个结论:移除一个DOM元素的同时,也要移除元素上面的事件,不然很可能会发生内存泄露,伤你于无形。

    说到这里,我就想起了zepto里的移除元素的remove方法:

    1 remove: function(){
    2   return this.each(function(){
    3      if(this.parentNode != null)
    4        this.parentNode.removeChild(this)
    5   })
    6 }

    说好的要移除元素上面的事件呢?

    另外我们对比下zepto和jQuery里的empty方法:

    zepto的empty方法:

    1 empty: function(){
    2      return this.each(function(){ this.innerHTML = '' })
    3 }

    jQuery的empty方法:

     1 empty: function() {
     2     var elem,i = 0;
     3     for ( ; (elem = this[i]) != null; i++ ) {
     4            if ( elem.nodeType === 1 ) {
     5            // Prevent memory leaks
     6            jQuery.cleanData( getAll( elem, false ) );
     7            // Remove any remaining nodes
     8            elem.textContent = "";
     9         }
    10     }
    11     return this;
    12 }

    API文档里还有这么一句话:

    To avoid memory leaks, jQuery removes other constructs such as data and event handlers from the child elements before removing the elements themselves.

    可见,对于移除DOM元素时,jQuery处理要更为严谨和合理。

    在模块化编程时,当我们会用RequireJS来组织代码时,有一种情况是需要注意的:

    1 define([],function(){
    2    var obj={
    3        bigString:new Array(1000).join(new Array(2000).join("XXXXX"));
    4        //
    5    };
    6    return obj;
    7 });

    当这个模块作为一个数据源时,在某个地方被加载一次后,即时当前视图已不再需要它,它还会一直保留在内存中。也就是说,返回值为一个对象时,它是不会被释放的。

    至于为何这样,你可以想想,我们define一个类后,能通过require来调用它,那么它肯定是在什么地方被保存了起来。所以,我们这个obj在RequireJs内部也会被引用,无法释放。

    也许你会问,那你干嘛要返回一个对象呢?我想,有时候,你应该也是这么做的。

    另外,不知道大家的Controller层是如何写的,我是让它继承Backbone.Router的:

     1  jass.Controller = Backbone.Router.extend({
     2         module: "",
     3         name: "",
     4         _bindRoutes: function () {
     5             if (!this.routes) return;
     6             this.routes = _.result(this, 'routes');
     7             var route, routes = _.keys(this.routes);
     8             var prefix = this.module + "/" + this.name + "/";
     9             while ((route = routes.pop()) != null) {
    10                 this.route(prefix + route, this.routes[route]);
    11             }
    12         },
    13         close: function () {
    14             // destory
    15             // remove actions from history.Handlers ???
    16             this.stopListening();
    17             this.off();
    18             this.trigger('destroy');
    19         }
    20 });

    这样写也会内存泄露,我们跟踪下router方法:

    1 this.route(prefix + route, this.routes[route]);  // this -->controller

    controller被引用了,它是无法释放的。如果在Controller层上面再引用了Model层表示的数据,泄露将会更加严重。

    另外,我这里企图作一些清理工作的close方法根本就没有时机去触发。

    我们简化Controller逻辑,它只负责向View层传递Model层的数据时,在多数情况下是会降低泄露的发生。

    但是,我们经常会面临这样的问题:

    1 多个View之间共享数据;

    2 多个Controller之间共享数据;

    这时数据应该保存在哪,该何时被清理掉?

    为了解决上面的问题,我希望从AngularJS中能得到一些启发,发现它的概念还是挺多的。然后找到AngularJS中依赖注入的模拟代码:

     1 var angular = function(){};
     2  
     3 Object.defineProperty(angular,"module",{
     4     value:function(modulename,args){
     5         var module = function(){
     6             this.args = args;
     7             this.factoryObject = {};
     8             this.controllerObject = {};
     9         }
    10         module.prototype.factory = function(name,service){
    11             //if service is not a function ... 
    12             //if service() the result is not a object ... and so on
    13             this.factoryObject[name] = service();
    14         }
    15         module.prototype.controller = function(name,args){
    16             var _self  = this;
    17             //init
    18             var content = {
    19                 $scope:{},
    20                 scope:function(){
    21                     return content.$scope;
    22                 }
    23             //  $someOther:{...}
    24             }
    25  
    26             var ctrl = args.pop();
    27             console.log(typeof ctrl);
    28             var factorys = [];
    29             while(service = args.shift()){
    30                 if(service in content){
    31                     factorys.push(content[service])
    32                 }else{
    33                     factorys.push(_self.factoryObject[service])
    34                 }
    35                  
    36             }
    37             ctrl.apply(null,factorys);
    38  
    39             _self.controllerObject[name] = function(){
    40                 return content;
    41             };
    42         }
    43         var m = new module();
    44         window[modulename] = m;
    45         return m;
    46     }
    47 })

    测试:

     1 var hello = angular.module('Test');
     2  
     3 hello.factory("actionService",function(){
     4     var say = function(){
     5         console.log("hello")
     6     }
     7     return {
     8         "say":say
     9     }
    10 })
    11  
    12 hello.controller("doCtrl",['$scope',"actionService",function($scope,actionService){
    13     $scope.do = function(){
    14         actionService.say();
    15     }
    16 }]);
    17  
    18 hello.controllerObject.doCtrl().scope().do()

    可见,AngularJS中构造的模块,控制器也是不会被释放的。

    在单页应用开发中,更要警惕内存泄露问题,不然它会是性能优化的一个巨大绊脚石。

    性能优化,是一个永久的话题,以后有所感悟,再来补充,持续更新!

    最近在研究Sencha Touch,期待有趣的发现!

    更多有关性能优化的讨论,推荐阅读:

    Memory leaks

    Memory leak patterns in JavaScript

    Writing Fast,Memory-Efficient JavaScript

    Backbone.js And JavaScript Garbage Collection

    雅虎网站页面性能优化的34条黄金守则

  • 相关阅读:
    基于Spring框架的Shiro配置(转发:http://kdboy.iteye.com/blog/1103794)
    Apache Shiro 使用手册(五)Shiro 配置说明(转发:http://kdboy.iteye.com/blog/1169637)
    Apache Shiro 使用手册(四)Realm 实现(转发:http://kdboy.iteye.com/blog/1169631)
    iOS定位服务与地图开发(4)---显示地图
    iOS定位服务与地图开发(3)---地理信息编码查询
    iOS定位服务与地图开发(2)---地理信息反编码
    iOS定位服务与地图开发(1)---获取经纬度
    社交网络编程(4)--- 通过SLRequest接收tweet
    社交网络编程(3)--- 通过SLRequest分享内容
    社交网络编程(2)---SLComposeViewController
  • 原文地址:https://www.cnblogs.com/stenson/p/3951035.html
Copyright © 2020-2023  润新知