zoukankan      html  css  js  c++  java
  • 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条黄金守则

  • 相关阅读:
    bzoj1415 NOI2005聪聪和可可
    Tyvj1952 Easy
    poj2096 Collecting Bugs
    COGS 1489玩纸牌
    COGS1487 麻球繁衍
    cf 261B.Maxim and Restaurant
    cf 223B.Two Strings
    cf 609E.Minimum spanning tree for each edge
    cf 187B.AlgoRace
    cf 760B.Frodo and pillows
  • 原文地址:https://www.cnblogs.com/stenson/p/3951035.html
Copyright © 2011-2022 走看看