首頁 web前端 js教程 深入探究AngularJS框架中Scope物件的超級教學_AngularJS

深入探究AngularJS框架中Scope物件的超級教學_AngularJS

May 16, 2016 pm 03:22 PM
angularjs javascript

一、遇到的問題
問題發生在使用 AngularJS 巢狀 Controller 的時候。因為每個 Controller 都有它對應的 Scope(相當於作用域、控制範圍),所以 Controller 的嵌套,也意味著 Scope 的巢狀。這時候如果兩個 Scope 內都有同名的 Model 會發生什麼事呢?從子 Scope 怎樣更新父 Scope 裡的 Model 呢?

這個問題很典型,比方說當前頁面是一個產品列表,那麼就需要定義一個 ProductListController

function ProductListController($scope, $http) {
  $http.get('/api/products.json')
    .success(function(data){
      $scope.productList = data;
    });
  $scope.selectedProduct = {};
}
登入後複製

你大概看到了在 Scope 裡還定義了一個 selectedProduct 的 Model,表示選取了某一個產品。這時會取得該產品詳情,而頁面透過 AngularJS 中的 $routeProvider 自動更新,拉取新的詳情頁模板,模板中有一個 ProductDetailController

function ProductDetailController($scope, $http, $routeParams) {
  $http.get('/api/products/'+$routeParams.productId+'.json')
    .success(function(data){
      $scope.selectedProduct = data;
    });
}
登入後複製

有趣的事情發生了,這裡也有一個 selectedProduct ,它會如何影響 ProductListController 中的 selectedProduct 呢?

答案是沒有影響。在AnuglarJS 裡子Scope 確實會繼承父Scope 中的對象,但當你試下對基本資料型別(string, number, boolean)的雙向資料綁定時,就會發現一些奇怪的行為,繼承並不像你想像的那樣工作。子 Scope 的屬性隱藏(覆寫)了父 Scope 中的同名屬性,對子 Scope 屬性(表單元素)的變更並不會更新父 Scope 屬性的值。這個行為其實不是 AngularJS 特有的,JavaScript 本身的原型鏈就是這樣運作的。開發者通常都沒有意識到 ng-repeat, ng-switch, ng-view 和 ng-include 統統都創建了他們新的子 scopes,所以在用到這些 directive 時也經常出問題。

二、解決的辦法
解決的辦法就是不使用基本資料型,而在 Model 永遠多加一點.

使用

<input type="text" ng-model="someObj.prop1">
登入後複製

來替代

<input type="text" ng-model="prop1">
登入後複製

是不是很坑爹?下面這個例子很清楚地表達了我所想表達的奇葩現象

app.controller('ParentController',function($scope){
  $scope.parentPrimitive = "some primitive"
  $scope.parentObj = {};
  $scope.parentObj.parentProperty = "some value";
});
app.controller('ChildController',function($scope){
  $scope.parentPrimitive = "this will NOT modify the parent"
  $scope.parentObj.parentProperty = "this WILL modify the parent";
});
登入後複製

查看 線上示範 DEMO
但是我真的確實十分很非常需要使用 string number 等原始資料型別呢? 2 個方法——

在子 Scope 中使用 $parent.parentPrimitive。 這將阻止子 Scope 創建它自己的屬性。
在父 Scope 中定義一個函數,讓子 Scope 調用,傳遞原始資料型別的參數給父親,從而更新父 Scope 中的屬性。 (不總是可行)
三、JavaScript 的原型鏈繼承
吐槽完畢,我們來深入了解 JavaScript 的原型鏈。這很重要,特別是當你從伺服器端開發轉到前端,你應該會很熟悉經典的 Class 類別繼承,我們來回顧一下。

假設父類別 parentScope 有以下成員屬性 aString, aNumber, anArray, anObject, 以及 aFunction。子類別 childScope 原型繼承父類別 parentScope,於是我們有:

201614150604637.png (619×257)

如果子Scope 嘗試去存取parentScope 中定義的屬性,JavaScript 會先在子Scope 中查找,如果沒有該屬性,則找它繼承的scope 去取得屬性,如果繼承的原型物件parentScope 中都沒有該屬性,那麼繼續在它的原型中尋找,從原型鏈一直往上直到到達rootScope。所以,下面的表達式結果都是 ture:

childScope.aString === 'parent string'
childScope.anArray[1] === 20
childScope.anObject.property1 === 'parent prop1'
childScope.aFunction() === 'parent output'
登入後複製

假設我們執行下面的語句

childScope.aString = 'child string'
登入後複製

原型鏈並沒有被查詢,反而是在 childScope 中增加了一個新屬性 aString。這個新屬性隱藏(覆蓋)了 parentScope 中的同名屬性。在下面我們討論 ng-repeat 和 ng-include 時這個概念很重要。

201614150634794.png (619×257)

假設我們執行這個操作:

childScope.anArray[1] = '22'
childScope.anObject.property1 = 'child prop1'
登入後複製

原型鏈被查詢了,因為物件 anArray 和 anObject 在 childScope 中沒有找到。它們在 parentScope 中被找到了,並且值被更新。 childScope 中沒有增加新的屬性,也沒有任何新的物件被建立。 (註:在 JavaScript 中,array 和 function 都是物件)

201614150658628.png (608×257)

假設我們執行這個操作:

childScope.anArray = [100, 555]
childScope.anObject = { name: 'Mark', country: 'USA' }
登入後複製

原型链没有被查询,并且子 Scope 新加入了两个新的对象属性,它们隐藏(覆盖)了 parentScope 中的同名对象属性。

201614150718718.png (608×320)

应该可以总结

如果读取 childScope.propertyX,并且 childScope 有属性 propertyX,那么原型链没有被查询。
如果设置 childScope.propertyX,原型链不会被查询。
最后一种情况,

delete childScope.anArray
childScope.anArray[1] === 22 // true
登入後複製

我们从 childScope 删除了属性,则当我们再次访问该属性时,原型链会被查询。删除对象的属性会让来自原型链中的属性浮现出来。

201614150741147.png (608×320)

四、AngularJS 的 Scope 继承
创建新的 Scope,并且原型继承:ng-repeat, ng-include, ng-switch, ng-view, ng-controller, directive with scope: true, directive with transclude: true
创建新的 Scope,但不继承:directive with scope: { ... }。它会创建一个独立 Scope。
注:默认情况下 directive 不创建新 Scope,即默认参数是 scope: false。

ng-include
登入後複製

假设在我们的 controller 中,

$scope.myPrimitive = 50;
$scope.myObject  = {aNumber: 11};
登入後複製

HTML 为:

<script type="text/ng-template" id="/tpl1.html">
  <input ng-model="myPrimitive">
</script>
<div ng-include src="'/tpl1.html'"></div>
 
<script type="text/ng-template" id="/tpl2.html">
  <input ng-model="myObject.aNumber">
</script>
<div ng-include src="'/tpl2.html'"></div>
登入後複製

每一个 ng-include 会生成一个子 Scope,每个子 Scope 都继承父 Scope。

201614151048789.png (541×115)

输入(比如”77″)到第一个 input 文本框,则子 Scope 将获得一个新的 myPrimitive 属性,覆盖掉父 Scope 的同名属性。这可能和你预想的不一样。

201614151107899.png (541×124)

输入(比如”99″)到第二个 input 文本框,并不会在子 Scope 创建新的属性,因为 tpl2.html 将 model 绑定到了一个对象属性(an object property),原型继承在这时发挥了作用,ngModel 寻找对象 myObject 并且在它的父 Scope 中找到了。

201614151132150.png (541×124)

如果我们不想把 model 从 number 基础类型改为对象,我们可以用 $parent 改写第一个模板:

<input ng-model="$parent.myPrimitive">
登入後複製

输入(比如”22″)到这个文本框也不会创建新属性了。model 被绑定到了父 scope 的属性上(因为 $parent 是子 Scope 指向它的父 Scope 的一个属性)。

201614151212469.png (541×117)

对于所有的 scope (原型继承的或者非继承的),Angular 总是会通过 Scope 的 $parent, $$childHead 和 $$childTail 属性记录父-子关系(也就是继承关系),图中为简化而未画出这些属性。

在没有表单元素的情况下,另一种方法是在父 Scope 中定义一个函数来修改基本数据类型。因为有原型继承,子 Scope 确保能够调用这个函数。例如,

// 父 Scope 中
$scope.setMyPrimitive = function(value) {
  $scope.myPrimitive = value;
登入後複製

查看 DEMO

ng-switch
ng-switch 的原型继承和 ng-include 一样。所以如果你需要对基本类型数据进行双向绑定,使用 $parent,或者将其改为 object 对象并绑定到对象的属性,防止子 Scope 覆盖父 Scope 的属性。
ng-repeat
ng-repeat 有一点不一样。假设在我们的 controller 里:

$scope.myArrayOfPrimitives = [ 11, 22 ];
$scope.myArrayOfObjects  = [{num: 101}, {num: 202}]
登入後複製

还有 HTML:

<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>
登入後複製

对于每一个 Item,ng-repeat 创建新的 Scope,每一个 Scope 都继承父 Scope,但同时 item 的值也被赋给了新 Scope 的新属性(新属性的名字为循环的变量名)。Angular ng-repeat 的源码实际上是这样的:

childScope = scope.$new(); // 子 scope 原型继承父 scope ...   
childScope[valueIdent] = value; // 创建新的 childScope 属性
登入後複製

如果 item 是一个基础数据类型(就像 myArrayOfPrimitives),本质上它的值被复制了一份赋给了新的子 scope 属性。改变这个子 scope 属性值(比如用 ng-model,即 num)不会改变父 scope 引用的 array。所以上面第一个 ng-repeat 里每一个子 scope 获得的 num 属性独立于 myArrayOfPrimitives 数组:

201614151333896.png (440×153)

这样的 ng-repeat 和你预想中的不一样。在 Angular 1.0.2 及更早的版本,向文本框中输入会改变灰色格子的值,它们只在子 Scope 中可见。Angular 1.0.3+ 以后,输入文本不会再有任何作用了。
我们希望的是输入能改变 myArrayOfPrimitives 数组,而不是子 Scope 里的属性。为此我们必须将 model 改为一个关于对象的数组(array of objects)。

所以如果 item 是一个对象,则对于原对象的一个引用(而非拷贝)被赋给了新的子 Scope 属性。改变子 Scope 属性的值(使用 ng-model,即 obj.num)也就改变了父 Scope 所引用的对象。所以上面第二个 ng-repeat 可表示为:

201614151355015.png (560×152)

这才是我们想要的。输入到文本框即会改变灰色格子的值,该值在父 Scope 和子 Scope 均可见。
ng-controller
使用 ng-controller 进行嵌套,结果和 ng-include 和 ng-switch 一样是正常的原型继承。所以做法也一样不再赘述。然而“两个 controller 使用 $scope 继承来共享信息被认为是不好的做法”
应该使用 service 在 controller 间共享数据。

如果你确实要通过继承来共享数据,那么也没什么特殊要做的,子 Scope 可以直接访问所有父 Scope 的属性。
directives
这个要分情况来讨论。

默认 scope: false – directive 不会创建新的 Scope,所以没有原型继承。这看上去很简单,但也很危险,因为你会以为 directive 在 Scope 中创建了一个新的属性,而实际上它只是用到了一个已存在的属性。这对编写可复用的模块和组件来说并不好。
scope: true – 这时 directive 会创建一个新的子 scope 并继承父 scope。如果在同一个 DOM 节点上有多个 directive 都要创建新 scope,则只有一个新 Scope 会创建。因为有正常的原型继承,所以和 ng-include, ng-switch 一样要注意基础类型数据的双向绑定,子 Scope 属性会覆盖父 Scope 同名属性。
scope: { ... } – 这时 directive 创建一个独立的 scope,没有原型继承。这在编写可复用的模块和组件时是比较好的选择,因为 directive 不会不小心读写父 scope。然而,有时候这类 directives 又经常需要访问父 scope 的属性。对象散列(object hash)被用来建立这个独立 Scope 与父 Scope 间的双向绑定(使用 ‘=')或单向绑定(使用 ‘@')。还有一个 ‘&' 用来绑定父 Scope 的表达式。这些统统从父 Scope 派生创建出本地的 Scope 属性。注意,HTML 属性被用来建立绑定,你无法在对象散列中引用父 Scope 的属性名,你必须使用一个 HTML 属性。例如,

和 scope: { localProp: '@parentProp' } 是无法绑定父属性 parentProp 到独立 scope的,你必须这样指定:
以及 scope: { localProp: '@theParentProp' }。独立的 scope 中 __proto__ 引用了一个 Scope 对象(下图中的桔黄色 Object),独立 scope 的 $parent 指向父 scope,所以尽管它是独立的而且没有从父 Scope 原型继承,它仍然是一个子 scope。

下面的图中,我们有 和 scope:

{ interpolatedProp: '@interpolated', twowayBindingProp: '=twowayBinding' }。
登入後複製

同时,假设 directive 在它的 link 函数里做了 scope.someIsolateProp = "I'm isolated"

201614151506832.png (467×157)

注意:在 link 函数中使用 attrs.$observe('attr_name', function(value) { ... } 来获取独立 Scope 用 ‘@' 符号替换的属性值。例如,在 link 函数中有 attrs.$observe('interpolated', function(value) { ... } 值将被设为 11. (scope.interpolatedProp 在 link 函数中是 undefined,相反scope.twowayBindingProp 在 link 函数中定义了,因为用了 ‘=' 符号)
transclude: true – 这时 directive 创建了一个新的 “transcluded” 子 scope,同时继承父 scope。所以如果模板片段中的内容(例如那些将要替代 ng-transclude 的内容)要求对父 Scope 的基本类型数据进行双向绑定,使用 $parent,或者将 model 一个对象的属性,防止子 Scope 属性覆盖父 Scope 属性。

transcluded 和独立 scope (如果有)是兄弟关系,每个 Scope 的 $parent 指向同一个父 Scope。当模板中的 scope 和独立 Scope 同时存在,独立 Scope 属性 $$nextSibling 将会指向模板中的 Scope。
在下图中,假设 directive 和上个图一样,只是多了 transclude: true

201614151536198.png (640×226)

查看 在线 DEMO,例子里有一个 showScope() 函数可以用来检查独立 Scope 和它关联的 transcluded scope。
总结
一共有四种 Scope:

普通进行原型继承的 Scope —— ng-include, ng-switch, ng-controller, directive with scope: true
普通原型继承的 Scope 但拷贝赋值 —— ng-repeat。 每个 ng-repeat 的循环都创建新的子 Scope,并且子 Scope 总是获得新的属性。
独立的 isolate scope —— directive with scope: {...}。它不是原型继承,但 ‘=', ‘@' 和 ‘&' 提供了访问父 Scope 属性的机制。
transcluded scope —— directive with transclude: true。它也遵循原型继承,但它同时是任何 isolate scope 的兄弟。
对于所有的 Scope,Angular 总是会通过 Scope 的 $parent, $$childHead 和 $$childTail 属性记录父-子关系。

PS:scope和rootscope的区别
scope是html和单个controller之间的桥梁,数据绑定就靠他了。rootscope是各个controller中scope的桥梁。用rootscope定义的值,可以在各个controller中使用。下面用实例详细的说明一下。
1,js代码

phonecatApp.controller('TestCtrl',['$scope','$rootScope', 
  function($scope,$rootScope) { 
    $rootScope.name = 'this is test'; 
  } 
]); 
 
phonecatApp.controller('Test111Ctrl',['$scope','$rootScope', 
  function($scope,$rootScope) { 
    $scope.name = $rootScope.name; 
  } 
]); 
登入後複製

2,html代码

<div ng-controller="TestCtrl"> 
  I set the global variable.<strong>{{$root.name}}</strong> 
</div> 
 
<div ng-controller="Test111Ctrl"> 
  1,get global variable .<strong>{{name}}</strong><br> 
  2,get global variable .<strong>{{$root.name}}</strong> 
</div> 
登入後複製

3,显示结果

I set the global variable.this is test 
1,get global variable .this is test 
2,get global variable .this is test 
登入後複製

由结果可以看出来,$rootScope.name设置的变量,在所有controller里面都是可以直接用{{$root.name}}来显示的,很强大。那当然也可以赋值给scope.
 

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

熱門話題

Java教學
1664
14
CakePHP 教程
1422
52
Laravel 教程
1316
25
PHP教程
1268
29
C# 教程
1240
24
如何使用WebSocket和JavaScript實現線上語音辨識系統 如何使用WebSocket和JavaScript實現線上語音辨識系統 Dec 17, 2023 pm 02:54 PM

如何使用WebSocket和JavaScript實現線上語音辨識系統引言:隨著科技的不斷發展,語音辨識技術已成為了人工智慧領域的重要組成部分。而基於WebSocket和JavaScript實現的線上語音辨識系統,具備了低延遲、即時性和跨平台的特點,成為了廣泛應用的解決方案。本文將介紹如何使用WebSocket和JavaScript來實現線上語音辨識系

WebSocket與JavaScript:實現即時監控系統的關鍵技術 WebSocket與JavaScript:實現即時監控系統的關鍵技術 Dec 17, 2023 pm 05:30 PM

WebSocket與JavaScript:實現即時監控系統的關鍵技術引言:隨著互聯網技術的快速發展,即時監控系統在各個領域中得到了廣泛的應用。而實現即時監控的關鍵技術之一就是WebSocket與JavaScript的結合使用。本文將介紹WebSocket與JavaScript在即時監控系統中的應用,並給出程式碼範例,詳細解釋其實作原理。一、WebSocket技

如何利用JavaScript和WebSocket實現即時線上點餐系統 如何利用JavaScript和WebSocket實現即時線上點餐系統 Dec 17, 2023 pm 12:09 PM

如何利用JavaScript和WebSocket實現即時線上點餐系統介紹:隨著網路的普及和技術的進步,越來越多的餐廳開始提供線上點餐服務。為了實現即時線上點餐系統,我們可以利用JavaScript和WebSocket技術。 WebSocket是一種基於TCP協定的全雙工通訊協議,可實現客戶端與伺服器的即時雙向通訊。在即時線上點餐系統中,當使用者選擇菜餚並下訂單

如何使用WebSocket和JavaScript實現線上預約系統 如何使用WebSocket和JavaScript實現線上預約系統 Dec 17, 2023 am 09:39 AM

如何使用WebSocket和JavaScript實現線上預約系統在當今數位化的時代,越來越多的業務和服務都需要提供線上預約功能。而實現一個高效、即時的線上預約系統是至關重要的。本文將介紹如何使用WebSocket和JavaScript來實作一個線上預約系統,並提供具體的程式碼範例。一、什麼是WebSocketWebSocket是一種在單一TCP連線上進行全雙工

JavaScript與WebSocket:打造高效率的即時天氣預報系統 JavaScript與WebSocket:打造高效率的即時天氣預報系統 Dec 17, 2023 pm 05:13 PM

JavaScript和WebSocket:打造高效的即時天氣預報系統引言:如今,天氣預報的準確性對於日常生活以及決策制定具有重要意義。隨著技術的發展,我們可以透過即時獲取天氣數據來提供更準確可靠的天氣預報。在本文中,我們將學習如何使用JavaScript和WebSocket技術,來建立一個高效的即時天氣預報系統。本文將透過具體的程式碼範例來展示實現的過程。 We

簡易JavaScript教學:取得HTTP狀態碼的方法 簡易JavaScript教學:取得HTTP狀態碼的方法 Jan 05, 2024 pm 06:08 PM

JavaScript教學:如何取得HTTP狀態碼,需要具體程式碼範例前言:在Web開發中,經常會涉及到與伺服器進行資料互動的場景。在與伺服器進行通訊時,我們經常需要取得傳回的HTTP狀態碼來判斷操作是否成功,並根據不同的狀態碼來進行對應的處理。本篇文章將教你如何使用JavaScript來取得HTTP狀態碼,並提供一些實用的程式碼範例。使用XMLHttpRequest

javascript如何使用insertBefore javascript如何使用insertBefore Nov 24, 2023 am 11:56 AM

用法:在JavaScript中,insertBefore()方法用於在DOM樹中插入一個新的節點。這個方法需要兩個參數:要插入的新節點和參考節點(即新節點將要插入的位置的節點)。

JavaScript與WebSocket:打造高效率的即時影像處理系統 JavaScript與WebSocket:打造高效率的即時影像處理系統 Dec 17, 2023 am 08:41 AM

JavaScript是一種廣泛應用於Web開發的程式語言,而WebSocket則是一種用於即時通訊的網路協定。結合二者的強大功能,我們可以打造一個高效率的即時影像處理系統。本文將介紹如何利用JavaScript和WebSocket來實作這個系統,並提供具體的程式碼範例。首先,我們需要明確指出即時影像處理系統的需求和目標。假設我們有一個攝影機設備,可以擷取即時的影像數

See all articles