AngularJs学习笔记--Scope

原文

  http://www.cnblogs.com/lcllao/archive/2012/09/23/2698651.htm

一、什么是 Scope 

scope  http://code.angularjs.org/1.0.2/docs/api/ng.$rootScope.Scope )是一个指向应用 model  object 。它也是 expression http://www.cnblogs.com/lcllao/archive/2012/09/16/2687162.html )的执行上下文。 scope被放置于一个类似应用的 DOM 结构的层次结构中。 scope 可以监测( watch,$watch expression 和传播事件。

 

二、scope 的特性

  • scope 提供 $watch API http://code.angularjs.org/1.0.2/docs/api/ng.$rootScope.Scope#$watch ),用于监测model 的变化。
  • scope 提供 $apply API http://code.angularjs.org/1.0.2/docs/api/ng.$rootScope.Scope#$apply ),在“Angular realm ”( controller  server  angular event handler )之外,从系统到视图传播任何 model 的变化。
  • scope 可以在提供到被共享的 model 属性的访问的时候,被嵌入到独立的应用组件中。 scope 通过(原型),从 parent scope 中继承属性。
  • scope  expression 求值之时提供上下文环境。例如, {{username}} 表达式是无意义的,除非它与一个特定的定义了 ”username”属性的 scope 一起进行求值。

 

三、Scope as Data-Model  scope 作为数据模型)

scope 是在应用 controller  view 之间的纽带。在模版 linking http://www.cnblogs.com/lcllao/archive/2012/09/04/2669802.html )的阶段, directive http://www.cnblogs.com/lcllao/archive/2012/09/09/2677190.html )在 scope 中设置 $watch表达式。 $watch  directive 能够得知属性的变化,使得 directive 将更新后的值渲染到DOM 中。

controller  directive 两者都与 scope 有引用,但它们两者之间没有(引用)(Both controllers and directives have reference to the scope, but not to each other)。这样的安排,将 controller  directive  DOM 中隔离开来。这是一个重要的地方,因为它让 controller  view 是隔离的,极大地提升了应用的可测试性(greatly improves the testing story of the applications)。

<!DOCTYPE HTML>
<html lang="zh-cn" ng-app>
<head>
    <meta charset="UTF-8">
    <title>data-model</title>
    <style type="text/css">
        .ng-cloak {
            display: none;
        }
    </style>
</head>
<body class="ng-cloak">
<div ng-controller="MyController">
    你的名字: <input type="text" ng-model="username"/>
    <button ng-click="sayHello()">欢迎</button>
    <hr/>
    {{greeting}}
</div>
<script src="../angular-1.0.1.js" type="text/javascript"></script>
<script type="text/javascript">
    function MyController($scope) {
        $scope.username = "My Little Dada";
        $scope.sayHello = function() {
            $scope.greeting = "Hello~" + $scope.username + "!";
        };
    }
</script>
</body>
</html>

 

在上面的例子中我们可以注意到 MyController  ”My Little Dada”对 scope 中的username 属性进行赋值。然后, scope 通知 input 进行赋值,将 username 的值预先填入 input 中。这展示了 controller 如何做才能够写入数据到 scope 中。

相似地, controller 可以将行为附加在 scope 中,正如那个当用户点击“欢迎”按钮时触发的 sayHello 方法一样。 sayHello 方法可以读取 username 属性,也可以创建greeting 属性。这表明,当它们绑定到 HTML input 控件时, scope 中的属性会自动更新。

逻辑上,显示 {{greeting}} 涉及以下两点:

  • 与定义了 {{greeting}} 表达式的模版 DOM 节点一起检索 scope 。在这个例子中,这个 scope 与传递到 MyController 中的 scope 是相同的。(我们在稍后将会讨论scope 的层次结构)
  • 通过之前检索的 scope ,对 greeting 表达式进行求值,然后将结果作为封闭 DOM元素的 text 的值。

我们可以认为, scope 和它自己的属性可以作为数据,用于渲染视图。 scope 是所有和 view 相关的东西单一的真相来源( The scope is the single source-of-truth for all things view related)。

从可测试性来看, controller  view 的分离是值得欣喜的,因为它允许我们在没有渲染细节的干扰下(专注于)测试行为。

   

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

 

四、Scope Hierarchies  scope 层次结构)

每一个 angular 应用有且只有一个 root scope ,但可以拥有多个 child scope 

应用可以拥有多个 child scope ,因为一些 directive 会创建新的 child scope (参考directive 文档,查看哪些 directive 可创建新的 scope ,如 ng-repeat )。当新的 scope被创建后,他们将作为一个 child scope ,加入到 parent scope 中。这样,创建了一个与它们附属的 DOM 相似的树结构。

当 angular  {{username}} 求值时,它首先查看与当前元素关联的 scope  username属性。如果没有找到对应的属性,它将会一直向上搜索 parent scope ,直到到达root scope 。在 javascript 中,这个行为被称为“原型继承”, child scope 典型地继承自它们的 parent 

这个例子说明应用中的 scope (是怎样的),属性的原型继承。

<!DOCTYPE HTML>
<html lang="zh-cn" ng-app>
<head>
    <meta charset="UTF-8">
    <title>scope-hierarchies</title>
    <style type="text/css">
        .ng-cloak {
            display: none;
        }
        .ng-scope {
            border: 1px dashed red;
        }
    </style>
</head>
<body class="ng-cloak">
<div ng-controller="MyController">
    经理:{{employee.name}} [{{department}}] <br/>
    报告:
    <ul>
        <li ng-repeat="employee in employee.reports">
            {{employee.name}} [{{department}}]
        </li>
    </ul>
    <hr/>
    {{greeting}}
</div>
<script src="../angular-1.0.1.js" type="text/javascript"></script>
<script type="text/javascript">
    function MyController($scope) {
        $scope.department = "某部";
        $scope.employee = {
            name:"My Little Dada",
            reports: [
                {name:"Lcllao"},
                {name:"那个谁^o^"}
            ]
        };
    }
</script>
</body>
</html>

注意, angular 自动放置 ng-scope class 到与 scope 粘附的元素中。 <style> 定义在上面的例子中,通过红色的虚线,高亮新的 scope 的范围。因为 repeater {{employee.name}} 表达式求值, child scope 是必须的,但取决于表达式在哪一个scope 进行求值,不同的 scope 有不同的结果。相似地, {{department}} 的值是从root scope 中原型继承得来的,只有在那个地方有,才有 department 属性的定义。

 

五、Retrieving Scopes from the DOM (从 DOM 中检索 scope 

scope 作为 $scope 数据属性附加到 DOM 中,可以被用于以调试作为目的的检索。(在应用中通过这个方式检索 Scope 是不可能的。)附加到的 DOM  root scope 的位置是通过 ng-app directive 的位置定义的。通常 ng-app 是放置在 <html> 元素中,但它也可以放置在其他元素中,例如,只有一部分视图需要被 angular 控制。

在 debugger 中查看 scope 

1. 在浏览器中,对着感兴趣的元素点击右键,选择“查看元素”。我们可以看到浏览器debugger 高亮了我们选中的元素。

2. debugger 允许我们在 console 中通过 $0 变量去访问当前选择的元素。

3. 想查看关联的 scope ,我们可以在 console 中输入: angular.element($0).scope()

 

六、Scope Events Propagation  Scope 事件传播)

scope 可以以类似于 DOM 事件的方式进行事件传播。事件可以被 broadcast http://code.angularjs.org/1.0.2/docs/api/ng.$rootScope.Scope#$broadcast )到 child scope 或者 emit  http://code.angularjs.org/1.0.2/docs/api/ng.$rootScope.Scope#$emit )到parent scope 中。(当前 scope 如果有监听,也会执行)

<!DOCTYPE HTML>
<html lang="zh-cn" ng-app>
<head>
    <meta charset="UTF-8">
    <title>scope-event-propagation</title>
    <style type="text/css">
        .ng-cloak {
            display: none;
        }
    </style>
</head>
<body class="ng-cloak">
<div ng-controller="MyController">
    root scope count:{{count}}
    <ul>
        <li ng-repeat="i in [1]" ng-controller="MyController">
            <button ng-click="$emit('MyEvent')">$emit("MyEvent")</button>
            <button ng-click="$broadcast('MyEvent')">$broadcast("MyEvent")</button>
            <br/>
            middle scope count:{{count}}
            <ul>
                <li ng-repeat="item in [1,2]" ng-controller="MyController">
                    Leaf scope count:{{count}}
                </li>
            </ul>
        </li>
    </ul>
</div>
<script src="../angular-1.0.1.js" type="text/javascript"></script>
<script type="text/javascript">
    function MyController($scope) {
        $scope.count = 0;
        $scope.$on("MyEvent", function() {
            $scope.count++;
        });
    }
</script>
</body>
</html>

 

七、Scope Life Cycle  scope 生命周期)

浏览器正常的事件流中,当浏览器接收到事件后,它会执行一个相应的 javascript 回调。一旦回调函数执行完毕后,浏览器将会重绘 DOM ,并返回到继续等待事件的状态。

当浏览器在 angular 执行环境外调用 javascript 代码时,这意味着 angular 是不知道model 的改变的。要正确处理 model 的修改,这个命令必须通过使 $apply 方法进入angular 执行环境。只有在 $apply 方法中的 model 变更,才会正确地被 angular 统计。例如,一个 directive 监听了 DOM 事件,例如 ng-click ,它必须在 $apply 方法中对表达式进行求值。

在对表达式求值之后, $apply 方法执行一个 $digest 。在 $digest 阶段里, scope 检查所有 $watch 监听的表达式,将现在的值与旧的值作比较。脏检查( dirty checking )是异步的。这意味着赋值语句(例如 $scope.username= ”angular”)将不会马上导致一个 $watch 被通知,反而, $watch 的通知将会延迟到 $digest 阶段。这个延迟是必须的,因为它把多个 model 更新联合到一个 $watch 通知中,这保证了在 $watch 通知的过程中,没有其他 $watch 在执行。如果一个 $watch 改变了 model 的值,那么它将会强制增加一个 $digest 周期。

1) Creation (创建 scope 

root scope 是在应用启动的过程中,被 $injector http://code.angularjs.org/1.0.2/docs/api/AUTO.$injector )创建的。在模版 linking 的过程中,一些 directive 会创建新的 child scope 

2) Watcher registration (注册 watcher 

在模版 linking 过程中, directive  scope 中注册 $watch 。这些 watch 将会被用作向DOM 传播 model 的值。

3) Model mutation  Model 变化)

为了让变化被正确地检测,我们需要将他们包裹在 scope.$apply 中。( angular API 已经隐式地做了这部操作,所以,当在 controller 中做同步的工作或者与 $http 或者$timeout 一起做异步工作的时候,不需要额外的 $apply 调用)。

4) Mutation observation (变化监测)

在 $apply 的结尾, angular 会在 root scope 执行一个 $digest 周期,这将会传播到所有 child scope 中。在 $digest 周期中,所有注册了 $watch 的表达式或者 function 都会被检查,判断 model 是否发生了改变,如果改变发生了,那么对应的 $watch 监听器将会被调用。

5) Scope destruction  scope 销毁)

当 child scope 不再是必须的时候, child scope 的产生者有责任通过scope.$destroy() API 销毁它们( child scope )。这将会停止 $digest 的调用传播传播到child scope 中,让被 child scope model 使用的内存可以被 gc  garbage collector )回收。

1. Scopes and Directives

在编译阶段中, compiler 依靠 DOM 模版匹配 directive  directive 通常可以分为两大类:

  • 观察型 directive  Observing directives ),例如 dobule-curly 表达式 {{expression}},使用 $watch 方法注册监听器。无论什么时候,表达式(的值)发生改变,这类directive 必须被通知,从而更新 view 
  • 监听型 directive  Listener directive ),例如 ng-click ,注册一个监听器到 DOM中。当 DOM 的监听器触发时, directive 会执行相关的表达式,并通过使用 $apply方法更新视图。

当一个外部的事件(例如用户动作、 timer 或者 XHR )被监听到,相关的expression 必须通过 $apply 方法应用到 scope 中,让所有监听器能够正确地更新。

2. Directives that Create Scopes

在大多数的情况中, directive  scope 是相互影响的,但不会创建新的 scope 实例。然而,一些 directive (例如 ng-controller  ng-repeat )会创建新 scope ,附加child scope 到对应的 DOM 元素中。我们通过使用angular.element(aDomElement).scope() 查看任意 DOM 元素的 scope 

3. Controllers and Scopes

在以下的情况中, scope  controller 是相互影响的:

  • controller 使用 scope 暴露 controller 方法到模版中(查看 ng-controller http://code.angularjs.org/1.0.2/docs/api/ng.directive:ngController ))。
  • controller 定义方法(行为),可以改变 model  scope 上的属性)。
  • controller 可能在 model 中注册 watch 。这些 watch 会在 controller 行为执行之后马上执行。

4. Scope $watch Performance Considerations  Scope $watch 的性能考虑)

在 angular 中,为了检测属性的变化而对 scope 进行脏检测( Dirty checking),是一个普遍的操作。为此,这要求 dirty checking 函数必须是高效的。应小心dirty checking 函数不要做任何 DOM 访问操作,因为 DOM 访问的速度比访问javascript 对象属性的速度要慢好几个数量级。

 

By Lcllao. 

l
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值