首页 > 前端技术 > angularJs项目实战!04:angularjs的性能问题
2013
09-23

angularJs项目实战!04:angularjs的性能问题

上一篇文章中我花了很多口舌去介绍angularjs是一个中型框架,面对大型应用时少不了第三方类库的配合。而我的核心议题是:如何以angularjs的思路使用其他类库,这里jquery是最好的例子了,谁让它争议最大。许多人一看到jquery就火冒三丈冲过来觉得这货是影响代码结构、打破angular way的纯洁的罪魁祸首,但事实是好的木匠总是会允许烂木头的存在,何况jquery并非烂木头。只要配合得当jquery完全可以做你项目中的小伙伴,而如果不改变传统的思路,就算用了angularjs你的代码也不容易维护。

不过对于angularjs本身来说,以上这个问题纯粹是使用者的理念和设计方法的问题,并不重要。重要的问题是,angularjs是个中型框架,做一个大型应用,往大了讲功能略显不足(比如缺少好用的UI插件和异步模块载入机制),往小了说又不够灵活(例如不能无缝与第三方路由整合)。但这些都不是最要命的。功能不足用第三方类库补嘛,不够灵活就学会适应吧,但最要命的问题是,性能效率问题。

怎么个性能效率问题呢?你可以自己测试一下:在一个页面上搞500+个不同的ng-model,然后看看页面的渲染效率会低到什么程度就知道了。

有人会说,谁没事干在一个页面上搞那么多ng-model啊!这肯定是写页面的人模块没有划分好!但事实是,这种情况很容易出现,例如,在显示一个angularjs实现的长列表的时候。

我大半年前曾经欢喜于angularjs的特性,而苦于没有一个好的基于angularjs的grid(表格)插件,于是自己动手瞎写了一个,叫anGrid. 代码已经很久没有维护了。但是还能用。 各位大爷轻喷。https://github.com/zhangdiwaa/anGrid

这个程序一开始运行无误,在显示20行X11列的列表(约造成了250个ng-model)毫无问题,反应也很快。配合angularjs的过滤器,排序、替换图标什么的就是手到擒来啊!

但问题很快发生了,我用了一个测试数据,造成了大约250行X11列的大列表(约造成了2900个ng-model),结果这个列表在chrome浏览器下居然渲染了20秒才出来,我还以为电脑死机了。测试了很多次都是如此。我认为是自己代码写的太烂。直到我做了1000+的ng-model测试,和看了同学侯振宇的博客以及angularjs源码才觉悟过来,其实angularjs也不是十全十美的。

Talk is cheap,我们直接来看内部实现吧。

angularjs双向绑定的核心是$digest方法。这个函数会直接检测“所有的数据模型”是否改动,有改动就去更新相应的视图元素。但事实上,看了源码我们就会发现这个$digest方法执行效率不会高。3重大循环吓死人,还要监听同步。连大神自己都在代码注释里吐槽说:yes,this code is a bit crazy.
 

$digest方法,请注意注释部分,中间有删截(感谢侯振宇的截图)

对于这个性能问题,只有换个框架才能改变。

我的同学侯振宇他们所在的团队,为此专门搞了个全新的MVVM框架avalon,性能超过angularjs 10倍——ng-model超过1万都很快。原理是将数据模型中的属性用get 和set 方法重写。在set方法中去更新所有和当前数据模型有关的视图元素,这就是为什么avalon一更新数据就能马上反映到视图上、并且性能更出众的原因。直接翻到avalon源码的“modelFactory”函数,在这个函数中avalon收集和当前模型有关的视图元素、其他相关联的数据,最后注册到属性中。其中更详细的原理可以直接参考作者 司徒正美 的github https://github.com/RubyLouvre/avalon

那如果不想换掉angularjs怎么办?那就努力把每页的ng-model控制在500以下吧。就上面那个angularjs实现gird表格插件的问题,其实用点“hack”方法也能搞定。假设要显示250行X11列的gird表格,那么就显示20行的元素和滚动条,让其他的元素不渲染不显示。只有当滚动条向下滑的时候才渲染新的元素,同时消除旧的元素。更进一步的还可以检测滚动条滚动的速度,滚动得慢就一行一行得预渲染,滚动得快就在停下的时候预渲染。如此就能始终把ng-model的数量控制在一个可以接受的范围内了。(嘛,这不就跟angular ui 里的ng-grid一样了吗!)

最后,我推荐一下我的同学侯振宇的博客 http://www.cnblogs.com/sskyy/ ,其在技术敏感性和钻研劲都比我强,嘛,也是他身为创业公司小头目的工作需要。而我呢,则要将更多的精力,从技术使用和项目开发上挪到技术研究和理论研究上。嘛,都是工作需要。

 


angularJs项目实战!04:angularjs的性能问题》有 3 条评论

    • 张迪 张迪 说:

      非常感谢!破狼的博客我一直在追。但是最近一段时间在赶项目所以疏于浏览他们的博客了。他的这个方法用于“100%确定不会改变的数据”非常好,因为页面上往往不是所有内容都是需要双向绑定,的确可以改善性能,虽然治标不治本,但是足够有用。
      我最早的项目是用php做后台,路由什么的都是后台的php开发框架codeIgniter做处理,angularjs只是在一些页面上使用用来做双向绑定。当然这种情况不能说是通过服务端来控制前端的路由。我现在的项目是使用angularjs的路由,它这个路由一旦使用就会强制,替换成其他的前端路由很困难,AngularJS不能无缝与第三方路由整合,只能用它自己的模块,如”ui-status“。
      对于你提的这个路由的问题,我并没仔细研究过,也没有这样的需求,所以也很难提出建设性的意见。现阶段我只能想到:要让服务器端提供对外的api,需要在服务器端做路由,这和客户端的路由不冲突,只要不重名。与其耗费精力让后端控制前端路由,不如让他们分别做好自己的路由。

    • 张迪 张迪 说:

      非常感谢!破狼的博客我一直在追。但是最近一段时间在赶项目所以疏于浏览他们的博客了。他的这个方法用于“100%确定不会改变的数据”非常好,因为页面上往往不是所有内容都是需要双向绑定,的确可以改善性能,虽然治标不治本,但是足够有用。
      我最早的项目是用php做后台,路由什么的都是后台的php开发框架codeIgniter做处理,angularjs只是在一些页面上使用用来做双向绑定。当然这种情况不能说是通过服务端来控制前端的路由。我现在的项目是使用angularjs的路由,它这个路由一旦使用就会强制,替换成其他的前端路由很困难,AngularJS不能无缝与第三方路由整合,只能用它自己的模块,如”ui-status“。
      对于你提的这个路由的问题,我并没仔细研究过,也没有这样的需求,所以也很难提出建设性的意见。现阶段我只能想到:要让服务器端提供对外的api,需要在服务器端做路由,这和客户端的路由不冲突,只要不重名。与其耗费精力让后端控制前端路由,不如让他们分别做好自己的路由。

留下一个回复

你的email不会被公开。