本文引自:https://www.cnblogs.com/zhoulujun/p/8881414.html

这篇是对angularJS的一些疑点回顾,是对目前angularJS开发的各种常见问题的整理汇总。如果对文中的题目全部了然于胸,觉得对整个angular框架应该掌握的七七八八了。希望志同道合的通知补充内容

Angular 的数据绑定采用什么机制,详述原理?

脏检查机制。阐释脏检查机制,必须先了解如下问题。

单向绑定(ng-bind) 和 双向绑定(ng-model) 的区别?

ng-bind 单向数据绑定($scope -> view),用于数据显示,简写形式是 {{}}。

两者的区别在于页面没有加载完毕 {{val}} 会直接显示到页面,直到 Angular 渲染该绑定数据(这种行为有可能将 {{val}} 让用户看到);而 ng-bind 则是在 Angular 渲染完毕后将数据显示。

ng-model 是双向数据绑定(scope−>viewandview−>scope−>viewandview−>scope),用于绑定值会变化的表单元素等。

双向数据绑定是 AngularJS 的核心机制之一。当 view 中有任何数据变化时,会更新到 model ,当 model 中数据有变化时,view 也会同步更新,显然,这需要一个监控。

双向数据绑定的原理?

Angular 在 scope 模型上设置了一个 监听队列,用来监听数据变化并更新 view 。

   每次绑定一个东西到 view 上时 AngularJS 就会往 watch队列里插入一条watch队列里插入一条watch,用来检测它监视的 model 里是否有变化的东西。    

当你写下表达式如{{ val }}时,AngularJS在幕后会为你在scope模型上设置一个watcher(表达式将被 Angular 编译成一个监视函数),它用来在数据发生变化的时候更新view。这里的watcher和你会在AngularJS中设置的watcher是一样的:

1
2
3
$scope.$watch('val'function(newValue, oldValue) {
  //update the DOM with newValue
});
  • 将数据附加到 Scope 上,数据自身不会对性能产生影响,如果没有监视器来监视这个属性,那个这个属性在不在 Scope 上是无关重要的;Angular 并不会遍历 Scope 上的属性,它将遍历所有的观察器。

  • 每个监视函数是在每次 $digest 过程中被调用的。因此,我们要注意观察器的数量以及每个监视函数或者监视表达式的性能。

$digest循环是在什么时候以各种方式开始的?

当浏览器接收到可以被 angular context 处理的事件时,digest循环就会触发,遍历所有的digest循环就会触发,遍历所有的watch,最后更新 dom。

举个栗子

1
<button ng-click="val=val+1">increase 1</button>

click 时会产生一次更新的操作(至少触发两次 $digest 循环)

  • 按下按钮

  • 浏览器接收到一个事件,进入到 angular context

  • digest循环开始执行,查询每个digest循环开始执行,查询每个watch 是否变化

  • 由于监视 scope.val的scope.val的watch 报告了变化,因此强制再执行一次 $digest 循环

  • 新的 $digest 循环未检测到变化

  • 浏览器拿回控制器,更新 $scope. val.新值对应的 dom

在调用了scope.scope.digest()后,digest循环就开始了。假设你在一个ng−click指令对应的handler函数中更改了scope中的一条数据,此时AngularJS会自动地通过调用digest循环就开始了。假设你在一个ng−click指令对应的handler函数中更改了scope中的一条数据,此时AngularJS会自动地通过调用digest()来触发一轮digest循环。当digest循环。当digest循环开始后,它会触发每个watcher。这些watchers会检查scope中的当前model值是否和上一次计算得到的model值不同。如果不同,那么对应的回调函数会被执行。调用该函数的结果,就是view中的表达式内容(译注:诸如{{ val }})会被更新。除了ng-click指令,还有一些其它的built-in指令以及服务来让你更改models(比如ng-model,timeout等)和自动触发一次timeout等)和自动触发一次digest循环。

目前为止还不错!但是,有一个小问题。在上面的例子中,AngularJS并不直接调用digest(),而是调用digest(),而是调用scope.apply(),后者会调用apply(),后者会调用rootScope.$digest()。因此,一轮digest循环在digest循环在rootScope开始,随后会访问到所有的children scope中的watchers。

通常写代码时我们无需主动调用 apply或apply或digest 是因为 angular 在外部对我们的回调函数做了包装。例如常用的 ng-click,这是一个指令(Directive),内部实现则 类似 于

1
2
3
DOM.addEventListener('click'function ($scope) {
  $scope.$apply(() => userCode());
});

可以看到:ng-click 帮我们做了 apply这个操作。类似的不只是这些事件回调函数,还有apply这个操作。类似的不只是这些事件回调函数,还有http、timeout等。我听很多人抱怨说angular这个库太大了什么都管,其实你可以不用它自带的这些服务(Service),只要你记得手工调用timeout等。我听很多人抱怨说angular这个库太大了什么都管,其实你可以不用它自带的这些服务(Service),只要你记得手工调用scope.$apply。

现在,假设你将ng-click指令关联到了一个button上,并传入了一个function名到ng-click上。当该button被点击时,AngularJS会将此function包装到一个wrapping function中,然后传入到scope.scope.apply()。因此,你的function会正常被执行,修改models(如果需要的话),此时一轮$digest循环也会被触发,用来确保view也会被更新。

Note: scope.scope.apply()会自动地调用rootScope.rootScope.digest()。apply()方法有两种形式。第一种会接受一个function作为参数,执行该function并且触发一轮apply()方法有两种形式。第一种会接受一个function作为参数,执行该function并且触发一轮digest循环。第二种会不接受任何参数,只是触发一轮$digest循环。我们马上会看到为什么第一种形式更好。

$digest 循环会运行多少次?

$digest 循环的上限是 10 次(超过 10次后抛出一个异常,防止无限循环)。

$digest 循环不会只运行一次。在当前的一次循环结束后,它会再执行一次循环用来检查是否有 models 发生了变化。

这就是脏检查(Dirty Checking),它用来处理在 listener 函数被执行时可能引起的 model 变化。因此 digest循环会持续运行直到model不再发生变化,或者digest循环会持续运行直到model不再发生变化,或者digest 循环的次数达到了 10 次(超过 10 次后抛出一个异常,防止无限循环)。

当 $digest 循环结束时,DOM 相应地变化。

脏检查如何被触发?

angular 会在可能触发 UI 变更的时候进行脏检查:这句话并不准确。实际上,

脏检查是digest执行的,另一个更常用的用于触发脏检查的函数apply——其实就是 $digest 的一个简单封装(还做了一些抓异常的工作)。

通常写代码时我们无需主动调用 apply或apply或digest 是因为 angular 在外部对我们的回调函数做了包装。例如常用的 ng-click,这是一个指令(Directive),内部实现则 类似于

1
2
3
DOM.addEventListener('click'function ($scope) {
  $scope.$apply(() => userCode());
});

angular对常用的dom事件,xhq事件作了封装,如果调用这些封装,就会在里面触发进入angular的digest流程,主要有以下情况:

  • DOM事件,如用户输入文本,点击按钮等,(ng-click)

  • XHQ响应事件($http)

  • 浏览器Location变更事件,即Url中hash部分变更($location)

  • Timer事件(Timeout,Timeout,interval)

  • 手动调用apply或apply或digest

apply()和apply()和digest() 的区别?

apply是apply是scope(或者是 direcvie 里的 link 函数中的 scope)的一个函数,调用它会强制一次 digest循环(除非当前正在执行循环,这种情况下会抛出一个异常,这是我们不需要在那里执行digest循环(除非当前正在执行循环,这种情况下会抛出一个异常,这是我们不需要在那里执行apply 的标志)。

apply()和apply()和digest() 有两个区别。

  • 1) 最直接的差异是, $apply 可以带参数,它可以接受一个函数,然后在应用数据之后,调用这个函数。所以,一般在集成非 Angular 框架(比如jQuery)的代码时,可以把代码写在这个里面调用。

  • 2) 当调用 digest的时候,只触发当前作用域和它的子作用域上的监控,但是当调用digest的时候,只触发当前作用域和它的子作用域上的监控,但是当调用apply 的时候,会触发作用域树上的所有监控。

什么时候手动调用 $apply() 方法?

取决于是否在 Angular 上下文环境(angular context)。

AngularJS对此有着非常明确的要求,就是它只负责对发生于AngularJS上下文环境中的变更会做出自动地响应(即,在apply()方法中发生的对于models的更改)。AngularJS的built−in指令就是这样做的,所以任何的model变更都会被反映到view中。但是,如果你在AngularJS上下文之外的任何地方修改了model,那么你就需要通过手动调用apply()方法中发生的对于models的更改)。AngularJS的built−in指令就是这样做的,所以任何的model变更都会被反映到view中。但是,如果你在AngularJS上下文之外的任何地方修改了model,那么你就需要通过手动调用apply()来通知AngularJS。这就像告诉AngularJS,你修改了一些models,希望AngularJS帮你触发watchers来做出正确的响应。

典型的需要调用 $apply() 方法的场景是:

  • 1) 使用了 JavaScript 中的 setTimeout() 来更新一个 scope model

  • 2) 用指令设置一个 DOM 事件 listener 并且在该 listener 中修改了一些 models

场景一

1
2
3
4
5
6
7
$scope.setMsg = function() {
    setTimeout(function() {
        $scope.message = 'hello world';
        console.log('message:' + $scope.message);
    }, 2000);
}
$scope.setMsg();

运行这个例子,会看到过了两秒钟之后,控制台确实会显示出已经更新的 model,然而,view 并没有更新。

在 scope.getMessage加入scope.getMessage加入apply() 方法。

1
2
3
4
5
6
7
8
$scope.getMessage = function() {
    setTimeout(function() {
        $scope.$apply(function() {
            $scope.message = 'hello world';
            console.log('message:' + $scope.message);
        });
    }, 2000);
}

再运行就 OK 了。

不过,在 AngularJS 中应该尽量使用 timeoutService来代替setTimeout(),因为前者会帮你调用timeoutService来代替setTimeout(),因为前者会帮你调用apply(),让你不需要手动地调用它。

场景二

实现一个 click 的指令,类似以下功能,directive 的编写如下:

1
2
3
4
5
6
7
app.directive("inc"function() {
    return function (scope, element, attr) {
        element.on("click"function() {
            scope.val++;
        });
    };
});

跟场景一的结果一样,这个时候,点击按钮,界面上的数字并不会增加。但查看调试器,发现数据确实已经增加了。

在 scope.val++; 一行后面添加 scope.apply();或者scope.apply();或者scope.digest(); 就 OK 了。

$apply() 方法的两种形式

1
2
3
4
5
6
//无参
$scope.$apply()
//有参
$scope.$apply(function(){
    ...
})

应该总使用接受一个 function 作为参数的 apply()方法。这是因为当传入一个function到apply()方法。这是因为当传入一个function到apply() 中的时候,这个 function 会被包装到一个 try…catch 块中,所以一旦有异常发生,该异常会被 $exceptionHandler service 处理。

想象一下如果有个 alert 框显示错误给用户,然后有个第三方的库进行一个网络调用然后失败了,如果不把它封装进 $apply 里面,Angular 永远不会知道失败了,alert 框就永远不会弹出来了。

在 AngularJS 中使用 $watch注意事项?

如果要监听的是一个对象,那还需要第三个参数

1
2
3
4
5
$scope.data.name = 'htf';
$scope.$watch('data'function(newValue, oldValue) {
    if (newValue === oldValue) { return; }
    $scope.updated++;
}, true);

表示比较的是对象的值而不是引用,如果不加第三个参数 true ,在 data.name 变化时,不会触发相应操作,因为引用的是同一引用。

脏检查的范围

前面说到:angular 会对所有绑定到 UI 上的表达式做脏检查。其实,在 angular 实现内部,所有绑定表达式都被转换为 scope.scope.watch()。每个 watch记录了上一次表达式的值。有ng−bind="a"即有watch记录了上一次表达式的值。有ng−bind="a"即有scope.watch(′a′,callback),而watch(′a′,callback),而scope.$watch 可不会管被 watch 的表达式是否跟触发脏检查的事件有关。

例如:

1
2
3
4
5
<div ng-show="false">
  <span id="span1" ng-bind="content"></span>
</div>
<span id="span2" ng-bind="content"></span>
<button ng-click="">TEST</button>

问:点击 TEST 这个按钮时会触发脏检查吗?触发几次?

首先:ng-click="" 什么都没有做。angular 会因为这个事件回调函数什么都没做就不进行脏检查吗?不会。

然后:#span1 被隐藏掉了,会检查绑定在它上面的表达式吗?尽管用户看不到,但是 scope.scope.watch('content', callback) 还在。就算你直接把这个 span 元素干掉,只要 watch 表达式还在,要检查的还会检查。

再次:重复的表达式会重复检查吗?会。

最后:别忘了 ng-show="false"。可能是因为 angular 的开发人员认为这种绑定常量的情况并不多见,所以 $watch 并没有识别所监视的表达式是否是常量。常量依旧会重复检查。

所以:

答:触发三次。一次 false,一次 content,一次 content

所以说一个绑定表达式只要放在当前 DOM 树里就会被监视,不管它是否可见,不管它是否被放在另一个 Tab 里,更不管它是否与用户操作相关。

另外,就算在不同 Controller 里构造的 scope也会互相影响,别忘了angular还有全局的scope也会互相影响,别忘了angular还有全局的rootScope,你还可以 scope.scope.emit。angular 无法保证你绝对不会在一个 controller 里更改另一个 controller 生成的 scope,包括 自定义指令(Directive)生成的 scope 和 Angular 1.5 里新引入的组件(Component)。

所以说不要怀疑用户在输入表单时 angular 会不会监听页面左边导航栏的变化。

如何优化脏检查与运行效率

脏检查慢吗?

说实话脏检查效率是不高,但是也谈不上有多慢。简单的数字或字符串比较能有多慢呢?十几个表达式的脏检查可以直接忽略不计;上百个也可以接受;成百上千个就有很大问题了。绑定大量表达式时请注意所绑定的表达式效率。建议注意一下几点:

  • 表达式(以及表达式所调用的函数)中少写太过复杂的逻辑

  • 不要连接太长的 filter(往往 filter 里都会遍历并且生成新数组)

  • 不要访问 DOM 元素。

1、使用单次绑定减少绑定表达式数量
单次绑定(One-time binding 是 Angular 1.3 就引入的一种特殊的表达式,它以 :: 开头,当脏检查发现这种表达式的值不为 undefined 时就认为此表达式已经稳定,并取消对此表达式的监视。这是一种行之有效的减少绑定表达式数量的方法,与 ng-repeat 连用效果更佳(下文会提到),但过度使用也容易引发 bug。

2、善用 ng-if 减少绑定表达式的数量

如果你认为 ng-if 就是另一种用于隐藏、显示 DOM 元素的方法你就大错特错了。

ng-if 不仅可以减少 DOM 树中元素的数量(而非像 ng-hide 那样仅仅只是加个 display: none),每一个 ng-if 拥有自己的 scope,ng-if 下面的 $watch 表达式都是注册在 ng-if 自己 scope 中。当 ng-if 变为 false,ng-if 下的 scope 被销毁,注册在这个 scope 里的绑定表达式也就随之销毁了。

考虑这种 Tab 选项卡实现:

1
2
3
4
5
6
7
8
9
10
<ul>
  <li ng-class="{ selected: selectedTab === 1 }">Tab 1 title</li>
  <li ng-class="{ selected: selectedTab === 2 }">Tab 2 title</li>
  <li ng-class="{ selected: selectedTab === 3 }">Tab 3 title</li>
  <li ng-class="{ selected: selectedTab === 4 }">Tab 4 title</li>
</ul>
<div ng-show="selectedTab === 1">[[Tab 1 body...]]</div>
<div ng-show="selectedTab === 2">[[Tab 2 body...]]</div>
<div ng-show="selectedTab === 3">[[Tab 3 body...]]</div>
<div ng-show="selectedTab === 4">[[Tab 4 body...]]</div>

对于这种会反复隐藏、显示的元素,通常人们第一反应都是使用 ng-show 或 ng-hide 简单的用 display: none 把元素设置为不可见。

然而入上文所说,肉眼不可见不代表不会跑脏检查。如果将 ng-show 替换为 ng-if 或 ng-switch-when

1
2
3
4
<div ng-if="selectedTab === 1">[[Tab 1 body...]]</div>
<div ng-if="selectedTab === 2">[[Tab 2 body...]]</div>
<div ng-if="selectedTab === 3">[[Tab 3 body...]]</div>
<div ng-if="selectedTab === 4">[[Tab 4 body...]]</div>

有如下优点:

  • 首先 DOM 树中的元素个数显著减少至四分之一,降低内存占用

  • 其次 $watch 表达式也减少至四分之一,提升脏检查循环的速度

  • 如果这个 tab 下面有 controller(例如每个 tab 都被封装为一个组件),那么仅当这个 tab 被选中时该 controller 才会执行,可以减少各页面的互相干扰

  • 如果 controller 中调用接口获取数据,那么仅当对应 tab 被选中时才会加载,避免网络拥挤

当然也有缺点:

  • DOM 重建本身费时间

  • 如果 tab 下有 controller,那么每次该 tab 被选中时 controller 都会被执行

  • 如果在 controller 里面调接口获取数据,那么每次该 tab 被选中时都会重新加载

  • 各位读者自己取舍。

3、给 ng-repeat 手工添加 track by

不恰当的 ng-repeat 会造成 DOM 树反复重新构造,拖慢浏览器响应速度,造成页面闪烁。除了上面这种比较极端的情况,如果一个列表频繁拉取 Server 端数据自刷新的话也一定要手工添加 track by,因为接口给前端的数据是不可能包含 $$hashKey 这种东西的,于是结果就造成列表频繁的重建。

其实不必考虑那么多,总之加上没坏处,至少可以避免 angular 生成 $$hashKey 这种奇奇怪怪的东西。

具体参看: 详解track by

脏检测的利弊?

很多人对Angular的脏检测机制感到不屑,推崇基于setter,getter的观测机制,在我看来,这只是同一个事情的不同实现方式,并没有谁完全胜过谁,两者是各有优劣的。

大家都知道,在循环中批量添加DOM元素的时候,会推荐使用DocumentFragment,为什么呢,因为如果每次都对DOM产生变更,它都要修改DOM树的结构,性能影响大,如果我们能先在文档碎片中把DOM结构创建好,然后整体添加到主文档中,这个DOM树的变更就会一次完成,性能会提高很多。

同理,在Angular框架里,考虑到这样的场景:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function TestCtrl($scope) {
    $scope.numOfCheckedItems = 0;
 
    var list = [];
 
    for (var i=0; i<10000; i++) {
        list.push({
            index: i,
            checked: false
        });
    }
 
    $scope.list = list;
 
    $scope.toggleChecked = function(flag) {
        for (var i=0; i<list.length; i++) {
            list[i].checked = flag;
            $scope.numOfCheckedItems++;
        }
    };
}

如果界面上某个文本绑定这个numOfCheckedItems,会怎样?在脏检测的机制下,这个过程毫无压力,一次做完所有数据变更,然后整体应用到界面上。这时候,基于setter的机制就惨了,除非它也是像Angular这样把批量操作延时到一次更新,否则性能会更低。

所以说,两种不同的监控方式,各有其优缺点,最好的办法是了解各自使用方式的差异,考虑出它们性能的差异所在,在不同的业务场景中,避开最容易造成性能瓶颈的用法。

ng-if跟ng-show/hide的区别有哪些?

第一点区别是,ng-if 在后面表达式为 true 的时候才创建这个 dom 节点,ng-show 是初始时就创建了,用 display:block 和 display:none 来控制显示和不显示。第二点区别是,ng-if 会(隐式地)产生新作用域,ng-switch 、 ng-include 等会动态创建一块界面的也是如此。

ng-repeat迭代数组的时候,如果数组中有相同值,会有什么问题,如何解决?

会提示 Duplicates in a repeater are not allowed. 加 track by $index 可解决。当然,也可以 trace by 任何一个普通的值,只要能唯一性标识数组中的每一项即可(建立 dom 和数据之间的关联)。

ng-click中写的表达式,能使用JS原生对象上的方法,比如Math.max之类的吗?为什么?

不可以。只要是在页面中,就不能直接调用原生的 JS 方法,因为这些并不存在于与页面对应的 Controller 的 scope中。除非在scope中。除非在scope 中添加了这个函数:

1
2
3
$scope.parseInt = function(x){
    return parseInt(x);
}

{{now | 'yyyy-MM-dd'}}这种表达式里面,竖线和后面的参数通过什么方式可以自定义?

定义方式:

1
2
3
4
5
6
app.filter('过滤器名称',function(){
    return function(需要过滤的对象, 过滤器参数1, 过滤器参数2, ...){
        //...做一些事情
        return 处理后的对象;
    }
});

使用方式有两种,一种是直接在页面里:

1
<p>{{now | date : 'yyyy-MM-dd'}}</p>

一种是在 js 里面用:

1
2
// $filter('过滤器名称')(需要过滤的对象, 参数1, 参数2,...)
$filter('date')(now, 'yyyy-MM-dd hh:mm:ss');

factory和service,provider是什么关系?

factory 把 service 的方法和数据放在一个对象里,并返回这个对象;service 通过构造函数方式创建 service,返回一个实例化对象;provider 创建一个可通过 config 配置的 service。从底层实现上来看,service 调用了 factory,返回其实例;factory 调用了 provider,将其定义的内容放在 $get 中返回。factory 和 service 功能类似,只不过 factory 是普通 function,可以返回任何东西(return 的都可以被访问,所以那些私有变量怎么写你懂的);service 是构造器,可以不返回(绑定到 this 的都可以被访问);provider 是加强版 factory,返回一个可配置的 factory。

详述angular的“依赖注入”

AngularJS 是通过构造函数的参数名字来推断依赖服务名称的,通过 toString() 来找到这个定义的 function 对应的字符串,然后用正则解析出其中的参数(依赖项),再去依赖映射中取到对应的依赖,实例化之后传入。因为 AngularJS 的 injector 是假设函数的参数名就是依赖的名字,然后去查找依赖项,那如果像下面这样简单注入依赖,代码压缩后(参数被重命名了),就无法查找到依赖项了。

1
2
function myCtrl = ($scope, $http){
    ...}

所以,通常会使用下面两种方式注入依赖(对依赖添加的顺序有要求)。

数组注释法:

1
2
3
myApp.controller('myCtrl', ['$scope''$http'function($scope, $http){
    ...
}])

显式 $inject :

1
2
3
4
5
myApp.controller('myCtrl', myCtrl);
function myCtrl = ($scope, $http){
    ...
}
myCtrl.$inject = ['$scope''$http'];

对于一个 DI 容器,必须具备三个要素:依赖项的注册,依赖关系的声明和对象的获取。在 AngularJS 中,module 和 $provide 都可以提供依赖项的注册;内置的 injector 可以获取对象(自动完成依赖注入);依赖关系的声明,就是上面的那两种方式。

html: {{currentDate()}} js: $scope.currentDate = function(){return new Date();} 这种写法有没有问题

有问题,时间是实时变化的,然后会一直更新数据,效率低,脏数据检查到10次之后不再继续检查;

解决方案:可以使用一个变量来接收函数调用

controller as 和controller 有什么区别,能解决什么问题?

在使用controller的时候,为控制器注入window与window与scope,这个时候controller中的属性与方法是属于$scope的,而使用controllerAS的时候,可以将controller定义为Javascript的原型类,在html中直接绑定原型类的属性和方法

优点:

  • 可以使用 Javascript 的原型类, 我们可以使用更加高级的 ES6 或者 TypeScript 来编写 Controller ;

  • 指代清晰。在嵌套scope时,子scope如果想使用父scope的属性,只需简单的使用父scope的别名引用父scope即可。

  • 避开了所谓的 child scope 原型继承带来的一些问题(原来别名ctrl就是定义在scope上的一个对象,这就是controller的一个实例,所有在JS中定义controller时绑定到this上的model其实都是绑定到scope上的一个对象,这就是controller的一个实例,所有在JS中定义controller时绑定到this上的model其实都是绑定到scope.ctrl上的。使用controller as的一大好处就是原型链继承给scope带来的问题都不复存在了,即有效避免了在嵌套scope的情况下子scope的属性隐藏掉父scope属性的情况。)

  • controller的定义不依赖$scope。

  • 定义controller时不用显式的依赖scope,这有什么好处呢?仔细看定义,这不就是一个普通的函数定义嘛,对!这就是好处!例子中的ScopeController就是所谓的POJO(PlainOldJavascriptObject,Java里偷来的概念),这样的Object与框架无关,里面只有逻辑。所以即便有一天你的项目不再使用AngularJS了,依然可以很方便的重用和移植这些逻辑。另外,从测试的角度看,这样的Object也是单元测试友好的。单元测试强调的就是孤立其他依赖元素,而POJO恰恰满足这个条件,可以单纯的去测试这个函数的输入输出,而不用费劲的去模拟一个假的scope,这有什么好处呢?仔细看定义,这不就是一个普通的函数定义嘛,对!这就是好处!例子中的ScopeController就是所谓的POJO(PlainOldJavascriptObject,Java里偷来的概念),这样的Object与框架无关,里面只有逻辑。所以即便有一天你的项目不再使用AngularJS了,依然可以很方便的重用和移植这些逻辑。另外,从测试的角度看,这样的Object也是单元测试友好的。单元测试强调的就是孤立其他依赖元素,而POJO恰恰满足这个条件,可以单纯的去测试这个函数的输入输出,而不用费劲的去模拟一个假的scope。

  • 防止滥用scope的scope的watch,on,on,broadcast方法。可能刚刚就有人想问了,不依赖scope我怎么watch一个model,怎样广播和响应事件。答案是没法弄,这些事还真是只有scope我怎么watch一个model,怎样广播和响应事件。答案是没法弄,这些事还真是只有scope能干。但很多时候在controller里watch一个model是很多余的,这样做会明显的降低性能。所以,当你本来就依赖scope的时候,你会习惯性的调用这些方法来实现自己的逻辑。但当使用controlleras的时候,由于没有直接依赖scope的时候,你会习惯性的调用这些方法来实现自己的逻辑。但当使用controlleras的时候,由于没有直接依赖scope,使用watch前你会稍加斟酌,没准就思考到了别的实现方式了呢。

  • 定义route时也能用controller as。除了在DOM中显式的指明ng-controller,还有一种情况是controller的绑定是route里定义好的,那这时能使用controller as吗?答案是肯定的,route提供了一个controllerAs参数。这样在模板里就可以直接使用别名home啦。

个人觉得还是偏向于使用controller as的,当然有一点要澄清,使用contoller as并没有什么性能上的提升,仅仅是一种好的习惯罢了。

无论定义controller时有没有直接依赖scope,DOM中的scope是始终存在的。即使使用controlleras,双向绑定还是通过scope,DOM中的scope是始终存在的。即使使用controlleras,双向绑定还是通过scope的watch以及digest来实现的。

请简述$compile的用法?

angularjs里比较重要但又很少手动调用的要属compile服务了,通常在写组件或指令时,都是angularjs自动编译完成的,但有时我们可能需要手动编译,比如封装一个table组件,根据参数实现自定义渲染,增加一列复选框或者一列按钮啥的,这是就需要用到compile服务了,通常在写组件或指令时,都是angularjs自动编译完成的,但有时我们可能需要手动编译,比如封装一个table组件,根据参数实现自定义渲染,增加一列复选框或者一列按钮啥的,这是就需要用到compile了。

$compile,在Angular中即“编译”服务,它涉及到Angular应用的“编译”和“链接”两个阶段,根据从DOM树遍历Angular的根节点(ng-app)和已构造完毕的 \$rootScope对象,依次解析根节点后代,根据多种条件查找指令,并完成每个指令相关的操作(如指令的作用域,控制器绑定以及transclude等),最终返回每个指令的链接函数,并将所有指令的链接函数合成为一个处理后的链接函数,返回给Angluar的bootstrap模块,最终启动整个应用程序。

先解说下angular中页面处理

ng对页面的处理过程:

  • 浏览器把HTML字符串解析成DOM结构

  • ng把DOM结构给$compile,返回一个link函数

  • 传入具体的scope调用这个link函数

  • 得到处理后的DOM,这个DOM处理了指令,连接了数

$compile是个编译服务。编译服务主要是为指令编译DOM元素。

编译一段HTML字符串或者DOM的模板,产生一个将scope和模板连接到一起的函数。

$compile用法:

1
$compile(element,transclude,maxPriority);
  • element:将要被编译和插入模板的元素或者HTML字符串

  • transclude:指令内有效的函数。Function(angular.Scope,cloneAttachFn=)

  • maxPriority:只有在指令比给定的优先级低时应用。只影响根元素,不影响子元素

1
2
3
4
5
6
7
8
9
 .controller('MyController'function ($scope, $compile) {
                // 创建编译函数
                var compileFn = $compile('<p>{{appCtrl.msg}}</p>');
                // 传入scope,得到编译好的dom对象(已封装为jqlite对象)
                // 也可以用$scope.$new()创建继承的作用域
                var $dom = compileFn($scope);
                // 添加到文档中
                $dom.appendTo('body');
            })

通过compile服务可以编译html字符串或dom对象或jqLite对象,然后得到一个编译函数,再传入compile服务可以编译html字符串或dom对象或jqLite对象,然后得到一个编译函数,再传入scope,就会在当前作用域进行编译,返回编译好的jqLite对象,这时就可以直接添加到文档中了(也可以先添加到文档再编译)。

编译的实质其实就是对dom对象解析,使dom对象与scope进行耦合,通过绑定可以实现数据的更新,像Vue其实也是一样的过程。

$compile解说推荐看《Angular中$compile源码分析

这篇是对angularJS的一些疑点回顾,文章的问题大多是从网上搜集整理而来,如有不妥之处或不远被引用,请通知本人修改,谢谢!

首发于周陆军个人网站,转载注明来源:《再谈angularJS数据绑定机制及背后原理—angularJS常见问题总结

参考文章:

angular常见问题总结的更多相关文章

  1. angular好文

    Angular常见问题:subscribe()还是 async 管道 ? 终极答案就在这里 Angular Development #10 – RouteReuseStrategy – Maintai ...

  2. 法门扫地僧总结vue面试题(部分来源网络)

    Front-End 前端开发工程师面试宝典!   (本文部分有转载,不定期更新!)             前言(README.md) 本仓库是我整理的前端常见面试题,大部分由我整理,其中个别部分参考 ...

  3. angular的常见问题

    ng-if 跟 ng-show/hide 的区别有哪些? 第一点区别是,ng-if 在后面表达式为 true 的时候才创建这个 dom 节点,ng-show 是初始时就创建了,用 display:bl ...

  4. [转贴]有关Angular 2.0的一切

    对Angular 2.0的策略有疑问吗?就在这里提吧.在接下来的这篇文章里,我会解释Angular 2.0的主要特性区域,以及每个变化背后的动机.每个部分之后,我将提供自己在设计过程中的意见和见解,包 ...

  5. angular问题总结与反思

    因为工作中实际开发需要,才开始接触angular框架.从当初的比葫芦画瓢,被各种问题.概念折磨摧残,到现在有一定的了解认识,觉得有必要将自己的认识进行简单的总结.不到位的地方还望多多包涵. 1.双向数 ...

  6. 再谈angularJS数据绑定机制及背后原理—angularJS常见问题总结

    这篇是对angularJS的一些疑点回顾,是对目前angularJS开发的各种常见问题的整理汇总.如果对文中的题目全部了然于胸,觉得对整个angular框架应该掌握的七七八八了.希望志同道合的通知补充 ...

  7. Ionic 1 & 2 开发常见问题 Q&A

    原文发表于我的技术博客 本文分享了在 Ionic 1 & 2 版本开发过程中常见问题的一些 Q&A,供慕课网同学或其他朋友参考. 原文发表于我的技术博客 1. 版本的问题 Ionic ...

  8. 推荐一款基于Angular实现的企业级中后台前端/设计解决方案脚手架

    ng-alain 是一个企业级中后台前端/设计解决方案脚手架,我们秉承 Ant Design 的设计价值观,目标也非常简单,希望在Angular上面开发企业后台更简单.更快速.随着『设计者』的不断反馈 ...

  9. Angular 4+ 修仙之路

    Angular 4.x 快速入门 Angular 4 快速入门 涉及 Angular 简介.环境搭建.插件表达式.自定义组件.表单模块.Http 模块等 Angular 4 基础教程 涉及 Angul ...

随机推荐

  1. 本机访问其它电脑上的oracle数据库

    最近发现很多人问到怎么才能访问别人机子上的oracle,这里来给大家做个示范 借助工具的话,oracle就自己带了两个配置和移值助手下面:net configuration assistant 和ne ...

  2. BNU 26349——Cards——————【区间dp】

    题目大意:给你n张牌,排成一排放在桌子上,可以从左端拿也可以从右端拿.现在有A,B两人轮流取牌,A先取,两人足够聪明,即都想取最大的牌总和,问A能取到的最大值. 解题思路:定义dp[i][j][k]. ...

  3. image压缩

    public byte[] compressPic(byte[] data) { if(data.length == 0){ return new byte[0]; } Image img = nul ...

  4. 《SQLServer删除重复数据的方法》

    方法一: declare @max integer,@id integer open cur_rows fetch cur_rows into @id,@max begin set rowcount ...

  5. Android环信即时通讯集成坑爹 注册报错208解决

    第一种情况:    你用的环信APPkey 是授权模式的   只要改成开放模式就可以了 第二种情况:    上述情况在环信官网都有说明  但是这种情况  环信官网就没得说明   只说的是同步方法 注册 ...

  6. html和java的交互,利用jsBridge开源框架

    html中,js注册监听和回调 function connectWebViewJavascriptBridge(callback) { if (window.WebViewJavascriptBrid ...

  7. python_opencv应用系列1:图片读写

    opencv的读写非常简单,主要用到的就是imread和imwrite两个函数 读取图片示例 import cv2 #imread(filename[, flags]) -> retval im ...

  8. mybatis由JDBC的演化过程分析

    我们知道,mybatis是对JDBC的封装,那么他是如何演变过来的呢? 摘自传智传媒Java培训资料 关于mybatis的演化原理,我们先看看我们最熟悉也是最基础的通过JDBC查询数据库数据,一般需要 ...

  9. WINCC runtime连接SIMOTION simulator SIMOSIM

    测试使用的软件版本 TIA Portal V14sp1 Windows7 sp1 (professional) Scout 5.1(integrated in TIA 集成项目) VMware wor ...

  10. 安卓手机下载YouTube视频的3种方法

    作为全球最大的在线视频网站,YouTube上面的内容可真是应有尽有啊,从教学视频到个人手工艺品制作流程,从各种搞笑视频到电视连续集等等,包罗万象.如果你想下载YouTube视频到电脑上面的话,网上有很 ...