AngularJS 中的脏数据检查
我们将介绍如何在 AngularJs 中进行脏数据检查。
在 AngularJS 中实现脏数据检查
AngularJS 对双向数据绑定的 $scope
变量执行脏数据检查。Ember.js
通过以编程方式修复 setter 和 getter 来执行双向数据绑定,但在 Angular 中,脏数据检查允许它查找可能可用或不可用的变量。
# AngularJs
$scope.$watch( wExp, listener, objEq );
$scope.$watch
函数用于我们想要查看变量何时被更改。我们必须选择三个参数来应用这个函数:wExp
是我们想要观看的内容,listener
是我们希望它在更新时执行的操作,以及我们想要检查变量还是对象。
当我们想要检查一个变量时,我们可以在使用这个函数时跳过这个。让我们看一个例子,如下所示。
#AngularJs
$scope.newName = 'Subhan';
$scope.$watch( function( ) {
return $scope.name;
}, function( newVal, oldVal ) {
console.log('$scope.newName is updated!');
} );
Angular 会将你的 watcher 函数保存在 $scope
中。你可以通过将 $scope
记录到控制台来检查这一点。
在 $watch
中,我们还可以使用字符串代替函数。这也将与函数的工作方式相同。
当我们在 Angular 源代码中添加一个字符串时,代码将是:
#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);
};
}
通过应用此代码,wExp
将被设置为一个函数。这将使用具有我们选择的名称的变量指定我们的侦听器。
AngularJS 中的 $watchers
函数
所有选定的观察者都将存储在 $scope
中的 $$watchers
变量中。当我们检查 $$watchers
时,你会发现一个对象数组,如下所示。
#AngularJs
$$watchers = [
{
eq: false,
fn: function( newVal, oldVal ) {},
last: 'Subhan',
exp: function(){},
get: function(){}
}
];
unRegWatch
函数由 $watch
函数返回。这表明如果我们想将初始的 $scope.$watch
分配给一个变量,我们可以通过命令它停止观察来轻松实现这一点。
只需确认我们已经打开并查看了在断开观察程序之前记录的第一个 $scope
。
AngularJS 中的 $scope.$apply
函数
当我们尝试运行 controller/directive/etc.
时,Angular 会运行一个函数,我们在其中调用 $scope.$watch
。在管理 rootScope
中的 $digest
函数之前,$apply
函数将运行我们选择的函数。
Angular $apply
函数如下:
#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;
}
}
}
AngularJS 中的 expr
参数
当使用 $scope.$apply
时,有时 Angular 或者我们会通过一个函数,expr
参数就是那个函数。
在使用此功能时,我们不会发现需要最频繁地使用 $apply
。让我们看看当 ng-keydown
使用 $scope.$apply
时会发生什么。
要提交指令,我们使用以下代码。
#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});
});
});
};
}
};
}];
}
);
此代码将遍历可以触发的不同类型的事件,生成一个名为 ng(eventName)
的新指令。当涉及指令的编译功能时,事件处理程序被记录在元素上,其中事件包含指令的名称。
当这个事件被触发时,Angular 将运行 $scope.$apply
,为其提供一个运行函数。
这就是 $scope
值将如何使用元素值更新的方式,但此绑定只是单向绑定。这样做的原因是我们应用了 ng-keydown
,它允许我们仅在应用此事件时进行更改。
结果,得到了一个新的值!
我们的主要目标是实现双向数据绑定。为了实现这个目标,我们可以使用 ng-model
。
为了使 ng-model
发挥作用,它同时使用 $scope.$watch
和 $scope.$apply
。
我们给出的输入将通过 ng-model
加入事件处理程序。此时,调用了 $scope.$watch
!
$scope.$watch
在指令的控制器中被调用。
#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;
});
当设置 $scope.$watch
时仅使用一个参数时,我们选择的函数将被应用,即使它是否已更新。ng-model
中的函数检查模型和视图是否同步。
如果不同步,该函数会给模型一个新值并更新它。当我们在 $digest
中运行这个函数时,这个函数允许我们通过返回新值来知道新值是什么。
为什么监听器没有被触发
让我们回到示例中的点,我们在与我们描述的类似的函数中取消注册了 $scope.$watch
函数。我们现在可以承认没有收到有关更新 $scope.name
的通知的原因,即使我们已经更新了它。
正如我们所知,$scope.$apply
由 Angular 在每个指令控制器函数上运行。当我们估计了指令的控制器功能时,$scope.$apply
函数将仅在一个条件下运行 $digest
。
这意味着我们在 $scope.$watch
函数能够执行之前取消了它的注册,因此它被调用的可能性很小甚至没有。
AngularJS 中的 $digest
函数
你可以通过 $scope.$apply
在 $rootScope
上调用此函数。我们可以在 $rootScope
上运行摘要循环,然后它将越过范围并运行摘要循环。
摘要循环将触发我们在 $$watchers
变量中的所有 wExp
函数。它将根据最后一个已知值检查它们。
如果结果是否定的,监听器将被触发。
在摘要循环中,当它运行时,它会循环两次。一次,它将在观察者之间循环,然后再次循环,直到循环不再是脏
。
当 wExp
和最后一个已知值不相等时,我们说循环是脏的。通常这个循环会运行一次,如果运行十次以上就会报错。
Angular 可以在任何可能改变模型值的东西上运行 $scope.$apply
。
你必须运行 $scope.$apply();
当我们在 Angular 之外更新了 $scope
。这将通知 Angular 范围已更新。
让我们设计一个基本版本的脏数据检查。
我们希望保存的所有数据都将在 Scope
函数中。我们将在函数上扩展对象以复制 $digest
和 $watch
。
因为我们不需要评估与 Scope
有关的函数,所以我们不会使用 $apply
。我们将直接使用 $digest
。
这就是我们的 Scope
的样子:
#AngularJs
var Scope = function( ) {
this.$$watchers = [];
};
Scope.prototype.$watch = function( ) {
};
Scope.prototype.$digest = function( ) {
};
$watch
应该采用两个参数,wExp
和 listener
。我们可以在 Scope
中设置值。
当 $watch
被调用时,我们将它们发送到先前存储在 Scope
中的 $$watcher
值中。
#AngularJs
var Scope = function( ) {
this.$$watchers = [];
};
Scope.prototype.$watch = function( wExp, listener ) {
this.$$watchers.push( {
wExp: wExp,
listener: listener || function() {}
} );
};
Scope.prototype.$digest = function( ) {
};
在这里,我们会注意到 listener
设置为一个空函数。
当没有提供 listener
时,你可以按照此示例轻松为所有变量注册 $watch
。
现在我们将运行 $digest
。我们可以检查旧值和新值是否相等。
如果监听器尚未被触发,我们也可以触发它。为此,我们将循环直到它们相等。
dirtyChecking
变量帮助我们实现了这个目标,无论值是否相等。
#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);
};
现在,我们将设计一个新的范围示例。我们将把它分配给 $scope
;然后,我们将注册一个 watch 函数。
然后我们将更新它并消化
它。
#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();
我们已经成功实现了脏数据检查。当我们观看控制台时,你将观察其日志。
#AngularJs
Subhan undefined
以前,未定义 $scope.newName
。我们已经为 Subhan
修复了它。
我们将把 $digest
函数连接到输入上的 keyup
事件。通过这样做,我们不必自己指定它。
这意味着我们也可以实现双向数据绑定。
#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();
};
使用这种技术,我们可以轻松地更新输入的值,如 $scope.newName
中所示。你也可以调用 updateScopeValue
,输入的值会显示出来。