1. 簡介
無論你正在編寫一個舊的應用程式還是在一個大型應用程式中採用AngularJS,效能是一個重要的方面。了解是什麼原因導致AngularJS應用程式慢下來非常重要,要知道,在開發過程中做出權衡是很重要的。本文將介紹一些AngularJS較常見的效能問題,以及最佳化的建議。
2. 效能測試工具
本文採用jsPerf http://jsperf.com/ 效能測試的基準。
3. 軟體效能
評估軟體效能有兩個基本的因素:
首先是演算法的時間複雜度。一個簡單的例子就是線性搜尋和二分檢索有著非常顯著的效能差距。
第二個軟體緩慢的原因稱為空間複雜度。這是一台電腦需要多少「空間」或記憶體來運行你的應用程式。記憶體需求越多,運行速度就越慢。
4 Javascript的效能
有些效能問題不只是Angular帶來的,而是JavaScript本來就有的。
4.1 循環
避免在循環內部呼叫函數,可以移到外部呼叫。
var sum = 0; for(var x = 0; x < 100; x++){ var keys = Object.keys(obj); sum = sum + keys[x]; }
上面的面向明顯沒有下面的快:
var sum = 0; var keys = Object.keys(obj); for(var x = 0; x < 100; x++){ sum = sum + keys[x]; }
4.2 DOM訪問
在取得DOM元素時要注意
angular.element('div.elementClass')
這種方式是非常昂貴的。其實這在AngularJS中並不會造成太大的問題。但是留意一下是有好處的。 DOM樹要小,DOM的訪問要盡可能的少。
4.3 變數作用範圍垃圾回收
把你的變數作用範圍限制越緊密越好,這樣垃圾回收器就可以更快地回收空間。注意下面的問題:
function demo(){ var b = {childFunction: function(){ console.log('hi this is the child function') }; b.childFunction(); return b; }
當這個函數終上了,這裡就沒有到b的引用。 b就會被回收了。但是如果有這樣一行:
var cFunc = demo();
這個引用就會阻止垃圾回收。要盡量避免這類引用。
4.4 陣列與物件
這裡有很多點:
例如:
for (var x=0; x<arr.length; x++) { i = arr[x].index; }
比這一種快一點(注* arr為數組, obj為json對象)
for (var x=0; x<100; x++) { i = obj[x].index; }
比這一種快一點
var keys = Object.keys(obj); for (var x = 0; x < keys.length; x++){ i = obj[keys[x]].index; }
5 重要的概念
我们已经讨论过有关JavaScript的性能,现在有必要看一看AngualrJS中的核心概念,看看它究竟是怎么运作的。
5.1 域(Scopes)和更新周期(Digest Cycle)
Angular的域本质上是一些JavaScript对象,它们从一些预定义的对象继承而来。基本上,小的域比大的域运行要快。
换句话说,每创建一个新的域,都会给垃圾回收器添加更多待回收的内容。
在写AngularJS应用中尤其要注意的一个核心概念和性能影响方面是更新周期(Digest Cycle)。实际上每一个域都会存放一个由方法组成的数组 $$watchers。
每当域中的一个值(属性)或绑定的DOM,如 ng-repeat,ng-switch 和 ng-if 等等,调用 $watch 时,一个函数(function)就会添加到相对应域中的$$watchers数组队列中。
当域中的值发生改变时,在$$watchers中所有的watchers函数都会被触发调用。并且当它们的任何一个修改了域中的某个值时,它们会被再次触发执行。
这个过程会一直循环下去直到$$watcher数组队列中不再做任何更改或抛出异常为止。
更外如果任何代码执行$scope.$apply(),都会触发更新周期。
最后一点是 $scope.evalAsync() 会在一个异步调用中执行,并且在当前和下个执行周期中,不会调用其的更新周期。
6. 在设计Angular时应该遵守的一般准则
6.1 大型对象和服务器调用
所以这些都告诉了我们什么?首先我们要尽可能地简化我们的对象。当对象是从服务器返回时,这一点尤为重要。
直接将数据库中的一行转换成对象只是临时性方案,因此不要使用.toJson().
只需要把Angular需要的属性值返回回来。
6.2 监视函数(Watching Functions)
另一个常见的问题是为观察者绑定的函数。不要将任何东西(ng-show, ng-repeat等等)直接绑定到一个函数。不要直接监视任何函数的返回值。该函数会在每个更新周期都执行,可能会降低你应用的速度。
6.3 监视对象(Watching Objects)
同样,Angular提供了第三个可选参数来监视整个对象的改动。将调用$watch的第三个参数设为true。这是一个非常可怕的想法。一个更好的解决办法是依靠服务和对象的引用,监视域之间的变化。
7 列表问题
7.1 长列表(Lists)
尽一些可能避免长列表。ng-repeat会进行了一些很重的DOM操作(更不用说对$$watchers的污染),所以无论是在分页或是在无限滚动中,尽量使用小型数据进行渲染。
7.2 过滤器(Filters)
要尽量避免使用过滤器。他们会在每个更新周期运行两次,每当发生任何改变时运行一次,另一次是收集更深层次的改变时触发。所以不要直接从内部列表中移除对象,使用CSS控制即可。(注* 用添加CSS类名去隐掉他们)
渲染时的 $index 值并不是真正的数组索引值,它豪无价值。但是排好序的数组索引,无法让你遍历到所有列表中的域。
7.3 更新 ng-repeat
当使用ng-repeat时要尽量避免对全局列表的刷新。ng-repeat会产生一个$$hashkey属性和一系统唯一的项。这意味着当你调用 scope.listBoundToNgRepeat = serverFetch() 时会引起对整个列表的重新刷新。会通知执行所有的watchers并触发每一个元素,这是非常消耗性能的。
这里有两种解决方案。一种是维护两个集合,和带有过虑器(filter)的ng-repeat(基本上需要自定义同步逻辑,因此算法更复杂,可维护性更差),另一种方案是使用track by去指定你自己的key(Angular 1.2 开始支持,只需要很少的同步逻辑)。
总之:
scope.arr = mockServerFetch();
会比下面的这种慢
var a = mockServerFetch(); for(var i = scope.arr.length - 1; i >=0; i--){ var result = _.find(a, function(r){ return (r && r.trackingKey == scope.arr[i].trackingKey); }); if (!result){ scope.arr.splice(i, 1); } else { a.splice(a.indexOf(scope.arr[i]), 1); } } _.map(a, function(newItem){ scope.arr.push(newItem); });
这种
<div ng-repeat="a in arr track by a.trackingKey">
比上面的慢些
<div ng-repeat="a in arr">
8 渲染问题
另一个引起Angular应用慢的原因是不正确地使用 ng-hide/ ng-show 或 ng-switch。
ng-hide 和 ng-show 简单地对CSS display属性进行切换。这意味着表面上看不见的东西其实还存在于域中, 所有的$$watchers还是会被触发。
ng-if 和 ng-switch实际上从DOM中完全移除了,相应的域也会被移除。性能差异显而易见。
9. 更新周期问题
9.1 绑定
尽量减少你的绑定。在Angular 1.3中这里有一个新的一次绑定语法,{{::scopeValue}}。它只会被域执行一次,并不添加到监视器要监视列表中(watcher array).
9.2 $digest() 和 $apply()
scope.$apply 是一个强大的工具,可以让你向Angular引入外部的值。本质上它会触发Angular的所有事件(例如ng-click)。问题是scope.$apply会从根域$rootScope开始,遍历所有的域链,触发每一个域。
scope.$digest只会执行指定域及其相关的域。两种性能差异不言自明。折中的方案是,不触发任何域等到下一个更新周期再更新。
9.3 $watch()
scope.$watch() 已经在很多场景被讨论过的。基本上scope.$watch是不好的设计的一个标志。如果你非要创建一个观察者。记住对它尽可能地解绑。你可以用$watch的返回函数解绑。
var unbinder = scope.$watch('scopeValueToBeWatcher', function(newVal, oldVal) { }); unbinder(); //这一行将watcher从 $$watchers 中移除。
如果你不能早一点解绑,记住在 $on('$destroy') 中进行解绑。
9.4 $on, $broadcast 和 $emit
像$watch一样,他们都是一些很慢的事件,(有可能)遍历整个作用域。他们可能像GOTO一样,让你的程序无法调试。不过幸运地是像$watch一样,他们都可以在完全不需要的时侯解绑。比如在 $on('$destroy')中。
9.5 $destroy
像前面提到的那样,你应该在$on('$destroy')中解绑你所有的事件侦听器,取消任何$timeout的实例,或者任何其它异步执行的交互。这不仅仅是确保安全。还可以让你的域更快地被垃圾回收。不这样做,他们会一直在后台运行。直接你清空CPU和RAM。
另外,解绑DOM上的事件侦听器也非常重要,不这样做很可能在老式浏览器中引起内存泄露。
9.6 $evalAsync
scope.$evalAsync是一个强大的工具。它可以在当前域中执行,并不触发域的更新。evalAsync可以极大地提高你网页的性能。
10 指令问题
10.1 隔离的域(Isolate Scope)和Transclusion
域隔离和Transclusion是Angular最另人激动的特性,它们是Angular的核心组件。
但是这里也有一些权衡,指令不能直接创建一个替换他们父组元素的域。通过隔离的域或Transclusion我们可以创建一个新的对象去跟踪,添加新的监视器,但是这也会降低应用的性能。在添加之前应该仔细想一想有没有这个必要。
10.2 编绎周期
指令(Directive)的compile函数是在域被附加前操作DOM的完美功能(比如说绑定事件)。一个很重要的性能方面是,传入compile函数的元素和属性以原始html模板呈现。只会被运行一次,接下来会直接使用。另外一个重要的点是prelink和postlink的区别。prelink从外向内执行。postlinks从内向外执行。prelink性能稍好一些,因为它不会产生第二次更新周期。但是这时子元素的DOM还未被创建。
11 DOM事件问题
Angular提供了很多预定义的DOM事件指令。ng-click,ng-mouseenter,ng-mouseleave等等。当调用scole.$apply()时这些事件都会被执行。另外一种更有效率的方式是直接在DOM上面绑定addEventListener,并且尽量使用scope.$digest
优化实例
测试一个应用框架确实是个严峻的挑战,当用户点击日志中任何一个单词,我们就要搜索出相关信息,而页面上可以点击的元素又不计其数;我们想让日志的分页功能也瞬间得到反馈。我们其实已经预先获取到了下一页面的日志数据,所以用户接口的更新就成为了瓶颈,如果拿 AngularJS直接实现日志视图的换页功能需要1.2秒,但是如果仔细优化一下的话就可以降到35毫秒。这些优化被证明在应用的其他部分也是适用的,并且对AngularJS适应性也很好。但我们必须打破一些规则来实现我们的想法,稍后讨论。
一个Github更新的日志demo
An AngularJS log viewer
本质上,日志视图就是一个日志消息的列表,每个字都可以点击。所以把Angular的指令加到DOM元素中,简单实现如下:
<span class='logLine' ng-repeat='line in logLinesToShow'> <span class='logToken' ng-repeat='token in line'>{{token | formatToken}} </span> </span>
在单页面应用中有个数千个tokens是很正常的,在早期的测试中,我们发现进入日志的下一页会花费好几秒来执行JavaScript。更糟的是,不相关的操作(比如点击导航下拉框)延迟也不轻,AngularJS的大神说最好把数据元素绑定的数量控制在200以下。对于一个单词就是一个元素的我们来说,早已远超这个数。
分析:
用Chrome的JavaScript profiler工具,我们可以快速定位两个拖延点。首先,每次更新要花大量时间在DOM元素的创建和销毁上,如果新的view有不同的行数,或者任何一行有不同数量单词,Angular的ng-repeat指令就会创建或者销毁DOM元素,这个代价太大了。
其次,每一个单词都有自己的change watcher,AngularJS会watch这些单词,一旦鼠标点击就会触发,这个是影响不相关操作(下拉菜单导航)延迟的罪魁祸首。
优化#1:缓存DOM elements
我们创建了一个ng-repeat指令的变体,在我们的版本中,如果绑定数据的数量减少了,超出的DOM元素会隐藏而不是销毁,如果元素的数量过会儿有增加了,我们会重用这些缓存的元素。
优化#2:Aggregate watchers
用来调用change watchers的所有时间大部分都浪费了,在我们的应用中,特定单词上的数据绑定都是永远不会改变的除非整个日志消息变化,为了达成这一点,我们创建了一个指令”hides“隐藏掉了子元素的change watchers,只有等特定父元素表达式修改的时候才会调用他们。就这样,我们避免了在每一次鼠标点击或者其他微小的修改而导致的全盘change watchers(为了实现这个想法,我们稍微修改了AngularJS的抽象层,我们稍后再细说)。
优化#3:推迟元素创建
前面说了,我们为日志里的每一个单词单独创建了DOM,我们可以利用每一行的单个DOM元素得到相同的视觉呈现;其他元素都是为响应鼠标点操作而创建的,因此,我们决定推迟这部分创建,只有当鼠标移动到某行的时候我们再创建他。
为了实现这个,我们为每一行创建了两个版本,一个就是简单的文本元素来显示完整的日志信息,另外一行就是个占位符,用来显示最终为每一个单词填充后的效果。这个占位符开始是隐藏的,当鼠标移动到那一行的时候才会显示,而简单文本那一行这个时候就隐藏掉。下面会讲到,显示占位符是如何填充单词元素的。
优化#4:避开对隐藏元素的监视
我们创建了另外一个指令,用来阻止对隐藏元素的监视,这个指令支持优化#1,相较于原数据,我们多了更多的隐藏DOM节点,所以必须消除对多出来的DOM节点的监视。这也支持优化#3,让推迟单词节点的创建更加容易。因为直到这行数据的tokenized版本出现我们才会创建他 。
下面的代码就是所有的优化后的样子,我们自定义的指令是粗体显示。
<span class='logLine' sly-repeat='line in logLinesToShow' sly-evaluate-only-when='logLines'> <div ng-mouseenter=”mouseHasEntered = true”> <span ng-show='!mouseHasEntered'>{{logLine | formatLine }} </span> <div ng-show='mouseHasEntered' sly-prevent-evaluation-when-hidden> <span class='logToken' sly-repeat='tokens in line'>{{token | formatToken }}</span> </div> </div> </span>
Sly-repeat 是ng-repeat的变体,用来隐藏多出来的DOM元素而不是销毁他们,sly-evaluate-only-when阻止内部change watchers除非“logLines”变量修改,sly-prevent-evaluation-when-hidden主要负责当鼠标移动到指定行的上面的时候,隐藏的div才显示。
这里展示出了AngularJS对于封装和分离的控制力,我们做了复杂的优化但是并没有影响模板的结构(这里展示的代码并不是真正产品里的代码,但是他展示了所有的要点)。
结果:
我们来看一下效果,我们添加了一些代码来衡量,从鼠标点击开始,一直到Angular's $digest循环结束(意味着更新DOM结束)。
我们衡量点击”下一页“按钮的性能是通过Tomcat日志,环境用的是MacBook Pro上的Chrome,结果见下表(每个数据都是10次测试的平均值):
数据已经缓存 | 从服务器获取数据 | |
简单实现 | 1190 ms | 1300 ms |
优化后 | 35 ms | 201 ms |
這些資料不包括瀏覽器用在DOM版面和重繪(JavaScript執行完成後)的時間,每次大概30毫秒。儘管如此,效果也顯而易見;下一頁的回應時間從1200毫秒驟降至35毫秒(如果算上渲染是65毫秒)。
「從伺服器取得資料」裡的資料包含了我們使用AJAX從後端取得log資料的時間。這個跟點擊下一頁按鈕不同,因為我們預先取下一頁的log數據,但或許適用於其他的UI回應。即使這樣,優化後的程式也可以做到即時更新。

电脑性能看如下几个方面:1、电脑安装的操作系统的版本;2、电脑所配置的处理器类型;3、电脑安装的内存大小;4、操作系统是32位的还是64位的。

Vue3是一款流行的JavaScript框架,它具有易学易用、高效稳定的特点,尤其擅长构建单页应用程序(SPA)。Vue3中的lazy函数,作为懒加载组件的利器之一,可以很大程度上提高应用程序的性能。本文将详解Vue3中的lazy函数的使用方法与原理,以及它在实际开发中的应用场景和优点。什么是懒加载?在传统的前后端分离的开发中,前端开发人员往往需要处理大量的

MySQL是一种常用的关系型数据库管理系统(RDBMS),在各种应用场景下都得到广泛的应用。然而,在高并发、大数据量的情况下,MySQL数据库的性能受到挑战,特别是在读写操作频繁的场景下,容易出现性能瓶颈。为了提高MySQL数据库的性能,可以通过设置MySQL缓存来减少数据库的IO操作,从而提高MySQL的查询效率。在本文中,我们将介绍如何通过设置MySQL

Javascript 是一个非常有个性的语言. 无论是从代码的组织, 还是代码的编程范式, 还是面向对象理论都独具一格. 而很早就在争论的Javascript 是不是面向对象语言这个问题, 显然已有答案. 但是, 即使 Javascript 叱咤风云二十年, 如果想要看懂 jQuery, Angularjs, 甚至是 React 等流行框架, 观看《黑马云课堂JavaScript 高级框架设计视频教程》就对了。

随着深度强化学习技术的快速发展,越来越多的研究团队开始将其应用于自动驾驶决策规划中,将行为决策与运动规划模块相融合,直接学习得到行驶轨迹。 自动驾驶中的决策规划模块是衡量和评价自动驾驶能力最核心的指标之一,它的主要任务是在接收到传感器的各种感知信息之后,对当前环境作出分析,然后对底层控制模块下达指令。典型的决策规划模块可以分为三个层次:全局路径规划、行为决策、运动规划。01 引言在一套完整的自动驾驶系统中,如果将感知模块比作人的眼睛和耳朵,那么决策规划就是自动驾驶的大脑。大脑在接收到传感器的各种

昨天一个跑了220个小时的微调训练完成了,主要任务是想在CHATGLM-6B上微调出一个能够较为精确的诊断数据库错误信息的对话模型来。不过这个等了将近十天的训练最后的结果令人失望,比起我之前做的一个样本覆盖更小的训练来,差的还是挺大的。这样的结果还是有点令人失望的,这个模型基本上是没有实用价值的。看样子需要重新调整参数与训练集,再做一次训练。大语言模型的训练是一场军备竞赛,没有好的装备是玩不起来的。看样子我们也必须要升级一下实验室的装备了,否则没有几个十天可以浪费。从最近的几次失败的微调训练来看

在如今信息时代,网站已经成为人们获取信息和交流的重要工具。一个响应式的网站能够适应各种设备,为用户提供优质的体验,成为了现代网站开发的热点。本篇文章将介绍如何使用PHP和AngularJS搭建一个响应式网站,从而提供优质的用户体验。PHP介绍PHP是一种开源的服务器端编程语言,非常适用于Web开发。PHP具有很多优点,如易于学习、跨平台、丰富的工具库、开发效

在本文中,我们将进行TPU与GPU的比较。但在我们深入研究之前,这是你必须知道的。机器学习和人工智能技术加速了智能应用的发展。为此,半导体公司不断创建加速器和处理器,包括TPU和CPU,以处理更复杂的应用程序。一些用户在理解何时建议使用TPU以及何时使用GPU来完成他们的计算机任务时遇到了问题。GPU也称为图形处理单元,是您PC的视频卡,可为您提供视觉和身临其境的PC体验。例如,如果您的PC未检测到GPU,您可以按照简单的步骤操作。为了更好地理解这些情况,我们还需要澄


熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

AI Hentai Generator
免費產生 AI 無盡。

熱門文章

熱工具

記事本++7.3.1
好用且免費的程式碼編輯器

ZendStudio 13.5.1 Mac
強大的PHP整合開發環境

SAP NetWeaver Server Adapter for Eclipse
將Eclipse與SAP NetWeaver應用伺服器整合。

mPDF
mPDF是一個PHP庫,可以從UTF-8編碼的HTML產生PDF檔案。原作者Ian Back編寫mPDF以從他的網站上「即時」輸出PDF文件,並處理不同的語言。與原始腳本如HTML2FPDF相比,它的速度較慢,並且在使用Unicode字體時產生的檔案較大,但支援CSS樣式等,並進行了大量增強。支援幾乎所有語言,包括RTL(阿拉伯語和希伯來語)和CJK(中日韓)。支援嵌套的區塊級元素(如P、DIV),

SecLists
SecLists是最終安全測試人員的伙伴。它是一個包含各種類型清單的集合,這些清單在安全評估過程中經常使用,而且都在一個地方。 SecLists透過方便地提供安全測試人員可能需要的所有列表,幫助提高安全測試的效率和生產力。清單類型包括使用者名稱、密碼、URL、模糊測試有效載荷、敏感資料模式、Web shell等等。測試人員只需將此儲存庫拉到新的測試機上,他就可以存取所需的每種類型的清單。