AngularJS开发指南9:AngularJS作用域的详解
AngularJS作用域是一个指向应用模型的对象。它是表达式的执行环境。作用域有层次结构,这个层次和相应的DOM几乎是一样的。作用域能监控表达式和传递事件。
作用域的特点
- 作用域提供APIs($watch)来观察模型的变化。
- 作用域提供APIs($apply)将任何模型的改变,反映到视图上。
- 作用域能通过共享模型成员的方式嵌套到应用组件上。一个作用域从父作用域继承属性。
- 作用域提供表达式执行的上下文。比如说表达式
{{username}}
本身是无意义的,除非把它放到指定username属性的作用域中。
作为数据模型的作用域
作用域是控制器和视图之间的“胶水”。在模板链接阶段,指令设置好作用域的$watch表达式。$watch使得指令能知晓属性的改变,这使得指令能重新渲染和更新DOM中的值。
控制器和指令都持有作用域的引用,但是不持有对方的引用。这使得控制器能从指令和DOM中脱离出来。这很重要,因为这使得控制器完全不需要知道view的存在,这大大改善了应用的测试。
举个例子:
<!doctype html>
<html ng-app>
<head>
<script src="http://code.angularjs.org/angular-1.0.2.min.js"></script>
<script src="script.js"></script>
</head>
<body>
<div ng-controller="MyController">
Your name:
<input type="text" ng-model="username">
<button ng-click='sayHello()'>greet</button>
<hr>
{{greeting}}
</div>
</body>
</html>
script.js:
function MyController($scope) {
$scope.username = 'World'; $scope.sayHello = function() {
$scope.greeting = 'Hello ' + $scope.username + '!';
};
}
上例中MyController将值World赋给了作用域中的username。然后作用域将这个赋值的操作通知给input,然后input就会被渲染成预填充了值的样子。这展示控制器如何将数据写入到作用域。
同样的,控制器能将行为添加到作用域,正如你看到的sayHello方法,这个方法是在用户点击'greet'按钮时被调用的。
逻辑上来说,表达式{{greeting}}
的渲染需要:
- 获取模板中定义了
{{greeting}}
DOM节点相关的作用域。在这个例子里,就是传入到MyController的作用域。 - 结合上一步获取到的作用域来计算表达式的值,将该值在DOM中替换掉表达式。
你可以把作用域和它的属性当做是用来渲染视图的数据。作用域是视图唯一相关联的变化来源。
作用域层级
每一个AngularJS应用都有一个绝对的根作用域。但是可能有多个子作用域。
一个应用可以有多个作用域,因为有一些指令会生成新的子作用域(参考指令的文档看看哪些指令会创建新作用域)。当新作用域被创建的时候,他们会被当成子作用域添加到父作用域下,这使得作用域会变成一个和相应DOM结构一个的树状结构。
当AngularJS执行表达式{{username}}
,它会首先查找和当前节点相关的作用域中叫做username的属性。如果没找到,那就会继续向上层作用域搜索,直到根作用域。在Javascript中,这被称为原型类型的继承,子作用域以原型的形式继承自父作用域。
下面这个例子展示了应用中的作用域,它们的继承关系。
<!doctype html>
<html ng-app>
<head>
<script src="http://code.angularjs.org/angular-1.0.2.min.js"></script>
<script src="script.js"></script>
<style>
.doc-example-live .ng-scope {
border: 1px dashed red;
}
</style>
</head>
<body>
<div ng-controller="EmployeeController">
Manager: {{employee.name}} [ {{department}} ]<br>
Reports:
<ul>
<li ng-repeat="employee in employee.reports">
{{employee.name}} [ {{department}} ]
</li>
</ul>
<hr>
{{greeting}}
</div>
</body>
</html>
script.js:
function EmployeeController($scope) {
$scope.department = 'Engineering';
$scope.employee = {
name: 'Joe the Manager',
reports: [
{name: 'John Smith'},
{name: 'Mary Run'}
]
};
}
注意当作用域和元素相关联的时候,AngularJS会自动给相应元素添加ng-scope类名。这个例子中的作用域范围突出显示了。子作用域的存在是很有必要的,因为迭代器要执行{{employee.name}}
表达式,它会根据不同的作用域生成不同的值。同样的,{{department}}
的执行是继承自根作用域的,因为只有根作用域中定义了它。
从DOM中获取作用域
作用域是作为$scope的数据属性关联到DOM上的,并且能在需要调试的时候被获取到。根作用关联的DOM就是ng-app指令定义的地方。一般来说ng-app都是放在<html>
元素中的,但是也能放在其他元素中。
在控制台中想获取关联的作用域:angular.element($0).scope()
作用域事件的传递
作用域中的事件传递是和DOM事件传递类似的。事件可以广播给子作用域或者传递给父作用域。举个例子:
<!doctype html>
<html ng-app>
<head>
<script src="http://code.angularjs.org/angular-1.0.2.min.js"></script>
<script src="script.js"></script>
</head>
<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> //当点击此按钮时,会触发MyEvent事件,这时会把此事件也传递给父作用域,也就是Root scope,这时它的count会增加1.当然同级的Middle scope的count也会加1.
<button ng-click="$broadcast('MyEvent')">$broadcast('MyEvent')</button> //当点击此按钮时,会触发MyEvent事件,这时会把此事件传递给子作用域,也就是Leaf scope,这时它的count会增加1,当然同级的Middle scope的count也会加1.
<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>
script.js:
function EventController($scope) {
$scope.count = 0;
$scope.$on('MyEvent', function() { //监听MyEvent事件
$scope.count++;
});
}
作用域的声明周期
浏览器接收到事件后的一般工作流程是执行一个相应的Javascript回调。回调一执行完,浏览器就会重新渲染DOM并且重新回到等待事件的状态。
当浏览器调用AngularJS上下文之外的Javascript代码时,AngularJS是不知道模型的更改的。要正确处理模型的更改,就要使用$apply方法进入AngularJS的执行上下文。只有在$apply方法内执行的模型修改才会正确地被AngularJS处理。比如,一个指令监听DOM事件,比如ng-click
,它必须在$apply方法中来执行表达式。
执行完表达式之后,$apply会进入$digest阶段。在$digest阶段,作用域会检查所有的$watch表达式,并将它们和之前的值比较。这意味着赋值语句,如$scope.username="angular"
不会马上导致$watch被通知,取而代之的是它会等到$digest阶段才被通知。这种方式是合理的,因为它将多个模型的更新整合到一个$watch通知里,并且保证了一个$watch通知期间不会有其他同样的$watch执行。
- 创建——根作用域是在应用被$injector启动时创建的。在模板链接阶段,有些指令会创建新的子作用域。
- 观察者注册——在模板链接阶段,指令会在作用域上注册观察者。这些观察者是用来将模型的改变传递给DOM的。
- 模型变化——为了正确地观测到模型变化,你需要并且只能在scope.$apply()中改变他们。(AngularJS的API会隐式地这么做,所以在控制器或者在$http,$timeout等服务中你不需要额外的调用$apply)。
- 变化的观测——在$apply的最后,AngularJS会在根作用域中执行一个$digest循环,它会将变化传递给所有子作用域。在$digest循环中,所有的$watch表达式或者函数都会被检测,来观察模型的变化。如果有变化被检测到了,$watch的监听回调就会被调用。
- 作用域的销毁——如果子作用域不再有用了。那么子作用域的创建者就会负责用scope.$destroy() API来将它销毁。这会停止$digest再调用此子作用域,并且让此作用域占用的内容能够被回收。
在模板编译阶段,编译器在DOM中匹配指令。指令通常分为两种:
- 观察型的指令,例如双花括号表达式
{{expression}}
,会用$watch来注册一个监听者。无论表达式什么时候改变,这类型的指令都会被通知,并且能更新视图。 - 监听者型的指令,比如
ng-click
,会向DOM注册一个监听者。当DOM监听者触发,指令会执行相关的表达式并且使用$apply方法更新视图。
当一个外界事件(比如用户操作,计时器或者XHR)触发时,相应的表达式必须在$apply()方法内,并由其相应的作用域调用,这样所有的监听者才会被正确地更新。
大部分情况下,指令和作用域交互,不会产生新的作用域实例。但是,有些指令,比如ng-controller
和ng-repeat
会创建新的作用域,并关联到相应的DOM元素上,你可以使用angular.element(aDomElement).scope()
方法来获得某一个DOM元素相关的作用域。
作用域和控制器在以下几种情况下交互:
- 控制器通过作用域来向模板暴露方法(参考
ng-controller
) - 控制器定义里能改变模型(作用域的属性)的方法(行为)
- 控制器在模型上注册了观察者。这些观察者会在控制器行为执行后立即被执行
检测属性的改变是AngularJS中一项常用的操作,所以它应该是高效的。要注意的是,执行检测的方法不应该包含任何DOM操作,因为在Javascript对象中,DOM获取要比属性获取慢很多很多。
加油!
AngularJS开发指南9:AngularJS作用域的详解的更多相关文章
- AngularJS开发指南4:指令的详解
指令是我们用来扩展浏览器能力的技术之一.在DOM编译期间,和HTML元素关联着的指令会被检测到,并且被执行.这使得指令可以为DOM指定行为,或者改变它. AngularJS有一套完整的.可扩展的.用来 ...
- 移动IM开发指南2:心跳指令详解
<移动IM开发指南>系列文章将会介绍一个IM APP的方方面面,包括技术选型.登陆优化等.此外,本文作者会结合他在网易云信多年iOS IM SDK开发的经验,深度分析实际开发中的各种常见问 ...
- “全栈2019”Java第一百零五章:匿名内部类覆盖作用域成员详解
难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第 ...
- “全栈2019”Java第九十八章:局部内部类访问作用域成员详解
难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第 ...
- 最锋利的Visual Studio Web开发工具扩展:Web Essentials详解
原文:最锋利的Visual Studio Web开发工具扩展:Web Essentials详解 Web Essentials是目前为止见过的最好用的VS扩展工具了,具体功能请待我一一道来. 首先,从E ...
- [ 转载 ] Java开发中的23种设计模式详解(转)
Java开发中的23种设计模式详解(转) 设计模式(Design Patterns) ——可复用面向对象软件的基础 设计模式(Design pattern)是一套被反复使用.多数人知晓的.经过分类 ...
- AngularJS开发指南16:AngularJS构建大型Web应用详解
AngularJS是由Google创建的一种JS框架,使用它可以扩展应用程序中的HTML功能,从而在web应用程序中使用HTML声明动态内容.在该团队工作的软件工程师Brian Ford近日撰写了一篇 ...
- AngularJS开发指南11:AngularJS的model,controller,view详解
model model这个词在AngularJS中,既可以表示一个(比如,一个叫做phones的model,它的值是一个包含多个phone的数组)对象,也可以表示应用中的整个数据模型,这取决于我们所讨 ...
- AngularJS开发指南2:AngularJS初始化过程
自动初始化 请将ng-app指令放到你应用的标签节点中, 如果你想要AngularJS自动执行整个<html>程序就把它放在 <html> 标签中.比如:<html ng ...
- AngularJS开发指南1:AngularJS简介
什么是 AngularJS? AngularJS 是一个为动态WEB应用设计的结构框架.它能让你使用HTML作为模板语言,通过扩展HTML的语法,让你能更清楚.简洁地构建你的应用组件.它的创新点在于, ...
随机推荐
- Sublime Text 2 (for OS X )配置成可以运行基于python3解释器的 .py文件
Mac自带的python 其version是python 2.7 官网下的Sublime Text 2部署好了以后默认也是 为了使ST2 可以在command+B时可以运行基于python3的.py, ...
- 【MVC 4】5.SportsSore —— 一个真实的应用程序
作者:[美]Adam Freeman 来源:<精通ASP.NET MVC 4> 前面建立的都是简单的MVC程序,现在到了吧所有事情综合在一起,以建立一个简单但真实的电子商务应用 ...
- [转载]ExtJs4 笔记(9) Ext.Panel 面板控件、 Ext.window.Window 窗口控件、 Ext.container.Viewport 布局控件
作者:李盼(Lipan)出处:[Lipan] (http://www.cnblogs.com/lipan/)版权声明:本文的版权归作者与博客园共有.转载时须注明本文的详细链接,否则作者将保留追究其法律 ...
- hihocoder-1389&&2016北京网赛07 Sewage Treatment(二分+网络流)
题目链接: Sewage Treatment 时间限制:2000ms 单点时限:2000ms 内存限制:256MB 描述 After years of suffering, people could ...
- UVALive 6470 Chomp --记忆化搜索
题意:给一个只有三行的方块阵(横向最多100个),然后p,q,r分别代表第1,2,3层的方格数,两人轮流去掉一个格子,此时这个格子的右上方都会被去掉,面临只剩最左下角的一个格子的状态的人输,问先手能否 ...
- NYOJ-93汉诺塔(三)
汉诺塔(三) 时间限制:3000 ms | 内存限制:65535 KB 难度:3 描述 在印度,有这么一个古老的传说:在世界中心贝拿勒斯(在印度北部)的圣庙里,一块黄铜板上插着三根宝石针.印度 ...
- JavaWeb学习----JSP简介及入门(含Eclipse for Java EE及Tomcat的配置)
[声明] 欢迎转载,但请保留文章原始出处→_→ 生命壹号:http://www.cnblogs.com/smyhvae/ 文章来源:http://www.cnblogs.com/smyhvae/p/ ...
- Unity 2D Touch Movement
Demo试玩(Kongregate既然也有广告时间了 --!)http://www.kongregate.com/games/zhaoqingqing/2d-touch-movement 操作步骤 1 ...
- 对于a标签点击之后可以发邮件和打电话的功能实现
<ul> <li><i class="phone"></i><a href="tel:021-69976089&qu ...
- 分布式环境下Unique ID生成方法
ID即标示符,在某个搜索域内能唯一标示其中某个对象.在关系型数据库中每个表都需要定义一个主键来唯一标示一条记录.为了方便一般都会使用一个auto_increment属性的整形数做为ID.因为数据库本身 ...