The examples in this article describe the scope, inheritance structure, event system and life cycle of AngularJS. Share it with everyone for your reference, the details are as follows:
In-depth discussion of Scope scope
Every $scope is an instance of the class Scope. Class Scope has methods that can control the scope life cycle, provides the ability to propagate events, and supports template rendering.
Scope hierarchy
Let’s take a look at this simple HelloCtrl example again:
var HelloCtrl = function($scope){ $scope.name = 'World'; }
HelloCtrl looks like an ordinary JavaScript constructor. In fact, except for the $scope parameter, Other than that, there's really nothing new about it. But where does this parameter come from?
This new scope is generated by the ng-controller directive using the Scope.$new() method. Wait, so we have to have at least one instance of scope in order to create a new scope! Yes, AngularJS actually has a $rootScope (this is the parent of all other scopes). This $rootScope instance is created when a new application starts. The
ng-controller directive is one of the directives that can create scopes. AngularJS will create a new instance of the Scope class whenever it encounters this create-scope directive in the DOM tree. These newly created scopes point to their own parent scope via the $parent property. There will be many directives in the DOM tree that can create scopes. As a result, many scopes are created.
The form of scope is similar to a parent-child, tree-like relationship, and the root is the $rootScope instance. Just like scope creation is driven by the DOM tree, the scope tree also mimics the structure of the DOM.
Now that you know that some directives create new child scopes, you might be wondering why all this complexity is needed. To understand this, let’s demonstrate an example where the ng-repeat loop directive is used.
The controller is as follows:
var WorldCtrl = function ($scope) { $scope.population = 7000; $scope.countries = [ {name: 'France', population: 63.1}, {name: 'United Kingdom', population: 61.8}, ]; };
The template is as follows:
<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>
This ng-repeat directive can iterate a collection of countries and create a new DOM element for each item in the collection. The syntax of the ng-repeat directive is very easy to understand; each item requires a new variable country and hangs it on $scope for view rendering.
But there is a problem here, that is, each country needs to mount a new variable to a $scope, and we cannot simply overwrite the previously mounted value. AngularJS solves this problem by creating a new scope for each element in the collection. These newly created scopes are very similar to the matching DOM tree structure, which we can also visualize with the awesome Chrome extension Batarang mentioned earlier.
Each scope (bounded by a rectangle) maintains its own data model. There is absolutely no problem in adding variables with the same name to different scopes, and no naming conflicts will occur (different DOM elements will point to different scopes, and use the variables of the corresponding scope to render the template). In this way, each element has its own namespace. In the previous example, each 25edfb22a4f469ecb59f1190150159c6 element has its own scope, and the country variables are defined in their respective scopes.
Scope’s hierarchy and inheritance
The attributes defined on the scope are visible to its sub-scopes. Just imagine, the sub-scope does not need to repeatedly define attributes with the same name! This is very useful in practice because we don't have to redefine properties over and over that would otherwise be available through the scope chain.
Looking at the previous example again, let’s say we want to display the given percentage of these countries to the total world population. To implement this function, we can define a worldsPercentage method on a scope and manage it by WorldCtrl, as follows:
$scope.worldsPercentage = function (countryPopulation) { return (countryPopulation / $scope.population)*100; }
Then every scope instance created by ng-repeat will call this The method is as follows:
<li ng-repeat="country in countries"> {{country.name}} has population of {{country.population}}, {{worldsPercentage(country.population)}} % of the World's population </li>
The inheritance rules of scope in AngularJS are the same as the inheritance rules of prototype in JavaScript (when you need to read an attribute, you will keep querying to the top of the inheritance tree until you find it. to this property).
The risk of inheritance through the scope chain
This kind of inheritance through the scope hierarchical relationship is very intuitive and easy to understand when reading data. But when writing data, it becomes a bit complicated.
Let’s take a look, if we define a variable in a scope, regardless of whether it is in a child scope. The JavaScript code is as follows:
var HelloCtrl = function ($scope) { };
The code of the view is as follows:
<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>
Run this code, you can find that although the name variable is only defined in the top-level scope, Visible throughout the app! This means that the variable is inherited from the scope chain. In other words, variables are defined on the parent scope and then accessed in the child scope.
现在,我们一起来看看,如果在 d5fd7aea971a85678ba271703566ebfd 中写点字会发生什么,运行结果你可能会感到吃惊,因为 HelloCtrl 控制器所初始化的作用域创建了一个新的变量,而不是直接去修改$rootScope 实例中的值。不过当我们认识到作用域也只不过是在彼此间进行了原型继承,也就不会觉得那么吃惊了。所有可以用在 JavaScript 对象上的原型继承的规则,都可以同等的用在 作用域 的原型链继承上去。毕竟 Scopes 作用域就是 JavaScript 对象嘛。
在子级作用域中去改变父级作用域上面的属性有几种方法。第一种,我们就直接通过 $parent 属性来引用父级作用域,但我们要看到,这是一个非常不可靠的解决方案。麻烦之处就在于,ng-model 指令所使用的表达式非常严重的依赖于整个DOM结构。比如就在 d5fd7aea971a85678ba271703566ebfd 标签上面的哪里插入另一个 可创建作用域 的指令,那$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程序设计有所帮助。