AngularJs之Scope做用域

前言:javascript

上篇博文AngularJs之directive中說了Scope做用域是個大坑,因此拿出來做爲重點總結!html

什麼是scope

  AngularJS 中,做用域是一個指向應用模型的對象,它是表達式的執行環境。做用域有層次結構,這個層次和相應的 DOM 幾乎是同樣的。做用域能監控表達式和傳遞事件。java

  在 HTML 代碼中,一旦一個 ng-app 指令被定義,那麼一個做用域就產生了,由 ng-app 所生成的做用域比較特殊,它是一個根做用域($rootScope),它是其餘全部$Scope 的最頂層。瀏覽器

  除了用 ng-app 指令能夠產生一個做用域以外,其餘的指令如 ng-controller,ng-repeat 等都會產生一個或者多個做用域。此外,還能夠經過 AngularJS 提供的建立做用域的工廠方法來建立一個做用域。這些做用域都擁有本身的繼承上下文,而且根做用域都爲$rootScope。app

  在生成一個做用域以後,在編寫 AngularJS 代碼時,$scope 對象就表明了這個做用域的數據實體,咱們能夠在$scope 內定義各類數據類型,以後能夠直接在 HTML 中以 {{變量名}} 方式來讓 HTML 訪問到這個變量。函數

繼承做用域

  AngularJS 在建立一個做用域時,會檢索上下文,若是上下文中已經存在一個做用域,那麼這個新建立的做用域就會以 JavaScript 原型繼承機制繼承其父做用域的屬性和方法。測試

  一些 AngularJS 指令會建立新的子做用域,而且進行原型繼承: ng-repeat、ng-include、ng-switch、ng-view、ng-controller, 用 scope: true 和 transclude: true 建立的 directive。spa

  如下 HTML 中定義了三個做用域,分別是由 ng-app 指令所建立的$rootScope,parentCtrl 和 childCtrl 所建立的子做用域,這其中 childCtrl 生成的做用域又是 parentCtrl 的子做用域。設計

示例一:做用域的繼承實例

<!doctype html>
<html>
<head>
    <meta charset=utf-8"/>
    <title>scope nick</title>
    <script src="http://apps.bdimg.com/libs/angular.js/1.4.6/angular.min.js"></script>
</head>
<script type="text/javascript"> angular.module('app', []) .controller('parentCtrl', ['$scope', function($scope) { $scope.args= 'Nick DeveloperWorks'; }]) .controller('childCtrl', ['$scope', function($scope) { $scope.args= 'Nick DeveloperWorks for test'; }]); </script>
<body ng-app="app">
<div ng-controller="parentCtrl">
    <input ng-model="args">
    <div ng-controller="childCtrl">
        <input ng-model="args">
    </div>
</div>
</body>
</html>

  繼承做用域符合 JavaScript 的原型繼承機制,這意味着若是咱們在子做用域中訪問一個父做用域中定義的屬性,JavaScript 首先在子做用域中尋找該屬性,沒找到再從原型鏈上的父做用域中尋找,若是還沒找到會再往上一級原型鏈的父做用域尋找。在 AngularJS 中,做用域原型鏈的頂端是$rootScope,AnguarJS 將會尋找到$rootScope 爲止,若是仍是找不到,則會返回 undefined。雙向綁定

  咱們用實例代碼說明下這個機制。首先,咱們探討下對於原型數據類型的做用域繼承機制:

示例二:做用域繼承實例-原始類型數據繼承

<!doctype html>
<html>
<head>
    <meta charset=utf-8"/>
    <title>scope nick</title>
    <script src="http://apps.bdimg.com/libs/angular.js/1.4.6/angular.min.js"></script>
</head>
<script type="text/javascript"> angular.module('app', []) .controller('parentCtrl', ['$scope', function($scope) { $scope.args = 'Nick DeveloperWorks'; }]) .controller('childCtrl', ['$scope', function($scope) { }]); </script>
<body ng-app="app">
<div ng-controller="parentCtrl">
    <input ng-model="args">
    <div ng-controller="childCtrl">
        <input ng-model="args">
    </div>
</div>
</body>
</html>

測試運行結果:

第一個輸入框:

  雖然在 childCtrl 中沒有定義具體的 args 屬性,可是由於 childCtrl 的做用域繼承自 parentCtrl 的做用域,所以,AngularJS 會找到父做用域中的 args 屬性並設置到輸入框中。並且,若是咱們在第一個輸入框中改變內容,內容將會同步的反應到第二個輸入框。

第二個輸入框:

  第二個輸入框的內容今後將再也不和第一個輸入框的內容保持同步。在改變第二個輸入框的內容時,由於 HTML 代碼中 model 明確綁定在 childCtrl 的做用域中,所以 AngularJS 會爲 childCtrl 生成一個 args 原始類型屬性。這樣,根據 AngularJS 做用域繼承原型機制,childCtrl 在本身的做用域找獲得 args 這個屬性,從而也再也不會去尋找 parentCtrl 的 args 屬性。今後,兩個輸入框的內容所綁定的屬性已是兩份不一樣的實例,所以不會再保持同步。

現將代碼作以下修改,結合以上兩個場景,會出現怎樣的結果?

示例三:做用域繼承實例-對象數據繼承

<!doctype html>
<html>
<head>
    <meta charset=utf-8"/>
    <title>scope nick</title>
    <script src="http://apps.bdimg.com/libs/angular.js/1.4.6/angular.min.js"></script>
</head>
<script type="text/javascript"> angular.module('app', []) .controller('parentCtrl', ['$scope', function($scope) { $scope.args = {}; $scope.args.content = 'Nick DeveloperWorks'; }]) .controller('childCtrl', ['$scope', function($scope) { }]); </script>
<body ng-app="app">
<div ng-controller="parentCtrl">
    <input ng-model="args.content">
    <div ng-controller="childCtrl">
        <input ng-model="args.content">
    </div>
</div>
</body>
</html>

測試結果是不管改變任何一個輸入框的內容,二者的內容始終同步。

  根據 AngularJS 的原型繼承機制,若是 ng-model 綁定的是一個對象數據,那麼 AngularJS 將不會爲 childCtrl 建立一個 args 的對象,天然也不會有 args.content 屬性。這樣,childCtrl 做用域中將始終不會存在 args.content 屬性,只能從父做用域中尋找,也便是兩個輸入框的的變化其實只是在改變 parentCtrl 做用域中的 args.content 屬性。所以,二者的內容始終保持同步。

  咱們再看一個例子,分析結果如何。

示例四:做用域繼承實例-再也不訪問父做用域的數據對象。

<!doctype html>
<html>
<head>
    <meta charset=utf-8"/>
    <title>scope nick</title>
    <script src="http://apps.bdimg.com/libs/angular.js/1.4.6/angular.min.js"></script>
</head>
<script type="text/javascript"> angular.module('app', []) .controller('parentCtrl', ['$scope', function($scope) { $scope.args = {}; $scope.args.content = 'Nick DeveloperWorks'; }]) .controller('childCtrl', ['$scope', function($scope) { $scope.args = {}; $scope.args.content = 'Nick DeveloperWorks for test'; }]); </script>
<body ng-app="app">
<div ng-controller="parentCtrl">
    <input ng-model="args.content">
    <div ng-controller="childCtrl">
        <input ng-model="args.content">
    </div>
</div>
</body>
</html>

  測試結果是兩個輸入框的內容永遠不會同步。子做用域有實例數據對象,則不訪問父做用域。

獨立做用域

  獨立做用域是 AngularJS 中一個很是特殊的做用域,它只在 directive 中出現。在對 directive 的定義中,咱們添加上一個 scope:{} 屬性,就爲這個 directive 建立出了一個隔離做用域。

示例5: directive 建立出一個孤立做用域

angular.module('isolate', []).directive("isolate", function () { return { scope : {}, }; })

  獨立做用域最大的特色是不會原型繼承其父做用域,對外界的父做用域保持相對的獨立。所以,若是在定義了孤立做用域的 AngularJS directive 中想要訪問其父做用域的屬性,則獲得的值爲 undefined。代碼以下:

示例六:獨立做用域的隔離性

<!doctype html>
<html>
<head>
    <meta charset=utf-8"/>
    <title>scope nick</title>
    <script src="http://apps.bdimg.com/libs/angular.js/1.4.6/angular.min.js"></script>
</head>
<script type="text/javascript"> angular.module('app', []) .controller('ctrl', ['$scope', function($scope) { $scope.args = {}; }]) .directive("isolateDirective", function () { return { scope : {}, link : function($scope, $element, $attr) { console.log($scope.$args); //輸出 undefined
 } }; }); </script>
<body ng-app="app">
<div ng-controller="ctrl">
    <div isolate-directive></div>
</div>
</body>
</html>

  上面的代碼中經過在 directive 中聲明瞭 scope 屬性從而建立了一個做用域,其父做用域爲 ctrl 所屬的做用域。可是,這個做用域是孤立的,所以,它訪問不到父做用域的中的任何屬性。存在這樣設計機制的好處是:可以建立出一些列可複用的 directive,這些 directive 不會相互在擁有的屬性值上產生串擾,也不會產生任何反作用。

AngularJS 獨立做用域的數據綁定

  在繼承做用域中,咱們能夠選擇子做用域直接操做父做用域數據來實現父子做用域的通訊,而在獨立做用域中,子做用域不能直接訪問和修改父做用域的屬性和值。爲了可以使孤立做用域也能和外界通訊,AngularJS 提供了三種方式用來打破獨立做用域「孤立」這一限制。

單向綁定(@ 或者 @attr)

  這是 AngularJS 獨立做用域與外界父做用域進行數據通訊中最簡單的一種,綁定的對象只能是父做用域中的字符串值,而且爲單向只讀引用,沒法對父做用域中的字符串值進行修改,此外,這個字符串還必須在父做用域的 HTML 節點中以 attr(屬性)的方式聲明。

  使用這種綁定方式時,須要在 directive 的 scope 屬性中明確指定引用父做用域中的 HTML 字符串屬性,不然會拋異常。示例代碼以下:

實例七: 單向綁定示例

<!doctype html>
<html>
<head>
    <meta charset=utf-8"/>
    <title>scope nick</title>
    <script src="http://apps.bdimg.com/libs/angular.js/1.4.6/angular.min.js"></script>
</head>
<script> angular.module('isolateScope', []) .directive("isolateDirective", function () { return { replace : true, template: '<button>{{isolates}}</button>', scope : { isolates : '@', }, link : function($scope, $element, $attr) { $scope.isolates = "DeveloperWorks";//無效
 } }; }) .controller("ctrl", function ($scope) { $scope.btns = 'NICK'; }); </script>
<body ng-app="isolateScope" >
<div ng-controller="ctrl">
    <button>{{btns}}</button>
    <div isolate-directive isolates="{{btns}}"></div>
</div>
</body>
</html>

  上面的代碼,經過在 directive 中聲明瞭 scope:{isolates:'@'} 使得 directive 擁有了父做用域中 data-isolates (isolates爲自定義屬性,不加data也能夠,但建議加上data)這個 HTML 屬性所擁有的值,這個值在控制器 ctrl 中被賦值爲'nick'。因此,代碼的運行結果是頁面上有兩個名爲 nick的按鈕。

  咱們還注意到 link 函數中對 isolates 進行了修改,可是最終不會在運行結果中體現。這是由於 isolates 始終綁定爲父做用域中的 btns 字符串,若是父做用域中的 btns 不改變,那麼在孤立做用域中不管怎麼修改 isolates 都不會起做用。

引用綁定(&或者&attr)

  經過這種形式的綁定,孤立做用域將有能力訪問到父做用域中的函數對象,從而可以執行父做用域中的函數來獲取某些結果。這種方式的綁定跟單向綁定同樣,只能以只讀的方式訪問父做用函數,而且這個函數的定義必須寫在父做用域 HTML 中的 attr(屬性)節點上。

  這種方式的綁定雖然沒法修改父做用域的 attr 所設定的函數對象,可是卻能夠經過執行函數來改變父做用域中某些屬性的值,來達到一些預期的效果。示例代碼以下:

示例八:引用綁定示例

<!doctype html>
<html>
<head>
    <meta charset=utf-8"/>
    <title>scope nick</title>
    <script src="http://apps.bdimg.com/libs/angular.js/1.4.6/angular.min.js"></script>
</head>
<script> angular.module('isolateScope', []) .directive("isolateDirective", function () { return { replace : true, scope : { isolates : '&', }, link : function($scope, $element, $attr) { var func = $scope.isolates(); func(); } }; }) .controller("ctrl", function ($scope) { $scope.func = function () { console.log("Nick DeveloperWorks"); } }); </script>
<body ng-app="isolateScope" >
<div ng-controller="ctrl">
    <div isolate-directive data-isolates="func"></div>
</div>
</body>
</html>

  這個例子中,瀏覽器的控制檯將會打印「Nick DeveloperWorks」文字。

  上面的代碼中咱們在父做用域中指定了一個函數對象$scope.func,在孤立做用域中經過對 HTML 屬性的綁定從而引用了 func。須要注意的是 link 函數中對 func 對象的使用方法,$scope.isolates 得到的僅僅是函數對象,而不是調用這個對象,所以咱們須要在調用完$scope.isolates 以後再調用這個函數,才能獲得真正的執行結果。

雙向綁定(=或者=attr)

雙向綁定賦予 AngularJS 孤立做用域與外界最爲自由的雙向數據通訊功能。在雙向綁定模式下,孤立做用域可以直接讀寫父做用域中的屬性和數據。和以上兩種孤立做用域定義數據綁定同樣,雙向綁定也必須在父做用域的 HTML 中設定屬性節點來綁定。

雙向綁定很是適用於一些子 directive 須要頻繁和父做用域進行數據交互,而且數據比較複雜的場景。不過,因爲能夠自由的讀寫父做用域中的屬性和對象,因此在一些多個 directive 共享父做用域數據的場景下須要當心使用,很容易引發數據上的混亂。

示例代碼以下:

示例九:雙向綁定示例

<!doctype html>
<html>
<head>
    <meta charset=utf-8"/>
    <title>scope nick</title>
    <script src="http://apps.bdimg.com/libs/angular.js/1.4.6/angular.min.js"></script>
</head>
<script> angular.module('isolateScope', []) .directive("isolateDirective", function () { return { replace : true, template: '<button>{{isolates}}</button>', scope : { isolates : '=', }, link : function($scope, $element, $attr) { $scope.isolates.name = "NICK"; } }; }) .controller("ctrl", function ($scope) { $scope.btns = { name : 'nick', dw : 'DeveloperWorks' }; }); </script>
<body ng-app="isolateScope" >
<div ng-controller="ctrl">
    <button>{{btns.dw}}</button>
    <button>{{btns.name}}</button>
    <div isolate-directive data-isolates="btns"></div>
</div>
</body>
</html>

  上面的代碼運行的結果是瀏覽器頁面上出現三個按鈕,其中第一個按鈕標題爲「DeveloperWorks」,第二和第三個按鈕的標題爲「NICK」。

初始時父做用域中的$scope.btns.name爲小寫的「nick」,經過雙向綁定,孤立做用域中將父做用域的 name改寫成爲大寫的「NICK」而且直接生效,父做用域的值被更改。

  推薦:這篇關於做用域的文章也寫的不錯,能夠看看!

相關文章
相關標籤/搜索