本文實例講述了AngularJS的scope,繼承結構,事件系統和生命週期。分享給大家供大家參考,具體如下:
深入探討 Scope 作用域
每一個 $scope 都是類 Scope 的一個實例。類別 Scope 擁有可以控制 scope 生命週期的方法,提供事件傳播的能力,並支援模板渲染。
作用域的層次結構
讓我們再來看看這個簡單的HelloCtrl 的例子:
var HelloCtrl = function($scope){ $scope.name = 'World'; }
HelloCtrl 看起來跟普通的JavaScript 構造函數沒什麼,
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 指令的語法非常容易理解;其中每一項都需要一個新的變數 country,並把它掛到 $scope 上面,以便視圖渲染使用。
但這裡有一個問題,就是,每一個 country 都需要將一個新的變數掛載到一個 $scope 上去,而我們也不能就簡單的覆蓋掉前面被掛在上去的值。 AngularJS 透過為集合中的每一個元素創建一個新的作用域來解決這個問題。新創建的這些作用域跟相匹配的DOM樹結構非常相像,我們也能通過之前提到的那個牛逼的 Chrome 擴展 Batarang 來可視化的看到這一點。
每一個作用域(以矩形標註邊界)維護屬於她自己的一段資料模型。為不同的作用域增加同名的變數是完全沒有問題的,不會發生命名衝突(不同的DOM元素會指向不同的作用域,並使用相對應的作用域的變數來渲染模板)。這樣一來,每個元素又有自己的命名空間,在前面的例子中,每一個
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 變數儘管是頂級定義在這個做了在整個應用中都是可見的!這說明變數是從作用域鏈繼承下來的。換句話說,變數是在父級作用域上定義的,然後在子級作用域中存取的。 🎜
现在,我们一起来看看,如果在 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程序设计有所帮助。