angular学习(五)—— Scopes

Scopes简介

Scopes是一个指向application模型的对象,是表达式执行的上下文,模拟application的DOM结构构成自己的层次结构。Scope可以观察表达式和传播事件。

Scopes特点

Scopes提供了API $watch观察model的变化。

Scopes提供了API $apply通过系统把model的更改从“Angular realm”(controllers, services, Angular event handlers)外部传播到view之中。

Scopes形成嵌套限制访问application组件的属性,同时也可以访问一些共享model的属性。所谓嵌套scopes不外乎两种,一种child scopes,另一种isolate scope,child scope可以访问从父scope继承的属性,isolate scope则不可以。

Scopes提供一个上下文,在其中的表达式可以求值。例如{ { username } }表达式是没有意义的,但是看做是在scopes上下文中的表达式的求值的话就有意义了。

Scope作为Data-Model

Scope是Controller和view之间的粘合剂,在程序链接阶段directives在scopes中设置$watch表达式,$watch允许directives得到属性改变的通知,并允许directives将改变后的值呈现给DOM视图。

Controller和directives都和scopes相关联,但是相互直接又不直接关联。这样使得Controller从指令和视图中隔离开来。这一点很重要,这使得Controller和view相互不可知,极大的提高了程序的可测试性。

<!DOCTYPE html>
<html>
<head>
</head>
<script src="script/angular.min.js"></script>

<script>
    angular.module(‘scopeExample‘, [])
            .controller(‘MyController‘, [‘$scope‘, function($scope) {
                $scope.username = ‘World‘;

                $scope.sayHello = function() {
                    $scope.greeting = ‘Hello ‘ + $scope.username + ‘!‘;
                };
            }]);
</script>
<body>
<div ng-app="scopeExample" ng-controller="MyController">
    Your name:
    <input type="text" ng-model="username">
    <button ng-click=‘sayHello()‘>greet</button>
    <hr>
    {{greeting}}
</div>
</body>
</html>

在上面的例子中,MyController首先将World赋值给scope中的username属性,scope将通知input这次赋值,然后将username的值预先填入到输入框中,这里演示了controller怎么样将数据写入到scope中。

同样controller可以分配行为给scope,比如这个例子中的sayHello方法,当用户点击greet按钮时,sayHello方法就会被调用。sayHello方法会去读取username属性并创建一个greeting属性。这里演示的是绑定到input部件的属性的自动更新。

greeting属性的呈现逻辑上分为两步:

1.在DOM中的模板里定义了{{greeting}},首先检索DOM关联的scope。

2.针对以上检索到的scope,计算求值greeting,并将结果分配给这个enclosing DOM元素的文本。

你可以把scopes及其属性是用于呈现view的数据,而且是view相关的单一真实数据来源。

Scope层次结构

每个angular的application都有一个root scope,但是可以有多个child scopes。

application可以有多个scopes,因为一些directives可以创建新的child scopes,新的scopes一旦被创建,就会被作为child scope增加到它的parent scope中,和他们链接的DOM一样,构成一个树形结构。

当Angular对{{name}}属性求值时,先在相关联的scope内寻找这个属性,如果没找到它就会去parent scope中找,一直搜到root scope。在JavaScript中这种行为被称为prototypical inheritance,child scopes prototypically继承于他们的parent scope。

这个例子讲解了application中点scopes,还有属性的prototypical inheritance,例子后面的一张图描诉了scope的边界。

<!DOCTYPE html>
<html>
<head>
</head>
<script src="script/angular.min.js"></script>

<script>
    angular.module(‘scopeExample‘, [])
            .controller(‘GreetController‘, [‘$scope‘, ‘$rootScope‘, function($scope, $rootScope) {
                $scope.name = ‘World‘;
                $rootScope.department = ‘Angular‘;
            }])
            .controller(‘ListController‘, [‘$scope‘, function($scope) {
                $scope.names = [‘Igor‘, ‘Misko‘, ‘Vojta‘];
            }]);
</script>

<style>
    .show-scope-demo.ng-scope,
    .show-scope-demo .ng-scope  {
        border: 1px solid red;
        margin: 3px;
    }
</style>
<body ng-app="scopeExample">
<div class="show-scope-demo">
    <div ng-controller="GreetController">
        Hello {{name}}!
    </div>
    <div ng-controller="ListController">
        <ol>
            <li ng-repeat="name in names">{{name}} from {{department}}</li>
        </ol>
    </div>
</div>
</body>
</html>

注意到Angular自动将scope影响到的元素附上了ng-scope的class样式。style定义了在新的scope创建的地方都会红色高亮显示。{{name}}在不同的scope内会得出不同的值,{{department}} prototypically继承于root scope。

在DOM中检索Scopes

Scope作为一种数据属性附加到DOM中,可以用来调试目的的检索。root scope被附加到ng-app声明的DOM节点。通常ng-app放在< html >元素,但它可以放在其他元素,例如,只有部分视图需要由Angular控制。

在调试器中检查scope:

1.在浏览器中右键你感兴趣的元素,并且选择检查元素(推荐chrome浏览器),您应该看到浏览器调试器与你点击高亮显示的元素。

2.调试器允许您访问当前选中的元素在Console输入$0

3.在Console中执行angular.element($0).scope()来检索scope

Scope事件传播

Scopes也能传播事件,就像传播DOM事件一样,可以通过broadcast广播到子children scopes,通过emit扩散到parent scope

<!DOCTYPE html>
<html ng-app="eventExample">
<head>
    <meta charset="uft-8"/>
    <title></title>
</head>
<script src="script/angular.min.js"></script>
<script>
    angular.module(‘eventExample‘, [])
            .controller(‘EventController‘, [‘$scope‘, function($scope) {
                $scope.count = 0;
                $scope.$on(‘MyEvent‘, function() {
                    $scope.count++;
                });
            }]);
</script>
<body>
<div ng-controller="EventController">
    Root scope <tt>MyEvent</tt> count: {{count}}
    <ul>
        <li ng-repeat="i in [1]" ng-controller="EventController">
            <button ng-click="$emit(‘MyEvent‘)">$emit(‘MyEvent‘)</button>
            <button ng-click="$broadcast(‘MyEvent‘)">$broadcast(‘MyEvent‘)</button>
            <br>
            Middle scope <tt>MyEvent</tt> count: {{count}}
            <ul>
                <li ng-repeat="item in [1, 2]" ng-controller="EventController">
                    Leaf scope <tt>MyEvent</tt> count: {{count}}
                </li>
            </ul>
        </li>
    </ul>
</div>

</body>
</html>

Scope生命周期

浏览器接受事件的普通流程是这样的,它会执行相应的javascript回调,一旦执行完毕浏览器将会重新呈现DOM并返回多个等待事件。

当浏览器调用进入javascript的代码时,因为这些代码在Angular的执行上下文之外,意味着Angular根本无从得知model的更改。模型修改正确的执行流程是通过$apply方法进入到Angular的执行上下文。在$apply方法中仅仅模型改变将被Angular执行。例如一个监听DOM事件的directive,比如ng-click,它必须在$apply方法中计算表达式的值。

在计算玩表达式的值之后,$apply会调用$digest方法。在$digest阶段,scope会检查所有的$watch表达式,并且比较它们和之前的值是否相同。这种脏检查是异步的,这意味着比如$scope.username=”angular”这项任务不会立即引起$watch的更改,取而代之的是$watch通知被延迟到$digest阶段。这种延迟是可取的,因为它把多个模型的改变聚集成一个$watch通知,并且能保证同时$watch通知期间没有其它$watches在运行。如果$watches改变了模型的值,它将被强制附加到$digest周期。

$apply()伪代码

function $apply(expr) {
  try {
    return $eval(expr);
  } catch (e) {
    $exceptionHandler(e);
  } finally {
    $root.$digest();
  }
}

Scope周期概要:

1.创建

在Angular程序的引导阶段,root scope通过$injector被创建。在template链接阶段,一些directives创建新的child scopes。

2.Watcher注册

在template链接阶段,directives注册Watcher到scope。这些watches被用于将模型的值传播到DOM中。

3.Model变化

为了让变化正确的被观察到,你应该仅仅使用scope.$apply()。Angular APIs会隐含的做这些,所以不需要额外的调用$apply()在controllers中,或者一些异步工作中比如 $http, $timeout or $interval 服务。

4.观察变化

在$apply的末尾,Angular会在root scope内调用$digest,会在所有的child scopes传播,在$digest周期,所有的$watched的表达式或者函数将被检查模型的改变,如果改变发生,watch的监听器将被调用。5.Scope销毁当childscope不再需要时,该childscope的创建者会去调用scope.destroy()销毁它们。这将阻止$digest传播到改child scope,并且该child scope占用的内存也会被垃圾回收器回收。

Scopes和Directives

在编译阶段,编译器将directives和DOM template进行匹配。directives通常分为两类:
1.观察directives,比如双大括号的表达式{{expression}},用 $watch()方法注册监听器。当表达式发生改变时,这种directives需要被通知,以便可以更新视图。
2.监听directives,比如ng-click,注册一个监听器在DOM上,当DOM的监听器被激活,这个指令将执行相应的表达式,并且用$apply()方法更新视图。
当一个外部事件 (如用户操作,定时器或XHR)收到时,相关的表达式必须通过$apply()应用到scope中,以便所有的listeners被正确的更新。

创建Scopes的Directives

在大多数情况下,directives和scopes相互作用,但是不会创建一个新的scope实例。然而一些命令,比如ng-controller和ng-repeat,会创建新的child scopes并且附加child scopes到相应的DOM元素中。你可以用angular.element(aDomElement).scope()方法检索任意DOM元素对应的scope。

Controllers和Scopes

Scopes和controllers直接的相互作用有以下几种情况:

1.controllers使用Scopes来把controller的方法暴露给templates。

2.controllers定义可以改变model的方法和行为。

3.controllers可以注册watches到模型中,当controller行为被执行后watches将立刻执行

Scope $watch的性能考虑

在Angular中,脏检查scope是否有属性改变是一项非常常规的操作。因此脏检查函数必须是高效的,应该非常小心的注意到,脏检查函数不能做任何DOM访问,DOM访问比JavaScript对象属性访问慢了好几个数量级。

Scope $watch的深度

脏检查可以使用三种策略:通过引用,通过集合内容,通过值。这三种脏检查可以检测到不同的变化,并且有不同的性能特征。

1.通过引用(scope.$watch (watchExpression, listener))

检测到一个值完全变为一个新的值的变化,如果这个值是一个数组或者一个对象,它里面的值的改变是不会检测到的,这种策略是性能最高效的。

2.通过集合内容(scope.$watchCollection (watchExpression, listener))

可以检测数据和对象内部的变化:当item被添加,删除,重新排序。这种检测深度是比较浅,因为它不进入嵌套的集合。这种策略要比第一种更耗费性能,因为他需要维护集合的副本内容。然而这种策略可以最小化必要的副本。

3.通过值(Watching by value (scope.$watch (watchExpression, listener, true))

可以检测到一个任意嵌套结构的所有变化,这种检测是最强力的,但是也是最耗性能的。每一次digest它都需要完整的遍历整个数据结构,并且要保存一份完整的副本在内存中。

与浏览器的event loop集成

这张图和下面的例子讲解Angular和浏览器之间的事件循环。

1.浏览器的事件循环等待一个事件的到来。一个事件是一个用户交互,计时器事件或网络事件(从服务器响应)。

2.事件的回调被执行,然后进入JavaScript上下文,这个回调可以修改DOM结构。

3.一旦回调执行完毕,浏览器就会离开JavaScript上下文,基于DOM的变化重新渲染视图。

Angular通过提供它自己的事件进程循环修改了普通的javascript流程。它吧javascript切割成两部分:传统部分和Angular执行上下文部分。在Angular执行上下文中仅仅一些操作可以受益:Angular数据绑定,异常处理,属性检测等等。你还可以调用$apply()从javascript执行上下文进入到Angular执行上下文。请记住一点,在大多数地方(controllers, services)已经被隐式的调用了。仅仅实现当自定义事件或者调用第三方库的回调函数时,$apply()才需要被显式的调用。

1.通过调用 scope.$apply(stimulusFn)进入到Angular执行上下文。stimulusFn是你希望在Angular上下文中执行的工作。

2.Angular执行stimulusFn(),它通常修改应用程序状态。

3.Angular进入\$diges循环。这个循环由两个小的循环构成:\$evalAsync队列和 \$watch列表。\$digest循环不断迭代,直到模型稳定。这意味着\$evalAsync队列是空的,\$watch列表没有任何变化。

4.\$evalAsync队列常常用于调度当前堆栈框架之外的工作,并且该工作在浏览器视图重新渲染之前。这个通常是用setTimeout(0)来做的,但是由于setTimeout(0)的缓慢,在每次事件后,浏览器重新加载视图可能引起闪烁。

5.\$watch列表是一组在最近一次迭代可能发生变化的表达式的集合,如果变化被检测到,那么\$watch将会被调用,这个函数通常是用新的值去更新DOM。

6.一旦Angular的\$digest循环完成,将会有Angular上下文转到Javascript上下文,接下来就是浏览器去重新呈现DOM以反应所有的改变。

下面讲解一下hello world的示例,这个示例所做的是当用户输入字符到文本框中,如何实现了数据绑定。

1. 编译阶段

1.1 ng-model和input指令在<input>控件设置了一个keydown监听器。

1.2 interpolation设置一个$watch来获得name改变的通知。

2. 运行时阶段

2.1 在input控件中按下’X’键,浏览器会发出keydown事件。

2.2 input指令捕获到了input值的改变,然后调用$apply(“name = ‘X’;”)去更新在Angular上下文的model。

2.3 Angular接受 name = ‘X’; 给model。

2.4 $digest循环开始

2.5 $watch列表检测到了在name属性上的变化然后通知interpolation从而更新DOM。

2.6.Angular退出执行上下文,接着退出keydown事件,退出javascript上下文。

2.7.浏览器用新的文本去重新呈现视图。

<!DOCTYPE html>
<html lang="en" ng-app>
    <head>
        <script src="script/angular.min.js"></script>
    </head>
    <body>
    Your name: <input type="text" ng-model="name" placeholder="World">
    <hr>
    Hello {{name || ‘World‘}}!
    </body>
</html>
时间: 2024-08-10 19:04:47

angular学习(五)—— Scopes的相关文章

angular学习笔记(五)-阶乘计算实例(1)

<!DOCTYPE html> <html ng-app> <head> <title>2.3.2计算阶乘实例1</title> <meta charset="utf-8"> <script src="../angular.js"></script> <script src="script.js"></script> </

angular学习笔记(五)-阶乘计算实例(2)

<!DOCTYPE html> <html ng-app> <head> <title>2.3.3计算阶乘实例2</title> <meta charset="utf-8"> <script src="../angular.js"></script> <script src="script.js"></script> </

angular学习笔记(二十五)-$http(3)-转换请求和响应格式

本篇主要讲解$http(config)的config中的tranformRequest项和transformResponse项 1. transformRequest: $http({ transformRequest: function(data){ //对前台发送的data进行处理 return data } }) 这个在测试的时候遇到了很大的问题.只要经过transformRequest函数处理,哪怕是不做任何处理,node后台都会报错,需要尝试使用php 2. transformResp

angular学习笔记(二十八)-$http(6)-使用ngResource模块构建RESTful架构

ngResource模块是angular专门为RESTful架构而设计的一个模块,它提供了'$resource'模块,$resource模块是基于$http的一个封装.下面来看看它的详细用法 1.引入angular-resource.min.js文件 2.在模块中依赖ngResourece,在服务中注入$resource var HttpREST = angular.module('HttpREST',['ngResource']); HttpREST.factory('cardResource

angular学习笔记(三十)-指令(5)-link

这篇主要介绍angular指令中的link属性: link:function(scope,iEle,iAttrs,ctrl,linker){ .... } link属性值为一个函数,这个函数有五个参数:scope,iEle,iAttrs,ctrl,linker scope:指令所在的作用域,这个scope和指令定义的scope是一致的.至于指令的scope,会在讲解scope属性的时候详细解释 iEle:指令元素的jqLite封装.(也就是说iEle可以调用angular封装的简版jq的方法和属

angular学习笔记(三十)-指令(6)-transclude()方法(又称linker()方法)-模拟ng-repeat指令

在angular学习笔记(三十)-指令(4)-transclude文章的末尾提到了,如果在指令中需要反复使用被嵌套的那一坨,需要使用transclude()方法. 在angular学习笔记(三十)-指令(5)-link文章也提到了link函数的第五个参数linker. 这篇文章就来讲解一下transclude()方法(linker()方法),是怎么使用的,另外,它也是compile函数的第三个参数,用法一样. 下面就通过自己写一个简易的模拟ngRepeat的指令cbRepeat,来了解linke

angular学习笔记(八)

本篇介绍angular控制视图的显示和隐藏: 通过给元素添加ng-show属性或者ng-hide属性来控制视图的显示或隐藏: ng-show: 绑定的数据值为true时,显示元素,值为false时,隐藏元素 ng-hide: 绑定的数据值为true时,隐藏元素,值为false时,显示元素 (其实只要用到其中一个就可以了) 下面来看个简单的例子,点击按钮可以显示/隐藏元素: <!DOCTYPE html> <html ng-app> <head> <title>

Beaglebone Back学习五(PWM测试)

PWM测试 参考链接 1 Enable PWM on BeagleBone with Device Tree overlays 2Using PWM on the Beaglebone Black 3 Beaglebone Coding 101: Buttons and PWM 4 Using PWM outputs 5 beaglebone-black-cpp-PWM 6 Enabling PWM Support in the kernel 7 Beaglebone Back学习五(PWM测试

angular学习笔记(二十三)-$http(1)-api

之前说到的$http.get和$http.post,都是基于$http的快捷方式.下面来说说完整的$http: $http(config) $http接受一个json格式的参数config: config的格式如下: { method:字符串 , url:字符串, params:json对象, data:请求数据, headers:请求头, transformRequest:函数,转换post请求的数据的格式, transformResponse:函数,转换响应到的数据的格式, cache:布尔

TweenMax动画库学习(五)

目录            TweenMax动画库学习(一)            TweenMax动画库学习(二)            TweenMax动画库学习(三)            TweenMax动画库学习(四)            TweenMax动画库学习(五)