剖析AngularJS作用域

来源:http://www.cnblogs.com/giggle/archive/2016/08/15/5769047.html
-Advertisement-
Play Games

對AngularJS的作用域做深入剖析,該隨筆主要分為兩大板塊:JavaScript原型鏈、AngularJS作用域。 ...


一、概要

在AngularJS中,子作用域(child scope)基本上都要繼承自父作用域(parent scope)。

但,事無絕對,也有特例,那就是指令中scope設置項為對象時,即scope:{…},這將會讓指令創建一個並不繼承自父作用域的子作用域,我們稱之為隔離作用域(isolated scope)。

指令中的scope一共可以有三個值,下麵我們再來溫習下:

指令之scope

scope: false

預設值,指令不會新建一個作用域,使用父級作用域。

scope: true

指令會創建一個新的子作用域,原型繼承於父級作用域。

scope: {…}

指令會新建一個隔離作用域,不會原型繼承父作用域。

那麼,理解AngularJS中作用域繼承有什麼用呢?

原因之一就是,有利於我們使用“雙向綁定”(也就是在form表單元素中綁定ng-model),例如,在初學AngularJS時,我們常會遇到“雙向綁定”不起作用的時候,如下:

<!DOCTYPE html>
    <head>
        <meta charset="utf-8"/>
        <script src="angular.js"></script>
    </head>
    <body ng-app="myApp">
        parent:<input type="text" ng-model="name"/>
        <div ng-controller="TestCtrl">
            child: <input type="text" ng-model="name"/>    
        </div>
        <script>
            var app = angular.module('myApp', []);
            app.controller('TestCtrl', function(){});
        </script>
    </body>
</html>

 執行上述代碼,結果如下:

 

其實AngularJS的作用域繼承與JavaScript的原型繼承是一樣的邏輯,固,如果想要上述代碼實現雙向綁定,我們可以利用ng-model綁定對象屬性,來達到目的,如下:

<!DOCTYPE html>
    <head>
        <meta charset="utf-8"/>
        <script src="angular.js"></script>
    </head>
    <body ng-app="myApp">
        parent:<input type="text" ng-model="obj.name"/>
        <div ng-controller="TestCtrl">
            child: <input type="text" ng-model="obj.name"/>    
        </div>
        <script>
            var app = angular.module('myApp', []);
            app.run(function($rootScope){
                $rootScope.obj = {};
            });
            app.controller('TestCtrl', function(){});
        </script>
    </body>
</html>

 執行上述代碼,結果如下:

 

二、JavaScript原型繼承

上面已經提到了AngularJS的作用域繼承與JavaScript的原型繼承是一樣兒一樣兒的,所以,我們首先來初步溫習下JavaScript的原型繼承。

假設,我們有父作用域(ParentScope),且其中包含了屬性aString、aNumber、anArray、anObject 以及aFunction。

好了,如果現在有一子作用域(ChildScope)繼承於這個父作用域(ParentScope),如下所示:

 

當我們通過ChildScope想訪問一個屬性時,JavaScript內部是如何為我們查找的呢?

答案:

首先JavaScript會第一時間在當前作用域(如這裡的ChildScope)中查找是否有這一屬性,如果在當前作用域中沒有找到,

那麼JavaScript就會沿著原型這條鏈(如這裡的:ChildScope-->ParentScope-->RootScope),一直找下去,倘若在某一父作用域中找到,就返回這個屬性值並停止原型鏈查找;

倘若一直找到根作用域(如這裡的RootScope)都沒有找到,則返undefined。

故而,下麵這些表達式,結果都為true:

childScope.aString === 'parent string' //true

childScope.anArray[1] === 20  //true

childScope.anObject.property1 === 'parent prop1'  //true

childScope.aFunction() === 'parent output'  //true

好了,假如,我們這麼做呢:

childScope.aString = 'child string'

那麼只會在childScope中新建一個值為’child string’的aString屬性。在這之後,倘若我們還想通過childScope訪問parentScope中aString屬性時,就束手無策了。

因為childScope已經有了一個aString屬性。理解這一點是非常重要的,在我們討論ng-repeat 和ng-include之前。

 

接下來,我們再這麼做呢:

childScope.anArray[1] = '22'

childScope.anObject.property1 = 'child prop1'

這樣會沿著原型鏈查找的,並改變屬性中的值。

為什麼呢?

原因就是我們這次賦值的是對象中的屬性。

好了,接下來,我們再這麼做:

childScope.anArray = [100, 555]

childScope.anObject = {name: 'Mark', country: 'USA'}

這樣做的效果,如同上面childScope.aString = ‘child string’一樣,不會啟動原型鏈查找。

 

總結:

1、如果我們讀取子作用域的屬性時,且該子作用域有這個屬性,則不會啟動原型鏈查找;

2、如果我們賦值子作用域的屬性時,依然不會啟動原型鏈查找。

1、If we read childScope.propertyX, and childScope has propertyX, then the prototype chain is not consulted.
2、If we set childScope.propertyX, the prototype chain is not consulted.
EnglishExpression

通過上面的總結,如果我們想讓childScope在已有自己的anArray屬性後,仍然訪問parentScope中的anArray值呢?

哈哈,刪除childScope中的anArray屬性嘛,如下:

delete childScope.anArray

childScope.anArray[1] === 22 //true

三、Angular作用域繼承

在前面“概要”部分已經說到Angular中子作用域基本上都繼承自父作用域,但也有例外(scope:{…}指令),但並沒有具體說明哪些指令會創建子作用域等,現在歸類如下:

創建子作用域,且繼承自父作用域

1、  ng-repeat

2、  ng-include

3、  ng-switch

4、  ng-controller

5、  directive (scope: true)

6、  directive(transclude: true)

創建子作用域,但並不繼承自父作用域

directive(scope: {…})

不創建作用域

directive(scoep: false)

下麵分別看看:

 --ng-include--

假設我們有一控制器,內容如下:

module.controller('parentCtrl', function($scope){
    $scope.myPrimitive = 50;
    $scope.myObject = {aNumber: 11};
});

有HTML代碼如下:

<div ng-controller="parentCtrl">
    parent-myPrimitive:<input type="text" ng-model="myPrimitive"/><br/>
    parent-obj.aNumber:<input type="text" ng-model="myObject.aNumber"/><br/>
    <script type="text/ng-template" id="/tpl1.html">
        includ-myPrimitive:<input ng-model = "myPrimitive"/>
    </script>
    <div ng-include src="'/tpl1.html'"></div>
    <script type="text/ng-template" id="/tpl2.html">
        includ-obj.aNumber:<input ng-model="myObject.aNumber"/>
    </script>
    <div ng-include src="'/tpl2.html'"></div>
</div>
代碼稍長,請自行打開

因為每個ng-include指令,都會創建一個新的作用域,且繼承於父作用域。固,代碼中的關係圖如下:

在chrome(需加--disable-web-security)下,執行上述代碼後,得下:

從執行結果看,符合上面的關係圖。

好了,倘若我們在第三個input框中,敲入字元(如77)後,子作用域會創建一個自己的myPrimitive屬性,從而阻止原型鏈查找。

如下所示:

 

 

倘若,我們在第四個input框中,輸入字元(如99)呢?子作用域會沿著原型鏈查找並修改,因為在這個input框中,我們綁定的是對象屬性。

如下圖所示:

 

如果我們想讓第三個input框達到第四個input框的效果,並且不使用對象屬性的方法,那麼我們可以利用$parent來達到目的。

修改第三個input框的HTML代碼:

includ-myPrimitive:<input ng-model="$parent.myPrimitive"/>

好了,這個時候,我們再在第三個input框中輸入22時,就會沿著原型鏈查找了,達到與第四個input框一樣的效果(因為$parent是為了讓子作用域訪問父作用域設置的屬性)。

 

除開$parent,還有$$childHead和$$childTail都是為了子作用和父作用域通信服務的。註意,是所有子作用域哦,所以包括了scope:{…}指令的情況。

--ng-switch--

ng-switch會創建子作用域,效果與上面所述的ng-include一樣。倘若,我們想要實現子作用域與父作用域實現雙向綁定,就使用$parent或者對象屬性。

Demo如下:

<!DOCTYPE html>
    <head>
        <meta charset="utf-8"/>
        <script src="angular.js"></script>
    </head>
    <body ng-app="myModule">
        <div ng-controller="parentCtrl">
            <input type="text" ng-model="obj.something"/>
            <div ng-switch="name">
                <div ng-switch-when="Monkey">
                    <h1>This is Monkey</h1>
                    <input type="text" ng-model="obj.something"/>
                </div>
                <div ng-switch-default>
                    <h1>This is Default</h1>
                </div>
            </div>
        </div>
        <script>
            var module = angular.module('myModule', []);
            module.controller('parentCtrl', function($scope){
                $scope.obj = {};
                $scope.name = "Monkey";
            });
        </script>
    </body>
</html>
代碼稍長,請自行打開

執行上述代碼,效果如下:

--ng-repeat--

ng-repeat也會創建子作用域,不過與上面講述的ng-include、ng-switch不同的是,ng-repeat會為每個遍歷的元素創建子作用域,且繼承自同一父作用域。

好了,下麵我們來具體看看,假如,現在我們有一控制器,如下:

module.controller('parentCtrl', function($scope){
    $scope.myArrayOfPrimitives = [11, 22];
    $scope.myArrayOfObjects = [{num: 101}, {num: 202}];                
});

HTML代碼如下:

<div ng-controller="parentCtrl">
    <ul>
        <li ng-repeat="num in myArrayOfPrimitives">
            <input ng-model="num">
        </li>
    </ul>
    <ul>
        <li ng-repeat="obj in myArrayOfObjects">
            <input ng-model="obj.num">
        </li>
    </ul>
</div>

因為,ng-repeat會為每個元素都創建一個子作用域,如上面代碼中<li ng-repeat=”num in myArrayOfPrimitives”>,ng-repeat指令會用num,去遍歷myArrayOfPrimitives:[11, 22]中的數據,即創建兩個繼承自父作用域的子作用域。且,在子作用域中會創建自己的屬性num,因此,倘若子作用域中的num值變動,肯定不會改變父作用域中myArrayOfPrimitives的相應數據咯。

然而,HTML代碼中第二個出現ng-repeat的地方<li ng-repeat=”obj in myArrayOfObjects”>,當子作用域變動時,會影響到父作用域中對應的元素。因為數組myArrayOfObjects:[{…}, {…}]中的元素為對象,固而,遍歷myArrayOfObjects中元素時,子作用域賦值的是對象,引用類型嘛,所以子作用域中變動對象屬性時,肯定會影響父作用域的相關值咯。

 

--ng-controller--

ng-controller指令與ng-include、ng-switch一樣,即創建子作用域,且繼承自父作用域。

--directives--

詳情見“初探指令

需要註意的是,scope為對象的指令(scope:{…}),雖然該類指令的作用域為隔離作用域,但是,它任然可以通過$parent訪問父作用域。

Isolate scope's __proto__ references Object. Isolate scope's $parent references the parent scope, so although it is isolated and doesn't inherit prototypically from the parent scope, it is still a child scope. 
EnglishExpression

Demo如下:

<!DOCTYPE html>
    <head>
        <meta charset="utf-8"/>
        <script src="angular.js"></script>
    </head>
    <body ng-app="myApp">
        <div ng-controller="TestCtrl">
            <input type="text" ng-model="name"/> 
            <test></test>            
        </div>
        <script>
            var app = angular.module('myApp', []);
            app.controller('TestCtrl', function($scope){
                $scope.name = 'Monkey';
            });
            app.directive('test', function(){
                return {
                    restrict: 'E',
                    scope: {},
                    controller: function($scope){
                        $scope.name = $scope.$parent.name;
                    },
                    template: '<input type="text" ng-model="$parent.name"/>'
                };
            });
        </script>
    </body>
</html>
代碼稍長,請自行打開

執行上述代碼,操作如下:

四、總結

有四種類型的子作用域:

1、要繼承於父作用域—ng-include, ng-switch, ng-controller, directive(scope: true).

2、要繼承於父作用域,但會為每個元素創建一個子作用域—ng-repeat.

3、隔離作用域—directive(scope:{…}).

4、要繼承於父作用域,且與任何的隔離作用的指令為兄弟關係—directive(transclude: true).

the directive creates a new "transcluded" child scope, which prototypically inherits from the parent scope. The transcluded and the isolated scope (if any) are siblings -- the $parent property of each scope references the same parent scope. When a transcluded and an isolate scope both exist, isolate scope property $$nextSibling will reference the transcluded scope. 
transclude:true

--Summary_in_English--

There are four types of scopes:

1、normal prototypal scope inheritance -- ng-include, ng-switch, ng-controller, directive with scope: true
2、normal prototypal scope inheritance with a copy/assignment -- ng-repeat. Each iteration of ng-repeat creates a new child scope, and that new child scope always gets a new property.
3、isolate scope -- directive with scope: {...}. This one is not prototypal, but '=', '@', and '&' provide a mechanism to access parent scope properties, via attributes.
4、transcluded scope -- directive with transclude: true. This one is also normal prototypal scope inheritance, but it is also a sibling of any isolate scope.
For all scopes (prototypal or not), Angular always tracks a parent-child relationship (i.e., a hierarchy), via properties $parent and $$childHead and $$childTail.
Summary
五、參考

Understanding Scope

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • sqlalchemy簡介 SQLAlchemy是Python編程語言下的一款開源軟體。提供了SQL工具包及對象關係映射(ORM)工具,使用MIT許可證發行。 SQLAlchemy“採用簡單的Python語言,為高效和高性能的資料庫訪問設計,實現了完整的企業級持久模型”。SQLAlchemy的理念是, ...
  • 整理這幾天里寫的幾個小程式,都是迴圈練習//設N是一個四位數,它的9倍恰好是其反序數//(例如:1234的反序數是4321),求N值package Azhi;public class Job_5 {public static void main(String[] args) { for(int n= ...
  • --> List 列表中的自動添加的多餘空間長度該怎麼去除呢?...(已解決,是char 數組中的空字元) if (buffer[i] == '\r' || buffer[i] == '\n' || buffer[i] == ' ' || buffer[i] == '\t') { continue; ...
  • 執行環境(Execution Context,也稱為"執行上下文")是JavaScript中最為重要的一個概念。執行環境定義了變數或函數有權訪問的其它數據,決定了各自的行為。當JavaScript代碼執行的時候,會進入不同的執行環境,這些不同的執行環境就構成了執行環境棧。 JavaScript中主要 ...
  • <style type="text/css">html,body{height:100%;}.text{width:100%;height:30%;background:#ccc;text-align:center;position:absolute;}.text:before{content:'' ...
  • CSS常用樣式之段落樣式(行高、段落縮進、段落對齊、文字間距、文字溢出、段落換行) ...
  • PFold.js是一款摺疊紙片插件,支持定義摺疊紙牌數量、摺疊動畫效果、摺疊方向,而且還支持摺疊結束後回調方法。 線上實例 使用方法 複製 複製 參數詳解 方法Method 下載 ...
  • CSS製作三角形和按鈕 用上一篇博文中關於邊框樣式的知識點,能製作出三角形和按鈕。 我先說如何製作三角形吧,相信大家在平時逛網站的時候都會看到一些導航欄中的三角形吧,比如說: 網易首頁的頭部菜單欄中,也會有這樣的三角形 當滑鼠經過時,三角形會垂直翻轉,如下 現在我分享製作三角形的做法,主要是利用邊框 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...