AngularJS的数据双向绑定是怎么实现的

2025-03-09 11:45:27
推荐回答(1个)
回答1:

AngularJS在$scope变量中使用脏值检查来实现了数据双向绑定。和Ember.js数据双向绑定中动态设施setter和getter不同,脏治检查允许AngularJS监视那些存在或者不存在的变量。

$scope.$watch
$scope.$watch( watchExp, listener, objectEquality );

为了监视一个变量的变化,你可以使用$scope.$watch函数。这个函数有三个参数,它指明了”要观察什么”(watchExp),”在变化时要发生什么”(listener),以及你要监视的是一个变量还是一个对象。当我们在检查一个参数时,我们可以忽略第三个参数。例如下面的例子:

$scope.name = 'Ryan';

$scope.$watch( function( ) {
return $scope.name;
}, function( newValue, oldValue ) {
console.log('$scope.name was updated!');
} );
1
2
3
4
5
6
7
8
代码laycode - v1.1
AngularJS将会在$scope中注册你的监视函数。你可以在控制台中输出$scope来查看$scope中的注册项目。

你可以在控制台中看到$scope.name已经发生了变化 – 这是因为$scope.name之前的值似乎undefined而现在我们将它赋值为Ryan!

对于$wach的第一个参数,你也可以使用一个字符串。这和提供一个函数完全一样。在AngularJS的源代码中可以看到,如果你使用了一个字符串,将会运行下面的代码:

if (typeof watchExp == 'string' && get.constant) {
var originalFn = watcher.fn;
watcher.fn = function(newVal, oldVal, scope) {
originalFn.call(this, newVal, oldVal, scope);
arrayRemove(array, watcher);
};
}
1
2
3
4
5
6
7
8
代码laycode - v1.1
这将会把我们的watchExp设置为一个函数,它也自动返回作用域中我们已经制定了名字的变量。

$$watchers
$scope中的$$watchers变量保存着我们定义的所有的监视器。如果你在控制台中查看$$watchers,你会发现它是一个对象数组。

$$watchers = [
{
eq: false, // 表明我们是否需要检查对象级别的相等
fn: function( newValue, oldValue ) {}, // 这是我们提供的监听器函数
last: 'Ryan', // 变量的最新值
exp: function(){}, // 我们提供的watchExp函数
get: function(){} // Angular's编译后的watchExp函数
}
];
1
2
3
4
5
6
7
8
9
10
代码laycode - v1.1
$watch函数将会返回一个deregisterWatch函数。这意味着如果我们使用$scope.$watch对一个变量进行监视,我们也可以在以后通过调用某个函数来停止监视。

$scope.$apply
当一个控制器/指令/等等东西在AngularJS中运行时,AngularJS内部会运行一个叫做$scope.$apply的函数。这个$apply函数会接收一个函数作为参数并运行它,在这之后才会在rootScope上运行$digest函数。

AngularJS的$apply函数代码如下所示:

$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;
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
代码laycode - v1.1
上面代码中的expr参数就是你在调用$scope.$apply()时传递的参数 – 但是大多数时候你可能都不会去使用$apply这个函数,要用的时候记得给它传递一个参数。

下面我们来看看ng-keydown是怎么来使用$scope.$apply的。为了注册这个指令,AngularJS会使用下面的代码。

var ngEventDirectives = {};
forEach(
'click dblclick mousedow www.hbbz08.com n mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress submit focus blur copy cut paste'.split(' '),
function(name) {
var directiveName = directiveNormalize('ng-' + name);
ngEventDirectives[directiveName] = ['$parse', function($parse) {
return {
compile: function($element, attr) {
var fn = $parse(attr[directiveName]);
return function ngEventHandler(scope, element) {
element.on(lowercase(name), function(event) {
scope.$apply(function() {
fn(scope, {$event:event});
});
});
};
}
};
}];
}
);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
代码laycode - v1.1
上面的代码做的事情是循环了不同的类型的事件,这些事件在之后可能会被触发并创建一个叫做ng-[某个事件]的新指令。在指令的compile函数中,它在元素上注册了一个事件处理器,它和指令的名字一一对应。当事件被出发时,AngularJS就会运行scope.$apply函数,并让它运行一个函数。

只是单向数据绑定吗?
上面所说的ng-keydown只能够改变和元素值相关联的$scope中的值 – 这只是单项数据绑定。这也是这个指令叫做ng-keydown的原因,只有在keydown事件被触发时,能够给与我们一个新值。

但是我们想要的是双向数据绑定!
我们现在来看一看ng-model。当你在使用ng-model时,你可以使用双向数据绑定 – 这正是我们想要的。AngularJS使用$scope.$watch(视图到模型)以及$scope.$apply(模型到视图)来实现这个功能。

ng-model会把事件处理指令(例如keydown)绑定到我们运用的输入元素上 – 这就是$scope.$apply被调用的地方!而$scope.$watch是在指令的控制器中被调用的。你可以在下面代码中看到这一点:

$scope.$watch(function ngModelWatch() {
var value = ngModelGet($scope);

//如果作用域模型值和ngModel值没有同步
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;
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
代码laycode - v1.1
如果你在调用$scope.$watch时只为它传递了一个参数,无论作用域中的什么东西发生了变化,这个函数都会被调用。在ng-model中,这个函数被用来检查模型和视图有没有同步,如果没有同步,它将会使用新值来更新模型数据。这个函数会返回一个新值,当它在$digest函数中运行时,我们就会知道这个值是什么!