javascript what 數據綁定在AngularJS中如何工作?




what is $scope in angularjs (11)

數據綁定在AngularJS框架中如何工作?

我沒有在他們的網站上找到技術細節。 當數據從視圖傳播到模型時,它如何工作或多或少都很清楚。 但是,AngularJS如何在沒有setter和getters的情況下跟踪模型屬性的變化?

我發現有JavaScript觀察者可以做這項工作。 但是它們在Internet Explorer 6Internet Explorer 7不受支持。 那麼,AngularJS如何知道我改變了以下內容並在視圖上反映了這種變化?

myobject.myproperty="new value";

  1. 單向數據綁定是一種從數據模型中獲取值並插入到HTML元素中的方法。 沒有辦法從視圖更新模型。 它用於傳統的模板系統。 這些系統只能在一個方向上綁定數據。

  2. Angular應用程序中的數據綁定是模型和視圖組件之間數據的自動同步。

數據綁定使您可以將模型視為應用程序中的單一來源。 該視圖始終是該模型的投影。 如果模型發生變化,視圖會反映變化,反之亦然。


通過臟檢查$scope對象

Angular在$scope對像中維護一個簡單的觀察者array 。 如果您檢查任何$scope您會發現它包含一個名為$$watchersarray

每個觀察者都是包含其他內容的object

  1. 觀察者正在監視的表達。 這可能只是一個attribute名稱,或者更複雜的東西。
  2. 表達式的最後已知值。 這可以根據表達式的當前計算值進行檢查。 如果值不同,觀察者將觸發該函數並將$scope標記為臟。
  3. 如果觀察者髒了,將執行一個函數。

如何定義觀察者

在AngularJS中定義觀察者有很多不同的方法。

  • 您可以顯式$watch $scope一個attribute

    $scope.$watch('person.username', validateUnique);
    
  • 您可以在模板中放置一個{{}}插值(在當前$scope為您創建一個觀察器)。

    <p>username: {{person.username}}</p>
    
  • 你可以要求一個指令,如ng-model來為你定義監視器。

    <input ng-model="person.username" />
    

$digest循環會檢查所有觀察者的最後值

當我們通過正常渠道(ng-model,ng-repeat等)與AngularJS交互時,摘要循環將由指令觸發。

摘要循環是$scope及其所有子項深度優先遍歷 。 對於每個$scope object ,我們迭代它的$$watchers array並評估所有表達式。 如果新的表達式值與最後一個已知值不同,則調用觀察者的函數。 這個函數可能會重新編譯DOM的一部分,重新​​計算$scope的值,觸發一個AJAX request ,任何你需要做的事情。

遍歷每個範圍,並對每個手錶表達式進行評估並根據最後一個值進行檢查。

如果觀察者被觸發,則$scope是臟的

如果觀察者被觸發,則應用程序知道某些內容已更改,並且$scope被標記為臟。

觀察者函數可以改變$scope$scope parent $scope上的其他屬性。 如果一個$watcher函數被觸發,我們不能保證我們的其他$scope仍然是乾淨的,所以我們再次執行整個摘要循環。

這是因為AngularJS具有雙向綁定,因此可以將數據傳回$scope樹。 我們可能會改變已經消化的更高$scope的價值。 也許我們改變$rootScope的值。

如果$digest是臟的,我們再次執行整個$digest循環

我們不斷循環到$digest循環,直到摘要循環清理完畢(所有$watch表達式都具有與上一周期相同的值),或者達到摘要限制。 默認情況下,此限制設置為10。

如果我們達到摘要限制,AngularJS將在控制台中引發錯誤:

10 $digest() iterations reached. Aborting!

摘要在機器上很難,但開發人員很容易

正如您所看到的,每當AngularJS應用程序發生變化時,AngularJS都會檢查$scope層次結構中的每個觀察者,以了解如何響應。 對於開發人員來說,這是一個巨大的生產力優勢,因為您現在需要編寫幾乎沒有接線代碼,AngularJS會注意到值是否已更改,並使應用程序的其餘部分與更改保持一致。

從機器的角度來看,雖然這是非常低效的,並且如果我們創建了太多觀察者,我們的應用程序就會放慢速度。 Misko引用了大約4000名觀察者的數據,然後您的應用在舊版瀏覽器上感覺會變慢。

例如,如果您通過大型JSON array ng-repeat ,則此限制很容易達到。 您可以使用諸如一次性綁定之類的功能來減輕編譯模板而不創建觀察者。

如何避免創建過多的觀察者

每當用戶與您的應用程序進行交互時,應用程序中的每個觀察者都將至少評估一次。 優化AngularJS應用的很大一部分是減少$scope樹中觀察者的數量。 一個簡單的方法是使用一次綁定

如果你的數據很少變化,你可以使用:: syntax綁定一次,如下所示:

<p>{{::person.username}}</p>

要么

<p ng-bind="::person.username"></p>

只有當包含模板被渲染並且數據被加載到$scope時,綁定才會被觸發。

當你對許多項目進行ng-repeat時,這一點尤其重要。

<div ng-repeat="person in people track by username">
  {{::person.username}}
</div>

顯然,沒有定期檢查Scope是否有附加到它的對像有任何更改。 並非所有連接到範圍的對像都被監視。 範圍原型維護$$觀察者Scope只在調用$digest時迭代通過這個$$watchers

Angular向$ $$ watchers中的每一個添加了一個觀察者

  1. {{expression}} - 在您的模板(以及其他任何有表達式的地方)或我們定義ng模型時。
  2. $ scope。$ watch('expression / function') - 在你的JavaScript中,我們可以附加一個觀察角度的範圍對象。

$ watch函數需要三個參數:

  1. 首先是一個觀察者函數,它只是返回對象,或者我們可以添加一個表達式。

  2. 其次是一個監聽器功能,當對象發生變化時會被調用。 所有的東西像DOM變化都將在這個函數中實現。

  3. 第三個是一個可選參數,它包含一個布爾值。 如果它的真實,有深度的觀察對象,並且它的錯誤角度只是看對象的參考。 $ watch的粗略實現看起來像這樣

Scope.prototype.$watch = function(watchFn, listenerFn) {
   var watcher = {
       watchFn: watchFn,
       listenerFn: listenerFn || function() { },
       last: initWatchVal  // initWatchVal is typically undefined
   };
   this.$$watchers.push(watcher); // pushing the Watcher Object to Watchers  
};

Angular有一個有趣的東西叫Digest Cycle。 $ digest循環由於調用$ scope。$ digest()而開始。 假設您通過ng-click指令更改處理函數中的$ scope模型。 在這種情況下,AngularJS通過調用$ digest()來自動觸發$ digest循環。除了ng-click,還有其他幾個內置的指令/服務可以讓你更改模型(例如ng-model,$ timeout等)並自動觸發$摘要循環。 $ digest的粗略實現看起來像這樣。

Scope.prototype.$digest = function() {
      var dirty;
      do {
          dirty = this.$$digestOnce();
      } while (dirty);
}
Scope.prototype.$$digestOnce = function() {
   var self = this;
   var newValue, oldValue, dirty;
   _.forEach(this.$$watchers, function(watcher) {
          newValue = watcher.watchFn(self);
          oldValue = watcher.last;   // It just remembers the last value for dirty checking
          if (newValue !== oldValue) { //Dirty checking of References 
   // For Deep checking the object , code of Value     
   // based checking of Object should be implemented here
             watcher.last = newValue;
             watcher.listenerFn(newValue,
                  (oldValue === initWatchVal ? newValue : oldValue),
                   self);
          dirty = true;
          }
     });
   return dirty;
 };

如果我們使用JavaScript的setTimeout()函數更新範圍模型,Angular無法知道您可能會更改哪些內容。 在這種情況下,我們有責任手動調用$ apply(),這會觸發$ digest循環。 同樣,如果您有一個指令設置DOM事件偵聽器並更改處理函數中的某些模型,則需要調用$ apply()以確保更改生效。 $ apply的主要思想是我們可以執行一些不知道Angular的代碼,但代碼可能會改變範圍。 如果我們將這個代碼封裝在$ apply中,它將負責調用$ digest()。 $ apply()的粗略實現。

Scope.prototype.$apply = function(expr) {
       try {
         return this.$eval(expr); //Evaluating code in the context of Scope
       } finally {
         this.$digest();
       }
};

我自己想了一會兒。 如果沒有setter, AngularJS會如何通知$scope對象的更改? 它輪詢他們嗎?

它實際上做的是這樣的:你修改模型的任何“正常”地方已經從AngularJS的內部調用過$apply ,所以它會在代碼運行後自動調用$apply 。 假設你的控制器有一個連接到某個元素的方法。 因為AngularJS將這種方法的調用連接在一起,所以它有機會在適當的地方執行$apply 。 同樣,對於在視圖中顯示正確的表達式,那些由AngularJS執行,因此它執行$apply

當文檔討論不得不為AngularJS以外的代碼手動調用$apply ,它是在討論代碼,這些代碼在運行時不是源自調用堆棧中的AngularJS本身。


以下是使用輸入字段與AngularJS進行數據綁定的示例。 我會在稍後解釋

HTML代碼

<div ng-app="myApp" ng-controller="myCtrl" class="formInput">
     <input type="text" ng-model="watchInput" Placeholder="type something"/>
     <p>{{watchInput}}</p> 
</div>

AngularJS代碼

myApp = angular.module ("myApp", []);
myApp.controller("myCtrl", ["$scope", function($scope){
  //Your Controller code goes here
}]);

正如你在上面的例子中看到的, AngularJS使用ng-model來監聽並觀察HTML元素上發生了什麼,特別是在input字段上。 什麼時候發生,做點什麼。 在我們的例子中, ng-model綁定到我們的視圖,使用鬍子符號{{}} 。 無論輸入字段內輸入什麼,都會立即顯示在屏幕上。 這就是數據綁定的美妙之處,以最簡單的形式使用AngularJS。

希望這可以幫助。

Codepen看到一個可用的例子


用圖片解釋:

數據綁定需要映射

範圍中的引用不完全是模板中的引用。 當你數據綁定兩個對象時,你需要第三個接受第一個對象並修改其他對象。

在這裡,當你修改<input> ,你可以觸摸data-ref3 。 經典的數據綁定機制將改變data-ref4 。 那麼其他{{data}}表達式將如何移動?

事件導致$ digest()

Angular維護每個綁定的oldValuenewValue 。 在每個Angular事件之後 ,著名的$digest()循環將檢查WatchList以查看是否有更改。 這些Angular事件ng-clickng-change$http completed ...只要任何oldValuenewValue不同, $digest()就會循環。

在前面的圖片中,它會注意到data-ref1和data-ref2已經改變。

結論

這有點像雞蛋和雞肉。 你永遠不知道誰是首發的,但希望大部分時間都能按預期工作。

另一點是你可以很容易地理解內存和CPU的簡單綁定的影響。 希望台式機足夠處理這個問題。 手機並不那麼強大。


這是我的基本理解。 這可能是錯的!

  1. 通過傳遞函數(將要觀看的事物返回)到$watch方法來$watch
  2. 監視項目的更改必須在由$apply方法包裝的代碼塊內進行。
  3. $apply結束時,會調用$digest方法,它會檢查每個手錶並檢查自上次運行$digest以來是否發生了更改。
  4. 如果發現任何更改,則摘要將被再次調用,直到所有更改穩定為止。

在正常的開發中,HTML中的數據綁定語法告訴AngularJS編譯器為您創建手錶,並且控制器方法在$apply已經運行。 所以對於應用程序開發者來說,它是全透明的


我碰巧需要將一個人的數據模型與一個表單關聯起來,我所做的是將數據與表單直接映射。

例如,如果模型有類似的東西:

$scope.model.people.name

表單的控制輸入:

<input type="text" name="namePeople" model="model.people.name">

這樣,如果修改對象控制器的值,這將在視圖中自動反映。

我通過模型的示例是從服務器數據更新的,當您要求根據書面加載的郵政編碼和郵政編碼列出與該視圖關聯的殖民地和城市時,默認情況下會為用戶設置第一個值。 而且,我工作得很好,發生了什麼,是angularJS有時需要幾秒鐘來刷新模型,為此,您可以在顯示數據時放置微調器。


AngularJS會記住該值,並將其與以前的值進行比較。 這是基本的髒檢查。 如果價值發生變化,則會觸髮變化事件。

$apply()方法,這是您在從非AngularJS世界轉換到AngularJS世界時所稱的$digest() 。 摘要只是簡單的舊臟檢。 它適用於所有瀏覽器,並且完全可以預測。

為了比較髒檢查(AngularJS)和更改監聽器( KnockoutJSBackbone.js ):雖然臟檢查可能看起來很簡單,甚至效率低下(我將在後面解決),但事實證明,它始終在語義上是正確的,而更改監聽器有很多怪異的角落案例,並且需要諸如依賴關係跟踪之類的東西來使其更加符合語義。 KnockoutJS依賴關係跟踪是AngularJS沒有的一個聰明的特性。

更改監聽器的問題:

  • 語法很殘酷,因為瀏覽器本身不支持它。 是的,有代理,但在所有情況下,它們在語義上都不正確,當然,舊瀏覽器上也沒有代理。 底線是臟檢查允許你做POJO ,而KnockoutJS和Backbone.js強迫你從它們的類繼承,並通過訪問器訪問你的數據。
  • 改變合併。 假設你有一個項目數組。 假設你想添加項目到一個數組中,當你循環添加時,每次你添加的時候你正在觸發改變的事件,這正在渲染UI。 這對性能非常不利。 你想要的只是在最後更新UI。 變化事件太細緻。
  • 更改偵聽器立即在setter上觸發,這是一個問題,因為更改偵聽器可以進一步更改數據,這會觸發更多的更改事件。 這很糟糕,因為在您的堆棧中,您可能會同時發生多個更改事件。 假設你有兩個數組需要保持同步,無論出於何種原因。 您只能添加到其中一個,但每次添加時都會觸發一個更改事件,該事件現在對世界有不一致的看法。 這與線程鎖定非常類似,因為每個回調都是專門執行並完成的,所以JavaScript避免了這種情況。 變更事件破壞了這一點,因為變更者可能產生意義深遠的後果,而這些後果並非有意且非明顯,從而再次產生線程問題。 事實證明,你想要做的就是延遲監聽器的執行,並且保證一次只能運行一個監聽器,因此任何代碼都可以自由地改變數據,並且它知道其他代碼在運行時不會運行。

性能如何?

所以看起來我們很慢,因為臟檢是無效的。 這是我們需要查看實數而不是僅僅具有理論參數的地方,但首先讓我們定義一些約束條件。

人類是:

  • - 任何超過50毫秒的速度都不會被人看到,因此可以被視為“即時”。

  • 有限 - 您無法在單個頁面上向人顯示超過2000條信息。 除此之外的任何東西都是非常糟糕的用戶界面,人類無法處理這一點。

所以真正的問題是:在50毫秒內你可以在瀏覽器上做多少次比較? 這是一個很難回答的問題,因為許多因素起作用,但這裡是一個測試用例: http://jsperf.com/angularjs-digest/6 : http://jsperf.com/angularjs-digest/6創建10,000個觀察者。 在現代瀏覽器上,這需要不到6毫秒。 在Internet Explorer 8大約需要40毫秒。 正如你所看到的,這些日子即使在慢速瀏覽器上也不是問題。 有一個警告:比較需要很簡單,以適應時間限制...不幸的是,向AngularJS添加一個緩慢的比較太容易了,所以當你不知道你是什麼時,很容易構建緩慢的應用程序是做。 但是我們希望通過提供一個儀器模塊來給出答案,這個模塊會告訴你哪些是比較慢的。

事實證明,視頻遊戲和GPU使用臟檢查方法,特別是因為它是一致的。 只要它們超過顯示器刷新頻率(通常為50-60 Hz,或每16.6-20毫秒),任何超過這個性能的結果都是浪費,因此與提高FPS相比,您最好繪製更多的內容。


AngularJS通過三個強大的函數: $watch()$digest()$apply()來處理數據綁定機制。 大多數情況下,AngularJS會調用$ scope。$ watch()和$ scope。$ digest(),但在某些情況下,您可能需要手動調用這些函數以使用新值進行更新。

$ watch() : -

該函數用於觀察$ scope中變量的變化。 它接受三個參數:表達式,偵聽器和相等對象,其中偵聽器和相等對像是可選參數。

$ digest() -

此函數遍歷$ scope對像中的所有監視及其子$ scope對象
(如果有的話)。 當$ digest()遍歷手錶時,它會檢查表達式的值是否已經改變。 如果值已更改,AngularJS將使用新值和舊值調用監聽器。 每當AngularJS認為有必要的時候,都會調用$ digest()函數。 例如,點擊按鈕後,或在AJAX調用之後。 您可能會遇到一些AngularJS不會為您調用$ digest()函數的情況。 在這種情況下,你必須自己調用它。

$ apply() -

Angular會自動更新僅在AngularJS環境中的模型更改。 當你在Angular上下文以外的任何模型中進行更改(如瀏覽器DOM事件,setTimeout,XHR或第三方庫)時,則需要手動調用$ apply()來通知Angular。 當$ apply()函數調用完成時,AngularJS會在內部調用$ digest(),以便更新所有數據綁定。


AngularJs支持雙向數據綁定
意味著你可以訪問數據查看 - >控制器控制器 - >查看

例如。

1)

// If $scope have some value in Controller. 
$scope.name = "Peter";

// HTML
<div> {{ name }} </div>

O / P

Peter

你可以在ng-model綁定數據就像: -
2)

<input ng-model="name" />

<div> {{ name }} </div>

在上面的示例中,無論用戶輸入什麼內容,它都會在<div>標籤中顯示。

如果想要將輸入從html綁定到控制器: -
3)

<form name="myForm" ng-submit="registration()">
   <label> Name </lbel>
   <input ng-model="name" />
</form>

這裡如果你想在控制器中使用輸入name

$scope.name = {};

$scope.registration = function() {
   console.log("You will get the name here ", $scope.name);
};

ng-model綁定我們的視圖並將其呈現在{{ }}表達式中。
ng-model是在視圖中向用戶顯示並與用戶交互的數據。
所以在AngularJs中綁定數據很容易。





data-binding