ASP源码.NET源码PHP源码JSP源码JAVA源码DELPHI源码PB源码VC源码VB源码Android源码
当前位置:首页 >> 软件工程 >> angular Angular移除不必要的$watch之性能优化

angular Angular移除不必要的$watch之性能优化(1/5)

来源:网络整理     时间:2017-11-24     关键词:angular

本篇文章主要介绍了"angular Angular移除不必要的$watch之性能优化",主要涉及到angular方面的内容,对于软件工程感兴趣的同学可以参考一下: 双向绑定是Angular的核心概念之一,它给我们带来了思维方式的转变:不再是DOM驱动,而是以Model为核心,在View中写上声明式标签。然后,Angular...

 Angular移除不必要的$watch之性能优化

双向绑定是Angular的核心概念之一,它给我们带来了思维方式的转变:不再是DOM驱动,而是以Model为核心,在View中写上声明式标签。然后,Angular就会在后台默默的同步View的变化到Model,并将Model的变化更新到View。

双向绑定带来了很大的好处,但是它需要在后台保持一只“眼睛”,随时观察所有绑定值的改变,这就是Angular 1.x中“性能杀手”的“脏检查机制”($digest)。可以推论:如果有太多“眼睛”,就会产生性能问题。在讨论优化Angular的性能之前,笔者希望先讲解下Angular的双向绑定和watchers函数。

双向绑定和watchers函数

为了能够实现双向绑定,Angular使用了$watch API来监控$scope上的Model的改变。Angular应用在编译模板的时候,会收集模板上的声明式标签 —— 指令或绑定表达式,并链接(link)它们。这个过程中,指令或绑定表达式会注册自己的监控函数,这就是我们所说的watchers函数。

下面以我们常见的Angular表达式({{}})为例。

HTML:

1
2
3
4
ng-app="com.ngnice.app" ng-c>"DemoController as demo">
hello : {{demo.count}}
type="button" ng-click="demo.increase ();">increase ++

JavaScript:

1
2
3
4
5
6
7
8
9
angular.module('com.ngnice.app')
.controller('DemoController', [function() {
var vm = this;
vm.count = 0;
vm.increase = function() {
vm.count++;
};
return vm;
}]);

这是一个自增长计数器的例子,在上面的代码我们用了Angular表达式({{}})。表达式为了能在Model的值改变的时候你能及时更新View,它会在其所在的$scope(本例中为DemoController)中注册上面提到的watchers函数,监控count属性的变化,以便及时更新View。

上例中在每次点击button的时候,count计数器将会加1,然后count的变化会通过Angular的$digest过程同步到View之上。在这里它是一个单向的更新,从Model到View的更新。如果处理一个带有ngModel指令的input交互控件,则在View上的每次输入都会被及时更新到Model之上,这里则是反向的更新,从View到Model的更新。

Model数据能被更新到View是因为在背后默默工作的$digest循环(“脏检查机制”)被触发了。它会执行当前scope以及其所有子scope上注册的watchers函数,检测是否发生变化,如果变了就执行相应的处理函数,直到Model稳定了。如果这个过程中发生过变化,浏览器就会重新渲染受到影响的DOM来体现Model的变化。

在Angular表达式({{}})背后的源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
function collectDirectives(node, directives, attrs, maxPriority, ignoreDirective) {
var nodeType = node.nodeType,
attrsMap = attrs.$attr,
match,
className;
switch (nodeType) {
case 1:
/* Element */
...
break;
case 3:
/* Text Node */
addTextInterpolateDirective(directives, node.nodeValue);
break;
case 8:
/* Comment */
...
break;
}
directives.sort(byPriority);
return directives;
}
function addTextInterpolateDirective(directives, text) {
var interpolateFn = $interpolate(text, true);
if (interpolateFn) {
directives.push({
priority: 0,
compile: function textInterpolateCompileFn(templateNode) {
// when transcluding a template that has bindings in the root
// then we don't have a parent and should do this in the linkFn
var parent = templateNode.parent(),
hasCompileParent = parent.length;
if (hasCompileParent) safeAddClass(templateNode.parent(), 'ng-binding');
return function textInterpolateLinkFn(scope, node) {
var parent = node.parent(),
bindings = parent.data('$binding') || [];
bindings.push(interpolateFn);
parent.data('$binding', bindings);
if (!hasCompileParent) safeAddClass(parent, 'ng-binding');
scope.$watch(interpolateFn, function interpolateFnWatchAction(value) {
node[0].nodeValue = value;
});
};
}
});
}
}

Angular会在compile阶段收集View模板上的所有Directive。Angular表达式会被解析成一种特殊的指令:addTextInterpolateDirective。到了link阶段,就会利用scope.$watch的API注册我们在上面提到的watchers函数:它的求值函数为$interpolate对绑定表达式进行编译的结果,监听函数则是用新的表达式计算值去修改DOM Node的nodeValue。可见,在View中的Angular表达式,也会成为Angular在$digest循环中watchers的一员。

在上面代码中,还有一部分是为了给调试器用的。它会在Angular表达式所属的DOM节点加上名为‘ng-binding’的调试类。类似的调试类还有‘ng-scope’,‘ng-isolate-scope’等。在Angular 1.3中我们可以使用compileProvider服务来关闭这些调试信息。

1
2
3
4
app.config(['$compileProvider', function ($compileProvider) {
// disable debug info
$compileProvider.debugInfoEnabled(false);
}]);

其它指令中的watchers函数

不仅Angular的表达式会使用$scope.$watch API添加watchers,Angular内置的大部分指令也一样,下面再举几个常用的Angular指令。

ngBind:它和Angular表达式很类似,都是绑定特定表达式的值到DOM的内容,并保持与scope的同步。不同之处在于它需要一个HTML节点并以attribute属性的方式标记。简单来说,我们可以认为Angular表达式就是ngBind的特定语法糖。当然,还是有一点区别的,详情参见“使用技巧”一章的“防止Angular表达式闪烁”。

1 2 3 4 5 下一页
上一篇编写多线程程序有几种实现方式? 下一篇响应式布局和BootStrap 全局CSS样式

angular相关图片

angular相关文章