AngularJS讲义 - 作用域

什么是作用域?

Angular中作用域(scope)是模板以及工作的上下文环境,作用域中存放了应用模型和视图相关的回调行为。作用域是层次化结构的与相关联的DOM结构相对应。作用域可以观察表达式以及传播事件。

  原文: scope is an object that refers to the application model. It is an execution context for expressions. Scopes are arranged in hierarchical structure which mimic the DOM structure of the application. Scopes can watch expressions and propagate events.

作用域的特性

  作用域提供了相关的APIs($watch)来监控模型的状态并且将Angular系统(视图、服务、事件处理器)内部的模型的变化同步到视图。

作用域可以嵌套来控制应用组件对模型属性的访问。嵌套的作用域可以是“父子”关系或者"同级"关系。子作用域可以继承父作用域的属性,相邻作用域是互补可见的。

作用域提供了表达式的上下文环境。例如表达式{{username}}只有在定义了username属性的作用域中才有意义。

作用域作为数据模型

  作用域是连接Angular控制器和视图的中间地带。指令会在模板链接阶段(linking)在作用域中建立对表达式的监控($watch)服务。这样$watch就可以将模型属性的变化情况及时通知给指令从而更新视图。

控制器和指令只能通过作用域连接,不可以直接关联。这样就实现了控制器和视图的解耦。这样就可以实现一套模型绑定多个视图,也提高了前端代码的可测性。

index.html

Html代码  

 1 <div ng-controller="MyController">
 3   Your name:
 5 <input type="text" ng-model="username">
 7 <button ng-click=‘sayHello()‘>greet</button>
 9 <hr>
11   {{greeting}}
13 </div>  

script.js

Js代码  

1 angular.module(‘scopeExample‘, [])
2 .controller(‘MyController‘, [‘$scope‘, function($scope) {
3   $scope.username = ‘World‘;
5   $scope.sayHello = function() {
6     $scope.greeting = ‘Hello ‘ + $scope.username + ‘!‘;
7   };
8 }]);  

上述例子说明了作用域的工作原理:

1. 在MyController控制器中定义了username属性, 并在输入文本控件中绑定了该属性。username被初始化为‘World‘,这样作用域会通知文本框中并在文本框预填入初始值。

2. 同样控制器在作用域中定义了sayHello行为,并通过ng-click注册到按钮的点击事件。当用户在input中输入其他值时,会通过作用域更新username属性,从而改变sayHello的结果。

运行结果:

{{greeting}}表达式的工作原理如下:

1. 先找到{{greeting}}表达式所在DOM相关的作用域。在此例中为MyController中的$scope.

2. 在作用域中找到greeting属性并替换{{greeting}}, 既而更新了视图。

scope及其属性提供了用来展现视图的数据。(原文: The scope is the single source-of-truth for all things view related.)

从测试的角度考虑, 视图与控制器分离是必要的, 这样我们就可以单独测试视图后面的行为而不用考虑视图的细节。

Js代码  

 1 it(‘should say hello‘, function() {
 2   var scopeMock = {};
 3   var cntl = new MyController(scopeMock);
 5   // Assert that username is pre-filled
 6   expect(scopeMock.username).toEqual(‘World‘);
 8   // Assert that we read new username and greet
 9   scopeMock.username = ‘angular‘;
10   scopeMock.sayHello();
11   expect(scopeMock.greeting).toEqual(‘Hello angular!‘);
12 });  

作用域的层次化结构

   每个Angular应用都有一个根作用域(root scope), 在根作用域下可以有多个子作用域。一些指令(directives)也会创建子作用域。 新的作用域会被添加到相应的父作用域上,这样就形成了与DOM视图相平行的树形结构。

让我们通过一个具体的例子来理解:

Html代码  

 1 <div class="show-scope-demo">
 2   <div ng-controller="GreetController">
 3     Hello {{name}}!
 4   </div>
 5   <div ng-controller="ListController">
 6     <ol>
 7       <li ng-repeat="name in names">{{name}} from {{department}}</li>
 8     </ol>
 9   </div>
10 </div>  

Js代码  

1 angular.module(‘scopeExample‘, [])
2 .controller(‘GreetController‘, [‘$scope‘, ‘$rootScope‘, function($scope, $rootScope) {
3   $scope.name = ‘World‘;
4   $rootScope.department = ‘Angular‘;
5 }])
6 .controller(‘ListController‘, [‘$scope‘, function($scope) {
7   $scope.names = [‘Igor‘, ‘Misko‘, ‘Vojta‘];
8 }]);  

Css代码  

1 .show-scope-demo.ng-scope,
2 .show-scope-demo .ng-scope  {
3   border: 1px solid red;
4   margin: 3px;
5 }
6  

输出:

   对应的DOM结构:


我们可以注意到Angular会自动给绑定作用域的DOM元素加上"ng-close"类, CSS文件中给ng-scope类的元素加了高亮显示。为<li>创建子作用域是必要的,因为每个<li>都会有{{name}}表达式指向自己的name属性。{{department}}中department则继承根作用域$rootScope.department属性。

获取DOM的作用域

      作用域是与视图相关联的,我们可以在debug的时候通过api获取绑定到视图的作用域。根作用域(root scope)定义在含有ng-app指令的DOM元素上。通常ng-app放在<html>元素中, 当然ng-app也可以放在任何DOM元素上,例如我们只想局部视图被angular控制。

在Chrome中我们只需右击然后选择“审查元素”选项进入调试界面。

通过$0便可获得当前选中的DOM元素。

通过angular.element($0).scope()或者$scope可以获得当前元素对应的作用域。

作用域事件传播

    类似DOM事件,我们可以在作用域间传播事件。事件可以被广播($broadcast)到子作用域或者向上传播到父作用($emit)域中。

让我们看具体的例子:

Html代码  

 1 <div ng-controller="EventController">
 2   Root scope <tt>MyEvent</tt> count: {{count}}
 3   <ul>
 4     <li ng-repeat="i in [1]" ng-controller="EventController">
 5       <button ng-click="$emit(‘MyEvent‘)">$emit(‘MyEvent‘)</button>
 6       <button ng-click="$broadcast(‘MyEvent‘)">$broadcast(‘MyEvent‘)</button>
 7       <br>
 8       Middle scope <tt>MyEvent</tt> count: {{count}}
 9       <ul>
10         <li ng-repeat="item in [1, 2]" ng-controller="EventController">
11           Leaf scope <tt>MyEvent</tt> count: {{count}}
12         </li>
13       </ul>
14     </li>
15   </ul>
16 </div>  

Js代码  

1 angular.module(‘eventExample‘, [])
2 .controller(‘EventController‘, [‘$scope‘, function($scope) {
3   $scope.count = 0;
4   $scope.$on(‘MyEvent‘, function() {
5     $scope.count++;
6   });
7 }]);
8  

在HTML模板中,我们通过ng-click注册了点击事件监听,分别来向子作用域广播和向上传播MyEvent时间,在控制器我们通过$scope.$on(‘MyEvent‘)监听事件。

运行结果如下:

 作用域的生命周期

通常浏览器接收一个事件时会执行一段Javascript回调执行相关的处理,等回调执行完会自动更新DOM继续等待处理新事件。当浏览器调用的js代码不在angular执行上下文中,angular将不会注意模型的更改。我们可以通过$apply方法,让模型的更改在angular的上下文中进行。也就是说只有在$apply过程中的模型更改才会被angular甄别。例如,某个指令监听DOM事件(比如ng-click),必须在$apply方法中执行angular表达式。

在表达式执行完成后,$apply会执行$digest。在$digest过程中,作用域会检查所有$watch的表达式,比较表达式的当前状态和上一次状态。这种脏数据(dirty data)的检查是异步的。也就是说给模型属性赋予新的值时,$watch不会立即被通知,通知$watch发生在$digest阶段。这个短暂的延迟是有原因的,angular会批量通知$watch模型的状态情况,并且保证同时只有一个$watch在进行。如果$watch改变了模型的状态,会再强行触发一次$digest过程:

1. 创建 - 更作用域会在应用启动时通过注入器创建并注入。在模板连接阶段,一些指令会创建自己的作用域。

2.  注册观察者 - 在模板连接阶段,将会注册作用域的监听器。这也监听器被用来识别模型状态改变并更新视图。

3.  模型状态改变 - 更新模型状态必须发生在scope.$apply方法中才会被观察到。Angular框架封装了$apply过程,无需我们操心。

4.  观察模型状态 - 在$apply结束阶段,angular会从根作用域执行$digest过程并扩散到子作用域。在这个过程中被观察的表达式或方法会检查模型状态是否变更及执行更新。

5.  销毁作用域

当不再需要子作用域时,通过scope.$destroy()销毁作用域,回收资源。

作用域和指令

在编译(compiling)阶段,angular编译器会将DOM模板和指令匹配绑定。指令一般可分为以下两类:

1. 观察指令(Observing directives), 例如表达式, 通过$watch方法注册监听。这类指令在表达式变化时会被通知从而更新视图。

2. 监听器指令(Listener directives), 例如ng-click会在DOM元素上注册监听事件。DOM事件会触发指令执行相关的表达式或者通过$apply更新视图。

当接收到外部事件时(用户动作,计时器或者ajax相关的事件),相关作用域的表达式必须通过$apply方法执行,确保所有监听器状态被正确的更新。

哪些指令会创建作用域?

  在大多数情况下,指令不会自行创建自己的作用域。但一些指令,例如ng-controller, ng-repeat等会创建子作用域和DOM绑定。我们可以通过angular.element(aDomElement).scope() 获取和DOM元素相关联的作用域。

控制器和作用域

  控制器和作用域可以通过以下方式交互:

   1. 控制器通过作用域暴露给模板相关的行为。

2. 控制器定义可以操作模型的方法。

3. 控制器可以通过$watch注册监听模型的状态。这些watch会立即在控制器方法执行后被触发执行。

angular会自动检查作用域内模型状态的变更,这些检查并不触及DOM操作,而只是检查作用域的属性。

出于对性能的考虑,对不同数据类型(引用、集合、 值)的检查会有不同的策略(参考图中的决策树):

检查引用 - 当表达式返回一个新的对象或者数组时,scope.$watch(watchExpression, listener)不会再具体对象里面的具体内容,而是比较引用是否指向新的内存地址。

检查集合的内容 - 当对集合类型的数据增加、删除元素或者排序时,$scope.$watchCollection(watchExpression, listener)会检查集合中的元素。对集合中内容的检查是Shallow的, 即不会检查嵌套在集合中的集合或者对象。这种策略试图减少对内存资源的消耗。

检查值 - 根据模型的数据结构,scope.$watch (watchExpression, listener, true)深度遍历数据结构中的每个域的值,这种策略是最强大的但需要相当大的资源开销。

与浏览器事件的交互

   我们将结合下图分析angular与浏览器事件交互的工作原理:

1. 浏览器的事件环路会一直等待事件发生,这里的事件包括用户的交互,计时器,网络事件等。

2. 事件会触发监听器在js上下文环境中调用相关回调方法更新DOM结构。

3. 回调结束后,浏览器会脱离js上下文环境,基于DOM的变动重新渲染视图。

angular在以上的Javascript流程中加入了自己的事件处理机制,把js分成了传统的js上下文和angular执行的上下文。angular中的操作会得益于angular的数据绑定机制、异常处理和属性监听等框架特性。我们也可以通过$apply进入angular的执行上下文环境。

在大多数情况下(控制器、服务),angular会自动给我们调用$apply处理事件。除非是实现自定义的事件及回调或者是与第三方库结合使用时才会显示调用$apply进入angular上下文环境。$apply工作步骤如下:

1. 通过调用scope.$apply(stimulusFn)进入angular上下文环境,stimulusFn为希望在angular上下文中执行的代码。

2. 通常stimulusFn用来改变应用的状态。

3. angular进入$digest环路,$digest环路包括两个子循环分别处理$evalAsync队列和$watch列表。$digest会持续迭代直到$evalAsync队列清空并且$watch列表中没有任何状态更新。

4. $evalAsync队列被用来调度图中右半部分渲染DOM之前的子任务。

5. $watch列表中包含了上一次迭代后变化的表达式。当相关的模型变化是,$watch会更新表达式的值并更新视图。

6. 当$digest循环终止即离开Angular和Javascript的上下文环境,浏览器会随之更新视图。

基于上述原理我们详解一下"Hello World"例子中的数据绑定是怎么实现的:

1. 在angular编译阶段:

- ng-model和input指令在<input>控件中建立了"keydown"事件的监听器

- angular通过(interpolation)建立$watch对name属性的跟踪

2. 在运行阶段:

- 键入’x‘ 即触发keydown事件, input指令会捕获输入的变化调用$apply("name = ‘x‘"),更新模型数据。

- 更新完成后进入$digest循环,$watch列表检测到name属性发生了变化并通知interpolation,更新DOM视图。

- angular退出运行上下文,从而退出了keydown事件和与之相关的js上下文环境。

- 浏览器检测到DOM变化重新展现视图。

时间: 2024-08-24 02:22:26

AngularJS讲义 - 作用域的相关文章

AngularJS Scope(作用域)

1. AngularJS Scope(作用域) Scope(作用域) 是应用在 HTML (视图) 和 JavaScript (控制器)之间的纽带. Scope 是一个对象,有可用的方法和属性. Scope 可应用在视图和控制器上. 2. 使用Scope:在 AngularJS 创建控制器时,你可以将 $Scope 对象当作一个参数传递 <!DOCTYPE html> <html> <head> <meta charset="UTF-8">

angularJs的作用域和依赖注入

一.angularJs的作用域 <!DOCTYPE HTML> <html ng-app> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <title>angular概述</title> <script src="angular.min.js"></scr

AngularJS的作用域Scope

1.简介 angularjs启动并生成视图时,会根据ng-app元素和$RootScope进行绑定.$RootScope是所有$scope对象的最上层,是angularjs中最接近于全局作用域的对象. $scope对象就是一个普通的javascript对象,在angularjs中充当数据模型,但与传统的数据模型又不一样,$scope并不负责处理和数据操作,它只是视图和HTML之间的桥梁. $scope的所有属性都可以自动被视图访问到. 作用域是构成AngularJS应用的核心,它与应用的数据模型

AngularJS笔记---作用域和控制器

什么是作用域. 什么是控制器, 作用域包含了渲染视图时所需的功能和数据,它是所有视图的唯一源头.可以将作用域理解成试图模型(ViewModel). 作用域之间可以是包含关系也可以是独立关系.可以通过设置不同的ng-Controller来让$scope处于不同的作用域下面.  一.$rootScope 1.1 $rootScope可以理解成全局变量,一旦赋值对整个module都有效.$rootScpoe不依赖于任何一个controller,  app.run是module被加载时候执行的代码. 可

AngularJS&ndash;Scope(作用域)

Scope Scope 是一个应用程序的模块的对象.它是表达式的执行上下文.它充斥在DOM树的各个层级上.作用域Scope可以监控表达式也可以广播事件(监控表达式,就是WPF中的属性变更通知,相当有作用哟!).     Scope的特点 Scope有一个监控方法($watch),用它来监视model(模型)的变化,也就是上面所说的监视并做变更通知. Scope有一个($apply)方法,我们用它就可以去执行一些来自非Angular的代码,或者第三方内库功能.它的好处就是让第三方函数加入了Angu

AngularJs 隔离作用域

初学NG,有诸多的不解,今天看了一篇文章,原文地址:https://segmentfault.com/a/1190000002773689#articleHeader0 ,本文运行的代码也出处此. 里面讲到了自定义指令的scope参数的值有三种,true,false,{}.我按照例子将scope的值运行试验了一把,利用Chrome插件batarang分析了一波,谈谈自己的理解,有不到的地方望各位海涵. 首先,隔离作用域与继承作用域(子作用域)是不同的,隔离作用域同当前DOM的作用域是完全隔离开的

AngularJS之作用域及控制器(一)

前言 之前有接触过基本的AngularJS,未过多涉及,于是乎本系列我们来着重讲讲AngularJS,对AngularJS中重要的几大内容进行梳理并进行一些小的例子的书写.在之前项目过程中用到过avalon,但是avalon并未被广泛使用也就放弃了,至于孰最轻量,性能更好作为非专业的我就不妄下结论,lz只明白一点,业界最流行什么前端框架,招聘大部分要求会哪些框架,lz才会去学习,当然其中的乐趣也是油然而生,每一次敲代码的过程好似在浩瀚大海中探幽一番,别有一番滋味. 简短介绍 AngularJS也

AngularJS 双向作用域

双向数据绑定 1.AngularJS => DOM 1 <!doctype html> 2 <html ng-app="myApp"> 3 <head> 4 <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.0-rc.3/angular.js"></script> 5 </head> 6 <body>

AngularJS讲义-控制器

在Angular中,控制器(Controller)就是基于JavaScript的构造方法,主要用来构造模型并建立模型和视图之间的数据绑定.控制器里面定义了应用程序的逻辑和行为. 通过ng-controller指令可以将控制器和DOM绑定起来.Angular会用定义过的控制器构造函数实例化一个控制器对象,在Angular应用(module)启动的时候,会创建一个根作用域, Angular实例化每个控制器时会创建一个新的子作用域并作为可注入参数($scope)注入到控制器的构造方法中. 在理解控制器