`
boyitech
  • 浏览: 85522 次
  • 性别: Icon_minigender_1
  • 来自: 南通
社区版块
存档分类
最新评论

博弈AngularJS讲义(6) - 作用域

阅读更多

什么是作用域?

  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

<div ng-controller="MyController">
  Your name:
    <input type="text" ng-model="username">
    <button ng-click='sayHello()'>greet</button>
  <hr>
  {{greeting}}
</div>

   script.js

angular.module('scopeExample', [])
.controller('MyController', ['$scope', function($scope) {
  $scope.username = 'World';

  $scope.sayHello = function() {
    $scope.greeting = 'Hello ' + $scope.username + '!';
  };
}]);

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

      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.)

    

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

  

it('should say hello', function() {
  var scopeMock = {};
  var cntl = new MyController(scopeMock);

  // Assert that username is pre-filled
  expect(scopeMock.username).toEqual('World');

  // Assert that we read new username and greet
  scopeMock.username = 'angular';
  scopeMock.sayHello();
  expect(scopeMock.greeting).toEqual('Hello angular!');
});

 

作用域的层次化结构

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

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

  

<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>

    

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'];
}]);

 

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

 

  输出:

   对应的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)域中。

    让我们看具体的例子:

    

<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>

    

angular.module('eventExample', [])
.controller('EventController', ['$scope', function($scope) {
  $scope.count = 0;
  $scope.$on('MyEvent', function() {
    $scope.count++;
  });
}]);

 

    在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变化重新展现视图。

  • 大小: 3.7 KB
  • 大小: 6.6 KB
  • 大小: 59.9 KB
  • 大小: 27.5 KB
  • 大小: 8.6 KB
  • 大小: 52.1 KB
分享到:
评论

相关推荐

    博弈AngularJS讲义(13) - 动画

    NULL 博文链接:https://boyitech.iteye.com/blog/2167272

    论文研究-不确定性下多目标博弈中弱Pareto-NS均衡的存在性.pdf

    论文研究-不确定性下多目标博弈中弱Pareto-NS均衡的存在性.pdf, 在已知不确定参数变化范围的假设下, 研究了多目标博弈中弱Pareto-NS均衡点的存在性问题. 首先结合非合作...

    耶鲁大学公开课博弈论讲义

    6. **子博弈完美纳什均衡**:在动态博弈中,如果每一子博弈的每个节点都存在纳什均衡,那么这个序列就是子博弈完美的。 7. **合作博弈与联盟博弈**:合作博弈允许玩家形成联盟,共同制定策略以增加整体利益。而联盟...

    博弈论专题------博弈论

    ### 博弈论专题知识点详解 #### 一、博弈论概览 博弈论,又称对策论,是现代数学的一个重要分支,主要研究在各种策略情况下,参与者如何做出决策以达到自身利益最大化。这一理论不仅在数学领域有广泛的应用,同时...

    哈佛本科博弈论讲义教材

    哈佛大学的博弈论讲义是这个领域的权威资料之一,深受学者和学生们的喜爱。本讲义深入浅出地介绍了博弈论的基本概念、理论与应用,帮助读者理解在竞争与合作中的决策制定。 首先,我们要理解博弈论的核心概念——...

    微观经济学讲义-第13章博弈论.doc

    微观经济学讲义-第13章博弈论

    博弈论与信息经济学讲义09-1.pptx

    2. **完全信息动态博弈** - 这部分涉及博弈的动态过程,我们将深入研究子博弈精炼纳什均衡,这是考虑到博弈的各个阶段,参与者如何在每个阶段做出最优决策。 3. **不完全信息静态博弈** - 在参与者掌握的信息不完全...

    势博弈与时变Log-linear分布式拓扑控制算法.pdf

    势博弈与时变Log-linear分布式拓扑控制算法 势博弈与时变Log-linear分布式拓扑控制算法是为了解决UWSNs(Underwater Wireless Sensor Networks,水下无线传感器网络)中网络拓扑控制不稳定、频繁变化引起的网络能耗...

    博弈论与信息经济学讲义06-4PPT课件.pptx

    本讲义详细介绍了非合作博弈理论和信息经济学的相关概念。 首先,讲义涵盖了非合作博弈理论的基础,包括完全信息静态博弈。在完全信息静态博弈中,所有参与者都了解游戏的所有规则、支付矩阵和对手的策略。纳什均衡...

    2019年2季度大类资产配置报告:从基本面博弈转向资金面博弈-华泰证券-20190424.pdf

    2019年2季度大类资产配置报告:从基本面博弈转向资金面博弈-华泰证券-20190424.pdf

    计算博弈第二讲-V61

    计算博弈第二讲-V61 计算博弈是自动化研究所 University of Chinese of Academy of Sciences 的一门专业普及课,旨在让学生理解博弈的基本概念、方法和应用。第二讲的主要内容是博弈表示方法、常见博弈类型和博弈的...

    七所大学的博弈论讲义

    七所大学的博弈论讲义,包括哈佛大学.卡内基梅隆大学.芝加哥大学.加利福尼亚大学(圣. 迭戈校区).加利福尼亚

    博弈论与信息经济学讲义06-5PPT学习教案.pptx

    讲义第三章详细讲解了子博弈精炼纳什均衡,这是一种动态博弈的解概念,要求均衡策略不仅要在初始阶段是最佳的,而且在任何可能的子博弈中也必须是最优的。以房地产开发项目的例子来说明,开发商A和B在面对不确定市场...

    人机博弈----PDF中文版

    人机博弈。PDF中文版。。。。。。。。。。。。。。。。。。。。。。。。。

    博弈是什么意思-博弈之道须正矣.docx

    博弈是什么意思-博弈之道须正矣.docx

    博弈论与信息经济学讲义06-5学习教案.pptx

    博弈论与信息经济学是经济学的重要分支,它研究在不确定环境下人们如何进行决策并相互作用。在本讲义中,我们将探讨非合作博弈理论和信息经济学的相关概念。 首先,非合作博弈理论是博弈论的核心,主要关注参与者在...

    奶粉行业首次覆盖报告:存量博弈,国产崛起-20210407.pdf

    奶粉行业首次覆盖报告:存量博弈,国产崛起-20210407

    博弈论与信息经济学讲义06-4学习教案.pptx

    本讲义主要涵盖了非合作博弈理论和信息经济学的核心概念。 首先,非合作博弈理论是博弈论的基础,它描述了两个或多个独立决策者之间的互动,这些决策者无法通过正式协议来协调行动。在完全信息静态博弈中,所有参与...

    博弈论与信息经济学讲义06-5PPT课件.pptx

    本讲义涵盖的内容广泛,包括了非合作博弈理论、完全信息与不完全信息博弈,以及信息经济学中的委托-代理理论和信号传递机制。 首先,非合作博弈理论是博弈论的基础,它假设参与者无法通过协议或合作改变博弈结果。...

Global site tag (gtag.js) - Google Analytics