首页 > 代码库 > App之性能优化

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             // destory15             // 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 on13             this.factoryObject[name] = service();14         }15         module.prototype.controller = function(name,args){16             var _self  = this;17             //init18             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条黄金守则

 

App之性能优化