1.背景介绍
1、AngularJS Scope(作用域)
Scope(作用域)是应用在 HTML (视图)和 JavaScript (控制器)之间的纽带。Scope是一个对象,有可用的方法和属性。Scope可应用在视图和控制器上。$scope的使用贯穿整个 Angular App应用,它与数据模型相关联,同时也是表达式执行的上下文.有了 $scope就在视图和控制器之间建立了一个通道,基于作用域视图在修改数据时会立刻更新 $scope,同样的 $scope发生改变时也会立刻重新渲染视图.
2、根作用域 rootScope
所有的应用都有一个 $rootScope,它可以作用在 ng-app指令包含的所有 HTML元素中。$rootScope可作用于整个应用中。是各个 controller中 scope的桥梁。用 rootscope定义的值,可以在各个 controller中使用
2.知识剖析
1、$scope
$scope是一个把view(一个DOM元素)连结到controller上的对象。在我们的MVC结构里,这个 $scope将成为model,它提供一个绑定到DOM元素(以及其子元素)上的excecution context。
$scope实际上就是一个JavaScript对象,controller和view都可以访问它,所以我们可以利用它在两者间传递信息。在这个 $scope对象里,我们既可以存储数据,又可以存储将要运行在view上的函数。每一个Angular应用都会有一个$rootScope。这个$rootScope 是最顶级的scope,它对应着含有ng-app 指令属性的那个DOM元素。如果页面上没有明确设定$scope ,Angular 就会把数据和函数都绑定到这里。
Angular应用启动并生成视图时,会将根 ng-app元素与 $rootScope进行绑定.$rootScope是所有 $scope的最上层对象,可以理解为一个 Angular应用中得全局作用域对象,所以不应该附加太多逻辑或者变量给$rootScope,和污染 Javascript全局作用域是一样的道理.
$scope的作用
$scope对象在 Angular中充当数据模型的作用,也就是一般 MVC框架中 Model得角色.但又不完全与通常意义上的数据模型一样,因为 $scope并不处理和操作数据,它只是建立了视图和 HTML之间的桥梁,让视图和 Controller之间可以友好的通讯。
它有如下作用和功能:
提供了观察者可以监听数据模型的变化
可以将数据模型的变化通知给整个 App
可以进行嵌套,隔离业务功能和数据
给表达式提供上下文执行环境
在 Javascript中创建一个新的执行上下文,实际就是用函数创建了一个新的本地上下文,
在 Angular中当为子 DOM元素创建新的作用域时,其实就是为子 DOM元素创建了一个新的执行上下文.
$scope的生命周期有4个阶段:
1.创建
控制器或者指令创建时, Angular会使用 $injector创建一个新的作用域,然后在控制器或指令运行时,将作用域传递进去.
2.链接
Angular启动后会将所有 $scope对象附加或者说链接到视图上,所有创建 $scope对象的函数也会被附加到视图上.
这些作用域将会注册当 Angular上下文发生变化时需要运行的函数.也就是 $watch函数, Angular通过这些函数或者何时开始事件循环.
3.更新
一旦事件循环开始运行,就会开始执行自己的脏值检测.一旦检测到变化,就会触发 $scope上指定的回调函数
4.销毁
通常来讲如果一个 $scope在视图中不再需要, Angular会自己清理它.
ng-controller指令给所在的DOM元素创建了一个新的$scope对象,并将这个$scope对象包含进外层DOM元素的$scope对象里。
在ng-app里,这个外层DOM元素的$scope对象,就是$rootScope对象。这个scope链是这样的:
所有scope都遵循原型继承(prototypal inheritance),这意味着它们都能访问父scope们。对任何属性和方法,如果AngularJS在当前scope上找不到,就会到父scope上去找,如果在父scope上也没找到,就会继续向上回溯,一直到$rootScope上。唯一的例外:有些指令属性可以选择性地创建一个独立的scope,让这个scope不继承它的父scope们。
3、$watch:
angularjs核心之一是双向绑定,那么这个双向绑定是如何实现的呢? 当我们在创建出scope下的一个新属性的时候,ng就会主动为我们新属性注册$watch这个方法,$watch用来监听的数据变化,当数据变化之后,就立即把view和scope上数据同步。AngularJS就能够自动注册并监听变量的改变。AngularJS会首先将在{{ }}中声明的表达式编译成函数并调用$watch方法。
$watch是一个scope函数,用于监听模型变化
$watch(watchExpression, listener, objectEquality){ ... };
watchExpression:$watch方法的第一个参数是一个函数,它通常被称为watch函数,它的返回值声明需要监听的变量;
listener:第二个参数是listener,在变量发生改变的时候会被调用。和传统的事件注册和监听没有什么本质上的差别,差别仅在于AngularJS能够自动注册绝大多数的change事件并进行监听,只要按照AngularJS要求的语法来写HTML中的表达式代码,即{{ }}。 $watch方法为当前scope注册了一个watcher,这个watcher会被保存到一个scope内部维护的数组中,即是$$watchers。 watcher的主要目的是对scope上的某个属性进行监控
objectEquality:是否深度监听,如果设置为true,它告诉Angular检查所监控的对象中每一个属性的变化. 当浏览器接收到可以被angular context处理的事件时,$digest循环就会触发。这个循环是由两个更小的循环组合起来的。 一个处理evalAsync队列(这个没有探究),另一个处理$watch队列。$digest将会遍历我们的$watch队列。如果有至少一个更新过, 这个循环就会再次触发,直到所有的$watch都没有变化。这样就能够保证每个model都已经不会再变化。 如果循环超过10次的话,它将会抛出一个异常,防止无限循环。每次当$digest循环结束时,DOM相应地变化。
例如我们按下按钮触发ng-click事件:
1、浏览器接收到一个事件,进入angular context。
2、 $digest循环开始执行,查询每个$watch是否变化。
3、 由于监视$scope.name的$watch报告了变化,它会强制再执行一次$digest循环。
4、 新的$digest循环没有检测到变化。
5、浏览器拿回控制权,更新与$scope.name新值相应部分的DOM。
6、这里重要的是每一个进入angular context的事件都会执行一个$digest循环,也就是说每次我们输入一个字母循环都会检查整个页面的所有$watch。 Angular会为我们自动调用$apply!因此当点击带有ng-click的元素时,事件就会被封装到一个$apply调用。 比如有一个ng-model="foo"的输入框,然后敲一个f,事件就会这样调用$apply("foo = 'f';"),触发$digest循环。
4、$state
$state是ui-rooter的一项服务负责表示状态以及它们之间的转换。它还提供了接口来询问当前状态
常用的方法有:
$state.go(to, params, options) :转换到新状态的方便方法
$state.includes(stateOrName, params, options) :确定当前活动状态是否等于或是状态状态子的方法。返回布尔值
$state.params: 返回状态参数的对象$stateParams
$stateParams是一个对象,包含 url中每个参数的键/值。$stateParams可以为控制器或者服务提供 url的各个部分。
注意:$stateParams必须与一个控制器相关,并且$stateParams中的“键/值”也必须事先在那个控制器的url属性中有定义。
3.常见问题
1、 如何自定义$watch?
2、 什么时候需要我们去调用$watch?
4.解决方案
1、自定义自己的watches:
angular.module("myApp",[]).controller('MainCtrl', function($scope) {$scope.name = "hello";$scope.updated = -1;$scope.$watch('name', function() {$scope.updated++;});});
//创造一个新的$watch的方法。第一个参数是一个字符串或者函数,在这里是只是一个字符串,就是我们要监视的变量的名字,
//第二个参数是当$watch说我监视的表达式发生变化后要执行的。当controller执行到这个$watch时,它会立即执行一次
2、 取消 $watch :
$watch会影响性能问题,特别是在移动设备上,在不需要时应该清除
$watch函数本身返回一个函数,所以,当$watch不再需要的时候,我们只需调用返回的函数即可:
.controller('MainCtrl', function($scope) {
$scope.updated = 0;
$scope.stop = function() {
textWatch();
};
var textWatch = $scope.$watch('text', function(newVal, oldVal) {
if (newVal === oldVal) { return; }
$scope.updated++;
});
});
2、 什么时候需要我们去调用$watch?
被调用的事件没有进入angular context,$digest循环永远没有执行。这种情况一般出现在指令的隔离作用域中
,也会出现在异步执行的函数体中。调用$watch需要通过$apply。
6.扩展思考
指令中的scope三个值有什么用?
false 共享作用域
true 创建自己的作用域,并继承父作用域
{}创建隔离作用域
7.参考文献
参考一: Angular.js中使用$watch监听模型变化 http://yuankeqiang.lofter.com/post/8de51_1454f93
参考二:关于$watch应用的一些小技巧 http://blog.csdn.net/u010451286/article/details/50635839
参考三: how the apply runs a digest :http://angular-tips.com/blog/2013/08/watch-how-the-apply-runs-a-digest
参考四:深入解析AngularJS框架中$scope的作用与生命周期 http://www.jb51.net/article/80492.htm
参考五:-@ui-router——$state服务原版详解 https://www.cnblogs.com/koleyang/p/4576419.html
问题:
1、如何移除不必要的$watch?
.controller('MainCtrl', function($scope) {
$scope.updated = 0;
$scope.stop = function() {
textWatch();
};
var textWatch = $scope.$watch('text', function(newVal, oldVal) {
if (newVal === oldVal) { return; }
$scope.updated++;
});
});
2、1 ui-sref、$state.go 的区别ui-sref 一般使用在...;消息中心$state.go('someState')一般使用在 controller里面;.controller('firstCtrl', function($scope, $state) { $state.go('login'); });这两个本质上是一样的东西,我们看ui-sref的源码:...element.bind("click", function(e) { var button = e.which || e.button; if ( !(button > 1 || e.ctrlKey || e.metaKey || e.shiftKey || element.attr('target')) ) { var transition = $timeout(function() { // HERE we call $state.go inside of ui-sref $state.go(ref.state, params, options); });ui-sref最后调用的还是$state.go()方法
3、什么时候使用$watch
angular会为我们自动执行$watch,当指令中有独立作用域,或者在异步函数中,改变的数据不在angular的执行上下文,就需要手动调用$apply 来触发$digest去执行$watch
4、$scope 和$rootscope的区别是,$rootscope是$scope 的祖宗作用域