Dirty Checking in AngularJS

Rana Hasnain Khan 31 Mai 2022
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 Hasnain Khan avatar Rana Hasnain Khan avatar

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