Dirty Checking in AngularJS
Wir werden vorstellen, wie man Dirty Checking in AngularJs durchführt.
Implementieren Sie Dirty Checking in AngularJS
AngularJS führt Dirty Checking an $scope
-Variablen für die bidirektionale Datenbindung durch. Ember.js
führt eine bidirektionale Datenbindung durch, indem es die Setter und Getter programmgesteuert repariert, aber in Angular erlaubt Dirty Checking, nach Variablen zu suchen, die verfügbar sein könnten oder nicht.
# AngularJs
$scope.$watch( wExp, listener, objEq );
Die Funktion $scope.$watch
wird verwendet, wenn wir sehen wollen, wann eine Variable geändert wird. Wir müssen drei Argumente auswählen, um diese Funktion anzuwenden: wExp
ist das, was wir beobachten möchten, listener
ist das, was wir tun möchten, wenn es aktualisiert wird, und ob wir eine Variable oder ein Objekt überprüfen möchten.
Wir können dies überspringen, während wir diese Funktion verwenden, wenn wir eine Variable überprüfen möchten. Lassen Sie uns ein Beispiel durchgehen, wie unten gezeigt.
#AngularJs
$scope.newName = 'Subhan';
$scope.$watch( function( ) {
return $scope.name;
}, function( newVal, oldVal ) {
console.log('$scope.newName is updated!');
} );
Angular speichert Ihre Watcher-Funktion im $scope
. Sie können dies überprüfen, indem Sie den $scope
in die Konsole einloggen.
In $watch
können wir anstelle einer Funktion auch einen String verwenden. Dies funktioniert auch genauso wie eine Funktion funktioniert.
Wenn wir eine Zeichenfolge in den Angular-Quellcode einfügen, lautet der Code:
#AngularJs
if (typeof wExp == 'string' && get.constant) {
var newFn = watcher.fn;
watcher.fn = function(newVal, oldVal, scope) {
newFn.call(this, newVal, oldVal, scope);
arrayRemove(array, watcher);
};
}
Durch Anwenden dieses Codes wird wExp
als Funktion gesetzt. Dadurch wird unser Listener mit der Variablen mit unserem gewählten Namen bezeichnet.
die $watchers
-Funktion in AngularJS
Alle ausgewählten Beobachter werden in der Variablen $$watchers
in $scope
gespeichert. Sie werden eine Reihe von Objekten finden, wenn wir $$watchers
überprüfen, wie unten gezeigt.
#AngularJs
$$watchers = [
{
eq: false,
fn: function( newVal, oldVal ) {},
last: 'Subhan',
exp: function(){},
get: function(){}
}
];
Die Funktion unRegWatch
wird von der Funktion $watch
zurückgegeben. Dies zeigt, dass wir, wenn wir das anfängliche $scope.$watch
einer Variablen zuweisen wollen, dies leicht erreichen können, indem wir ihr befehlen, die Überwachung zu beenden.
Bestätigen Sie einfach, dass wir das erste protokollierte $scope
geöffnet und angesehen haben, bevor Sie den Watcher trennen.
die Funktion $scope.$apply
in AngularJS
Wenn wir versuchen, controller/directive/etc.
auszuführen, führt Angular eine Funktion aus, in der wir $scope.$watch
aufrufen. Bevor die Funktion $digest
im rootScope
verwaltet wird, führt die Funktion $apply
eine von uns ausgewählte Funktion aus.
Die Angular-Funktion $apply
sieht wie folgt aus:
#AngularJs
$apply: function(expr) {
try {
beginPhase('$apply');
return this.$eval(expr);
} catch (e) {
$exceptionHandler(e);
} finally {
clearPhase();
try {
$rootScope.$digest();
} catch (e) {
$exceptionHandler(e);
throw e;
}
}
}
das expr
-Argument in AngularJS
Bei der Verwendung von $scope.$apply
, manchmal Angular oder wir würden eine Funktion durchlaufen, ist das Argument expr
diese Funktion.
Bei der Verwendung dieser Funktion werden wir nicht feststellen, dass es am häufigsten erforderlich ist, $apply
zu verwenden. Mal sehen, was passiert, wenn ng-keydown
$scope.$apply
verwendet.
Um die Anweisung zu übermitteln, verwenden wir den folgenden Code.
#AngularJs
var EventNgDirective = {};
forEach(
keydown keyup keypress submit focus blur copy cut paste'.split(' '),
function(newName) {
var newDirectiveName = newDirectiveNormalize('ng-' + newName);
EventNgDirective[newDirectiveName] = ['$parse', function($parse) {
return {
compile: function($element, attr) {
var fn = $parse(attr[directiveName]);
return function ngEventHandler(scope, element) {
element.on(lowercase(newName), function(event) {
scope.$apply(function() {
fn(scope, {$event:event});
});
});
};
}
};
}];
}
);
Dieser Code durchläuft die unterschiedlichen Arten von Ereignissen, die ausgelöst werden können, und generiert eine neue Direktive namens ng(eventName)
. Wenn es um die Kompilierfunktion der Direktive geht, wird ein Ereignishandler auf dem Element aufgezeichnet, wobei das Ereignis den Namen der Direktive enthält.
Wenn dieses Ereignis ausgelöst wird, wird $scope.$apply
von Angular ausgeführt und bietet ihm eine Funktion zum Ausführen an.
Auf diese Weise wird der Wert $scope
mit dem Elementwert aktualisiert, aber diese Bindung ist nur eine Einwegbindung. Der Grund dafür ist, dass wir ng-keydown
angewendet haben, was uns erlaubt, nur zu ändern, wenn dieses Ereignis angewendet wird.
Dadurch ergibt sich ein neuer Wert!
Unser Hauptziel ist es, eine bidirektionale Datenbindung zu erreichen. Um dieses Ziel zu erreichen, können wir das ng-Modell
verwenden.
Damit ng-model
funktioniert, verwendet es sowohl $scope.$watch
als auch $scope.$apply
.
Die Eingabe, die wir gegeben haben, wird dem Event-Handler von ng-model
hinzugefügt. An dieser Stelle wird $scope.$watch
aufgerufen!
$scope.$watch
wird im Controller der Direktive aufgerufen.
#AngularJs
$scope.$watch(function ngModelWatch() {
var value = ngModelGet($scope);
if (ctrl.$modelValue !== value) {
var formatters = ctrl.$formatters,
idx = formatters.length;
ctrl.$modelValue = value;
while(idx--) {
value = formatters[idx](value);
}
if (ctrl.$viewValue !== value) {
ctrl.$viewValue = value;
ctrl.$render();
}
}
return value;
});
Wenn beim Setzen von $scope.$watch
nur ein Argument verwendet wird, wird die von uns gewählte Funktion angewendet, unabhängig davon, ob sie aktualisiert wird oder nicht. Die im ng-model
vorhandene Funktion prüft, ob Model und View synchron sind.
Wenn es nicht synchron ist, gibt die Funktion dem Modell einen neuen Wert und aktualisiert ihn. Diese Funktion ermöglicht es uns, den neuen Wert zu erfahren, indem wir den neuen Wert zurückgeben, wenn wir diese Funktion in $digest
ausführen.
Warum der Zuhörer nicht gefeuert wird
Kehren wir zu dem Punkt im Beispiel zurück, an dem wir die Funktion $scope.$watch
in der ähnlichen Funktion, wie wir sie beschrieben haben, abgemeldet haben. Wir können jetzt den Grund dafür anerkennen, warum wir nicht über die Aktualisierung von $scope.name
benachrichtigt werden, selbst wenn wir es aktualisiert haben.
Wie wir wissen, wird $scope.$apply
von Angular auf jeder Direktiven-Controller-Funktion ausgeführt. Die Funktion $scope.$apply
führt $digest
nur unter einer Bedingung aus, wenn wir die Controller-Funktion der Direktive geschätzt haben.
Das bedeutet, dass wir die Funktion $scope.$watch
abgemeldet haben, bevor sie hätte ausgeführt werden können, sodass es eine minimale bis gar keine Chance gibt, dass sie aufgerufen wird.
die $digest
-Funktion in AngularJS
Sie können diese Funktion auf dem $rootScope
durch $scope.$apply
aufrufen. Wir können den Digest-Zyklus auf dem $rootScope
ausführen, und dann übergibt er die Bereiche und führt stattdessen den Digest-Zyklus aus.
Der Digest-Zyklus wird alle unsere wExp
-Funktionen in der $$watchers
-Variablen auslösen. Es wird sie mit dem letzten bekannten Wert vergleichen.
Wenn die Ergebnisse negativ sind, wird der Zuhörer gefeuert.
Wenn der Digest-Zyklus ausgeführt wird, wird er zweimal wiederholt. Einmal durchläuft es die Wächter und dann wieder Schleifen, bis der Kreislauf nicht mehr schmutzig
ist.
Wenn der wExp
und der letzte bekannte Wert nicht äquivalent sind, sagen wir, dass der Zyklus verschmutzt ist. Normalerweise wird dieser Zyklus einmal ausgeführt und gibt einen Fehler aus, wenn er mehr als zehnmal ausgeführt wird.
Angular kann $scope.$apply
auf alles ausführen, wo die Möglichkeit besteht, dass sich ein Modellwert ändert.
Sie müssen $scope.$apply();
ausführen wenn wir $scope
ausserhalb von Angular aktualisiert haben. Dadurch wird Angular benachrichtigt, dass der Bereich aktualisiert wurde.
Lassen Sie uns eine einfache Version von Dirty Checking entwerfen.
Alle Daten, die wir speichern möchten, befinden sich in der Funktion Scope
. Wir erweitern das Objekt auf eine Funktion, um $digest
und $watch
zu duplizieren.
Da wir keine Funktionen in Bezug auf Scope
bewerten müssen, verzichten wir auf $apply
. Wir werden direkt $digest
verwenden.
So wird unser Scope
aussehen:
#AngularJs
var Scope = function( ) {
this.$$watchers = [];
};
Scope.prototype.$watch = function( ) {
};
Scope.prototype.$digest = function( ) {
};
Es gibt zwei Parameter, wExp
und listener
, die $watch
übernehmen soll. Wir können die Werte in Scope
einstellen.
Beim Aufruf von $watch
senden wir diese in den zuvor in Scope
gespeicherten $$watcher
-Wert.
#AngularJs
var Scope = function( ) {
this.$$watchers = [];
};
Scope.prototype.$watch = function( wExp, listener ) {
this.$$watchers.push( {
wExp: wExp,
listener: listener || function() {}
} );
};
Scope.prototype.$digest = function( ) {
};
Hier werden wir feststellen, dass listener
auf eine leere Funktion gesetzt ist.
Sie können ganz einfach eine $watch
für alle Variablen registrieren, wenn kein listener
angeboten wird, indem Sie diesem Beispiel folgen.
Jetzt werden wir $digest
betreiben. Wir können prüfen, ob der Altwert und der Neuwert gleichwertig sind.
Wir können den Listener auch feuern, wenn er noch nicht gefeuert ist. Um dies zu erreichen, werden wir dann eine Schleife durchlaufen, bis sie äquivalent sind.
Die Variable dirtyChecking
hat uns geholfen, dieses Ziel zu erreichen, egal ob die Werte gleich sind.
#AngularJs
var Scope = function( ) {
this.$$watchers = [];
};
Scope.prototype.$watch = function( wExp, listener ) {
this.$$watchers.push( {
wExp: wExp,
listener: listener || function() {}
} );
};
Scope.prototype.$digest = function( ) {
var dirtyChecking;
do {
dirtyChecking = false;
for( var i = 0; i < this.$$watchers.length; i++ ) {
var newVal = this.$$watchers[i].wExp(),
oldVal = this.$$watchers[i].last;
if( oldVal !== newVal ) {
this.$$watchers[i].listener(newVal, oldVal);
dirtyChecking = true;
this.$$watchers[i].last = newVal;
}
}
} while(dirtyChecking);
};
Jetzt werden wir ein neues Beispiel für unseren Bereich entwerfen. Wir weisen dies $scope
zu; dann registrieren wir eine Überwachungsfunktion.
Dann werden wir es aktualisieren und verdauen
.
#AngularJs
var Scope = function( ) {
this.$$watchers = [];
};
Scope.prototype.$watch = function( wExp, listener ) {
this.$$watchers.push( {
wExp: wExp,
listener: listener || function() {}
} );
};
Scope.prototype.$digest = function( ) {
var dirtyChecking;
do {
dirtyChecking = false;
for( var i = 0; i < this.$$watchers.length; i++ ) {
var newVal = this.$$watchers[i].wExp(),
oldVal = this.$$watchers[i].last;
if( oldVal !== newVal ) {
this.$$watchers[i].listener(newVal, oldVal);
dirtyChecking = true;
this.$$watchers[i].last = newVal;
}
}
} while(dirtyChecking);
};
var $scope = new Scope();
$scope.newName = 'Subhan';
$scope.$watch(function(){
return $scope.newName;
}, function( newVal, oldVal ) {
console.log(newVal, oldVal);
} );
$scope.$digest();
Wir haben Dirty Checking erfolgreich implementiert. Sie werden seine Protokolle beobachten, wenn wir die Konsole beobachten.
#AngularJs
Subhan undefined
Früher war $scope.newName
nicht definiert. Wir haben es für Subhan
behoben.
Wir werden die $digest
-Funktion mit einem keyup
-Ereignis an einer Eingabe verbinden. Auf diese Weise müssen wir es nicht selbst benennen.
Dies bedeutet, dass wir auch eine bidirektionale Datenbindung erreichen können.
#AngularJs
var Scope = function( ) {
this.$$watchers = [];
};
Scope.prototype.$watch = function( wExp, listener ) {
this.$$watchers.push( {
wExp: wExp,
listener: listener || function() {}
} );
};
Scope.prototype.$digest = function( ) {
var dirty;
do {
dirtyChecking = false;
for( var i = 0; i < this.$$watchers.length; i++ ) {
var newVal = this.$$watchers[i].wExp(),
oldVal = this.$$watchers[i].last;
if( oldVal !== newVal ) {
this.$$watchers[i].listener(newVal, oldVal);
dirtyChecking = true;
this.$$watchers[i].last = newVal;
}
}
} while(dirtyChecking);
};
var $scope = new Scope();
$scope.newName = 'Subhan';
var element = document.querySelectorAll('input');
element[0].onkeyup = function() {
$scope.newName = element[0].value;
$scope.$digest();
};
$scope.$watch(function(){
return $scope.newName;
}, function( newVal, oldVal ) {
console.log('Value updated In Input - The new value is ' + newVal);
element[0].value = $scope.newName;
} );
var updateScopeValue = function updateScopeValue( ) {
$scope.name = 'Butt';
$scope.$digest();
};
Mit dieser Technik können wir den Wert der Eingabe leicht aktualisieren, wie in $scope.newName
zu sehen ist. Sie können auch updateScopeValue
aufrufen, und der Wert der Eingabe zeigt dies an.
Rana is a computer science graduate passionate about helping people to build and diagnose scalable web application problems and problems developers face across the full-stack.
LinkedIn