AngularJS深入探讨scope,继承结构,事件系统和生命周期

本文实例讲述了AngularJS的scope,继承结构,事件系统和生命周期。分享给大家供大家参考,具体如下:

深入探讨 Scope 作用域

每一个 $scope 都是类 Scope 的一个实例。类 Scope 拥有可以控制 scope 生命周期的方法,提供事件传播的能力,并支持模板渲染。

作用域的层次结构

让我们再来看看这个简单的 HelloCtrl 的例子:

var HelloCtrl = function($scope){
  $scope.name = 'World';
}

HelloCtrl 看起来就跟普通的 JavaScript 构造函数没什么区别,事实上,除了 $scope 这个参数之外,确实没什么新奇之处。不过,这个参数究竟是从哪里来的呢?

这个新的作用域是由 ng-controller指令使用 Scope.$new() 方法生成的。等一下,这么说来我们必须至少拥有一个 scope 的实例才能创建新的 scope!没错,AngularJS其实有一个 $rootScope(这个是所有其他作用域的父级)。这个 $rootScope 实例是在一个新的应用启动的时候创建的。

ng-controller指令就是 可以创建作用域 指令的其中一个。AngularJS 会在任何它在DOM树中碰到这种 可以创建作用域 指令的时候创建一个新的 Scope类的实例。这些新创建的作用域通过 $parent 属性指向它自身的父作用域。DOM树中会有很多 可以创建作用域 的指令,结果就是,很多作用域被创建了。

作用域的形式类似于父子、树状的关系,并且最根部的就是 $rootScope 实例。就像作用域是被DOM树驱动着创建的一样,作用域树也是在模仿 DOM 的结构。

现在你已经知道了,一些指令会创建新的子级的作用域,你可能会想,为什么会需要这些复杂的东西。要想理解这一点,我们来演示一个例子,其中使用了 ng-repeat 循环指令。

控制器如下:

var WorldCtrl = function ($scope) {
  $scope.population = 7000;
  $scope.countries = [
    {name: 'France', population: 63.1},
    {name: 'United Kingdom', population: 61.8},
  ];
};

模版如下:

<ul ng-controller="WorldCtrl">
  <li ng-repeat="country in countries">
    {{country.name}} has population of {{country.population}}
  </li>
  <hr>
  World's population: {{population}} millions
</ul>

这个 ng-repeat 指令可以迭代一个 countries 的集合,并且为集合中的每一项都创建新的DOM 元素。ng-repeat 指令的语法非常容易理解;其中每一项都需要一个新的变量 country,并把它挂到 $scope 上面,以便视图渲染使用。

但这里有一个问题,就是,每一个 country 都需要将一个新的变量挂载到一个 $scope 上去,而我们也不能就简单的覆盖掉前面被挂在上去的值。AngularJS 通过为集合中的每一个元素都创建一个新的作用域来解决这个问题。新创建的这些作用域跟相匹配的DOM树结构非常相像,我们也能通过之前提到的那个牛逼的 Chrome 扩展 Batarang 来可视化的看到这一点。

每一个作用域(以矩形标注边界)维护属于她自己的一段数据模型。给不同的作用域增加同名的变量是完全没有问题的,不会发生命名冲突(不同的DOM元素会指向不同的作用域,并使用相对应的作用域的变量来渲染模板)。这样一来,每个元素又有自己的命名空间,在前面的例子中,每一个<li> 元素都有自己的作用域,而 country 变量就定义在各自的作用域上面。

Scope的层次结构和继承

定义在作用于上的属性对他的子级作用于来说是可见的,试想一下,子级作用域并不需要重复定义同名的属性!这在实践中是非常有用的,因为我们不必一遍又一遍的重复定义本来可以通过作用域链得到的那些属性。

再来看看前面的例子,假设我们想要显示给出的这些国家与世界总人口的百分比。要实现这个功能,我们可以在一个作用域上定义一个 worldsPercentage 的方法,并由 WorldCtrl 来管理,如下所以:

$scope.worldsPercentage = function (countryPopulation) {
  return (countryPopulation / $scope.population)*100;
}

然后被 ng-repeat 创建的每一个作用域实例都来调用这个方法,如下:

<li ng-repeat="country in countries">
  {{country.name}} has population of {{country.population}},
  {{worldsPercentage(country.population)}} % of the World's
  population
</li>

AngularJS中作用域的继承规则跟 JavaScript 中原型的继承规则是相同的(在需要读取一个属性的时候,会一直向继承树的上方查询,直到找到了这个属性为止)。

贯穿作用域链的继承的风险

这种透过作用域层次关系的继承,在读数据的时候显得非常的直观、易于理解。但是在写数据的时候,就变的有点复杂了。

让我们来看看,如果我们在一个作用域上定义了一个变量,先不管是否在子级作用域上。JavaScript代码如下:

var HelloCtrl = function ($scope) {
};

视图的代码如下:

<body ng-app ng-init="name='World'">
  <h1>Hello, {{name}}</h1>
  <div ng-controller="HelloCtrl">
    Say hello to: <input type="text" ng-model="name">
    <h2>Hello, {{name}}!</h2>
  </div>
</body>

运行一下这段代码,就可以发现,这个 name 变量尽管仅仅是定义在了最顶级的作用域上,但在整个应用中都是可见的!这说明变量是从作用域链上继承下来的。换句话说,变量是在父级作用域上定义的,然后在子级作用域中访问的。

现在,我们一起来看看,如果在 <input> 中写点字会发生什么,运行结果你可能会感到吃惊,因为 HelloCtrl 控制器所初始化的作用域创建了一个新的变量,而不是直接去修改$rootScope 实例中的值。不过当我们认识到作用域也只不过是在彼此间进行了原型继承,也就不会觉得那么吃惊了。所有可以用在 JavaScript 对象上的原型继承的规则,都可以同等的用在 作用域 的原型链继承上去。毕竟 Scopes 作用域就是 JavaScript 对象嘛。

在子级作用域中去改变父级作用域上面的属性有几种方法。第一种,我们就直接通过 $parent 属性来引用父级作用域,但我们要看到,这是一个非常不可靠的解决方案。麻烦之处就在于,ng-model 指令所使用的表达式非常严重的依赖于整个DOM结构。比如就在 <input> 标签上面的哪里插入另一个 可创建作用域 的指令,那$parent 就会指向一个完全不同的作用域了。

就经验来讲,尽量避免使用 $parent 属性,因为它强制的把 AngularJS 表达式和你的模板所创建的 DOM 结构捆绑在了一起。这样一来,HTML结构的一个小小的改动,都可能会让整个应用崩溃。

另一个解决方案就是,不要直接把属性绑定到 作用域上,而是绑到一个对象上面,如下所示:

<body ng-app ng-init="thing = {name : 'World'}">
  <h1>Hello, {{thing.name}}</h1>
  <div ng-controller="HelloCtrl">
    Say hello to: <input type="text" ng-model="thing.name">
    <h2>Hello, {{thing.name}}!</h2>
  </div>
</body>

这个方案会更可靠,因为他并没有假设 DOM 树的结构是什么样子。

避免直接把数据绑定到 作用域的属性上。应优先选择把数据双向绑定到对象的属性上(然后再把对象挂到 scope 上)。就经验而言,在给 ng-model 指令的表达式中,你应该有一个点(例如, ng-model="thing.name")。

作用域层级和事件系统

层级关系中的作用域可以使用 event bus(一种事件系统)。AngularJS可以在作用域层级中传播具名的装备齐全的事件。事件可以从任何一个作用域中发出,然后向上($emit)和向下($broadcast)四处传播。

AngularJS核心服务和指令使用这种事件巴士来发出一些应用程序状态变化的重要事件。比如,我们可以监听$locationChangeSuccess 事件(由 $rootScope 实例发出),然后在任何 location(浏览器中就是URL)变化的时候都会得到通知,如下所示:

$scope.$on('$locationChangeSuccess', function(event, newUrl, oldUrl){
  //react on the location change here
  //for example, update breadcrumbs based on the newUrl
});

每一个作用域对象都会有这个 $on 方法,可以用来注册一个作用域事件的侦听器。这个函数所扮演的侦听器在被调用时会有一个 event 对象作为第一个参数。后面的参数会根据事件类型的不同与事件本身的配备一一对应。

类似于 DOM 事件,我们可以调用 event 对象的 preventDefault() 和 stopPropagation() 方法。stopPropagation() 方法将会阻止事件沿着作用域层级继续冒泡,并且只在事件向上层传播的时候($emit)才有效。

尽管 AngularJS 的事件系统是模仿了 DOM 的,但两个事件传播系统是完全独立的,没有任何共同之处。

虽然在作用域层级中传播事件对一些问题来说是一种非常优雅方案(特别是对全局的,异步的状态变化来说),但还是要适度使用。通常情况下,可以依靠双向数据绑定来得到一个比较干净的方案。在整个 AngularJS 框架中,一共只发出($emit)了三个事件($includeContentRequested,$includeContentLoaded,$viewContentLoaded)和七个广播($broadcast)($locationChangeStart, $locationChangeSuccess, $routeUpdate, $routeChangeStart,$routeChangeSuccess, $routeChangeError, $destroy)。正如你所看到的,作用域事件使用的非常少,我们应该在发送自定义的事件之前认真的评估一下其他的可选方案(多数会是双向数据绑定)。

千万不要在 AngularJS 中模仿 DOM 的基于事件的编程方式。大多数情况下,你的应用会有更好的架构方式,你也可以在双向数据绑定这条路上深入探索。

作用域的生命周期

作用域需要提供相互隔离的命名空间,避免变量的命名冲突。作用域们都很小,而且被以层级的方式组织起来,对内存使用的管理来说很有帮助。当其中一个作用域不再需要 ,它就可以被销毁了。结果就是,这个作用域所暴露出来的模型和方法就符合的垃圾回收的标准。

新的作用域通常是被 可创建作用域 的指令所生成和销毁的。不过也可以使用 $new() 和 $destroy() 方法来手动的创建和销毁作用域。

希望本文所述对大家AngularJS程序设计有所帮助。

(0)

相关推荐

  • AngularJS的ng-repeat指令与scope继承关系实例详解

    本文实例分析了AngularJS的ng-repeat指令与scope继承关系.分享给大家供大家参考,具体如下: ng-repeat指令的使用方式可以参考如下代码: <!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <title>ng-repeat</title> <script src="jquery-1

  • 深入理解Angular.JS中的Scope继承

    前言 AngularJS中scope之间的继承关系使用JavaScript的原型继承方式实现.本文结合AngularJS Scope的实现以及相关资料谈谈原型继承机制.下面来看看详细的介绍: 基本原理 在JavaScript中,每创建一个构造函数(constructor),就会同时给该函数生成一个指向原型对象的属性prototype.每个原型对象又获得一个constructor属性指向相应的构造函数,原型对象的其他属性和方法从Object继承而来.每个通过构造函数创建的实例,都包含一个指向构造函

  • AngularJS深入探讨scope,继承结构,事件系统和生命周期

    本文实例讲述了AngularJS的scope,继承结构,事件系统和生命周期.分享给大家供大家参考,具体如下: 深入探讨 Scope 作用域 每一个 $scope 都是类 Scope 的一个实例.类 Scope 拥有可以控制 scope 生命周期的方法,提供事件传播的能力,并支持模板渲染. 作用域的层次结构 让我们再来看看这个简单的 HelloCtrl 的例子: var HelloCtrl = function($scope){ $scope.name = 'World'; } HelloCtrl

  • 深入解析AngularJS框架中$scope的作用与生命周期

    $scope 的使用贯穿整个 Angular App 应用,它与数据模型相关联,同时也是表达式执行的上下文.有了 $scope 就在视图和控制器之间建立了一个通道,基于作用域视图在修改数据时会立刻更新 $scope,同样的 $scope 发生改变时也会立刻重新渲染视图. 有了 $scope 这样一个桥梁,应用的业务代码可以都在 controller 中,而数据都存放在controller 的 $scope 中. $scope是一个把view(一个DOM元素)连结到controller上的对象.在

  • 关于angularJs指令的Scope(作用域)介绍

    每当一个指令被创建的时候,都会有这样一个选择,是继承自己的父作用域(一般是外部的Controller提供的作用域或者根作用域($rootScope)),还是创建一个新的自己的作用域,当然AngularJS为我们指令的scope参数提供了三种选择,分别是:false,true,{}:默认情况下是false. 1.scope = false JS 代码: html 代码: result: 修改文本框的内容,两个名字都会变,实际上修改的是同一个$scope的name属性. 2. scope=true

  • 深入探究AngularJS框架中Scope对象的超级教程

    一.遇到的问题 问题发生在使用 AngularJS 嵌套 Controller 的时候.因为每个 Controller 都有它对应的 Scope(相当于作用域.控制范围),所以 Controller 的嵌套,也就意味着 Scope 的嵌套.这个时候如果两个 Scope 内都有同名的 Model 会发生什么呢?从子 Scope 怎样更新父 Scope 里的 Model 呢? 这个问题很典型,比方说当前页面是一个产品列表,那么就需要定义一个 ProductListController functio

  • 浅谈AngularJs指令之scope属性详解

    AngularJS使用directive()方法类定义一个指令: .directive("name",function(){ return{ }; }) 上面是定义一个指令的主体框架,该方法接受两个参数: 1.第一个参数:name表示定义的指令的名称(angularjs会用这个name注册这个指令) 2.第二个参数:函数,该番薯必须返回一个对象或者一个函数,但通常我们会返回一个对象.return后接的就是返回的对象. 在返回的对象中有一个scope属性,这个属性用来修饰指令的作用域.

  • AngularJS中监视Scope变量以及外部调用Scope方法

    在AngularJS中,有时候需要监视Scope中的某个变量,因为变量的改变会影响一些界面元素的显示.有时,也希望通过jQuery调用Scope的某个方法. 比如以下场景: <div> <button id="jQBtn">jQ Button</button> </div> <div id="ngSection" ng-controller="NGCtrl"> <input typ

  • Java异常继承结构解析_动力节点Java学院整理

    Java异常类层次结构图: 异常的英文单词是exception,字面翻译就是"意外.例外"的意思,也就是非正常情况.事实上,异常本质上是程序上的错误,包括程序逻辑错误和系统错误.比如使用空的引用.数组下标越界.内存溢出错误等,这些都是意外的情况,背离我们程序本身的意图.错误在我们编写程序的过程中会经常发生,包括编译期间和运行期间的错误,在编译期间出现的错误有编译器帮助我们一起修正,然而运行期间的错误便不是编译器力所能及了,并且运行期间的错误往往是难以预料的.假若程序在运行期间出现了错误

  • AngularJS中isolate scope的用法分析

    本文实例讲述了AngularJS中isolate scope的用法.分享给大家供大家参考,具体如下: angular js中指令directive有个特别有用的东西,那就是 isolate scope (被隔离的scope) 关于具体他和全局的scope 有什么区别,可以参考下面这篇文章: AngularJS 全局scope与Isolate scope通信用法示例 本文主要讲解 其具体的几种使用方式: 1. = 的使用 [html] <div class="card" ng-re

随机推荐