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

书名:Mastering Web Application Development with AngularJS

Chapter 1

深入探讨 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 来可视化的看到这一点,下面是屏幕截图:
AngularJS作用域截图
正如我们在截图中所看到的,每一个作用域(以矩形标注边界)维护属于她自己的一段数据模型。给不同的作用域增加同名的变量是完全没有问题的,不会发生命名冲突(不同的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() 方法来手动的创建和销毁作用域。

转载请注明来自[超2真人]
本文链接:http://www.peichao01.com/static_content/doc/html/Mastering_Web_Application_Development_with_AngularJS_3.html

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
这个文档是我自己原创制作的,在别的网上肯定是没有的。 而且做得非常好看,和非常准确。 如果下载的人多,将会把中英文对照的版本也上传。 Knockout是一个以数据模型(data model)为基础的能够帮助你创建富文本,响应显示和编辑用户界面的JavaScript类库。任何时候如果你的UI需要自动更新(比如:更新依赖于用户的行为或者外部数据源的改变),KO能够很简单的帮你实现并且很容易维护。 重要特性: 优雅的依赖追踪 - 不管任何时候你的数据模型更新,都会自动更新相应的内容。 Elegant dependency tracking - automatically updates the right parts of your UI whenever your data model changes. 声明式绑定 - 浅显易懂的方式将你的用户界面指定部分关联到你的数据模型上。 Declarative bindings - a simple and obvious way to connect parts of your UI to your data model. You can construct a complex dynamic UIs easily using arbitrarily nested binding contexts. 轻易可扩展 - 几行代码就可以实现自定义行为作为新的声明式绑定。 Trivially extensible - implement custom behaviors as new declarative bindings for easy reuse in just a few lines of code. 额外的好处: 纯JavaScript类库 – 兼容任何服务器端和客户端技术 Pure JavaScript library - works with any server or client-side technology 可添加到Web程序最上部 – 不需要大的架构改变 Can be added on top of your existing web application - without requiring major architectural changes 简洁的 - Gzip之前大约25kb Compact - around 13kb after gzipping 兼容任何主流浏览器 - (IE 6+、Firefox 2+、Chrome、Safari、其它) Works on any mainstream browser - (IE 6+, Firefox 2+, Chrome, Safari, others) 采用行为驱动开发 - 意味着在新的浏览器和平台上可以很容易通过验证。 Comprehensive suite of specifications - (developed BDD-style) means its correct functioning can easily be verified on new browsers and platforms 开发人员如果用过Silverlight或者WPF可能会知道KO是MVVM模式的一个例子。如果熟悉 Ruby on Rails 或其它MVC技术可能会发现它是一个带有声明式语法的MVC实时form。换句话说,你可以把KO当成通过编辑JSON数据来制作UI用户界面的一种方式… 不管它为你做什么 Developers familiar with Ruby on Rails, ASP.NET MVC, or other MV* technologies may see MVVM as a real-time form of MVC with declarative syntax. In another sense, you can think of KO as a general way to make UIs for editing JSON data… whatever works for you :) OK, 如何使用它? 简单来说:声明你的数据作为一个JavaScript 模型对象(model object),然后将DOM 元素或者模板(templates)绑定到它上面. The quickest and most fun way to get started is by working through the interactive tutorials. Once you’ve got to grips with the basics, explore the live examples and then have a go with it in your own project. KO和jQuery (或Prototype等)是竞争关系还是能一起使用? 所有人都喜欢jQuery! 它是一个在页面里操作元素和事件的框架,非常出色并且易使用,在DOM操作上肯定使用jQuery,KO解决不同的问题。 Everyone loves jQuery! It’s an outstanding replacement for the clunky, inconsistent DOM API we had to put up with in the past. jQuery is an excellent low-level way to manipulate elements and event handlers in a web page. I certainly still use jQuery for low-level DOM manipulation. KO solves a different problem. 如果页面要求复杂,仅仅使用jQuery需要花费更多的代码。 例如:一个表格里显示一个列表,然后统计列表的数量,Add按钮在数据行TR小于5调的时候启用,否则就禁用。jQuery 没有基本的数据模型的概念,所以需要获取数据的数量(从table/div或者专门定义的CSS class),如果需要在某些SPAN里显示数据的数量,当添加新数据的时候,你还要记得更新这个SPAN的text。当然,你还要判断当总数>=5条的时候禁用Add按钮。 然后,如果还要实现Delete功能的时候,你不得不指出哪一个DOM元素被点击以后需要改变。 As soon as your UI gets nontrivial and has a few overlapping behaviors, things can get tricky and expensive to maintain if you only use jQuery. Consider an example: you’re displaying a list of items, stating the number of items in that list, and want to enable an ‘Add’ button only when there are fewer than 5 items. jQuery doesn’t have a concept of an underlying data model, so to get the number of items you have to infer it from the number of TRs in a table or the number of DIVs with a certain CSS class. Maybe the number of items is displayed in some SPAN, and you have to remember to update that SPAN’s text when the user adds an item. You also must remember to disable the ‘Add’ button when the number of TRs is 5. Later, you’re asked also to implement a ‘Delete’ button and you have to figure out which DOM elements to change whenever it’s clicked. Knockout的实现有何不同? 使用KO非常简单。将你的数据描绘成一个JavaScript数组对象myItems,然后使用模板(template)转化这个数组到表格里(或者一组DIV)。不管什么时候数组改变, UI界面也会响应改变(不用指出如何插入新行或在哪里插入),剩余的工作就是同步了。例如:你可以声明绑定如下一个SPAN显示数据数量(可以放在页面的任何地方,不一定非要在template里): It’s much easier with KO. It lets you scale up in complexity without fear of introducing inconsistencies. Just represent your items as a JavaScript array, and then use a foreach binding to transform this array into a TABLE or set of DIVs. Whenever the array changes, the UI changes to match (you don’t have to figure out how to inject new TRs or where to inject them). The rest of the UI stays in sync. For example, you can declaratively bind a SPAN to display the number of items as follows: There are <span data-bind="text: myItems().count"></span> items就是这些!你不需要写代码去更新它,它的更新依赖于数组myItems的改变。同样, Add按钮的启用和禁用依赖于数组 myItems 的长度,如下: That’s it! You don’t have to write code to update it; it updates on its own when the myItems array changes. Similarly, to make the ‘Add’ button enable or disable depending on the number of items, just write: <button data-bind="enable: myItems().count < 5">Add</button>之后,如果你要实现Delete功能,不必指出如何操作UI元素,只需要修改数据模型就可以了。 Later, when you’re asked to implement the ‘Delete’ functionality, you don’t have to figure out what bits of the UI it has to interact with; you just make it alter the underlying data model. 总结:KO没有和jQuery或类似的DOM 操作API对抗竞争。KO提供了一个关联数据模型和用户界面的高级功能。KO本身不依赖jQuery,但是你可以一起同时使用jQuery, 生动平缓的UI改变需要真正使用jQuery。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值