feta(scope): watch object refference or equality

Breaks: Must set $watch equality to true for the old behavior
This commit is contained in:
Misko Hevery 2012-02-23 15:01:08 -08:00
parent ffa8441886
commit d6e3e1baab
3 changed files with 46 additions and 26 deletions

View file

@ -592,7 +592,7 @@ function classDirective(name, selector) {
if (isObject(newVal) && !isArray(newVal))
newVal = map(newVal, function(v, k) { if (v) return k });
if (newVal) element.addClass(isArray(newVal) ? newVal.join(' ') : newVal); }
});
}, true);
});
}
@ -837,7 +837,7 @@ var ngStyleDirective = valueFn(function(scope, element, attr) {
forEach(oldStyles, function(val, style) { element.css(style, '');});
}
if (newStyles) element.css(newStyles);
});
}, true);
});

View file

@ -35,6 +35,15 @@
* event processing life-cycle. See {@link guide/dev_guide.scopes developer guide on scopes}.
*/
function $RootScopeProvider(){
var TTL = 10;
this.ttl = function(value) {
if (arguments.length) {
TTL = value;
}
return TTL;
}
this.$get = ['$injector', '$exceptionHandler', '$parse',
function( $injector, $exceptionHandler, $parse) {
@ -248,9 +257,11 @@ function $RootScopeProvider(){
*
* - `string`: Evaluated as {@link guide/dev_guide.expressions expression}
* - `function(newValue, oldValue, scope)`: called with current and previous values as parameters.
*
* @param {boolean=} objectEquality Compare object for equality rather then for refference.
* @returns {function()} Returns a deregistration function for this listener.
*/
$watch: function(watchExp, listener) {
$watch: function(watchExp, listener, objectEquality) {
var scope = this,
get = compileToFn(watchExp, 'watch'),
array = scope.$$watchers,
@ -258,7 +269,8 @@ function $RootScopeProvider(){
fn: listener,
last: initWatchVal,
get: get,
exp: watchExp
exp: watchExp,
eq: !!objectEquality
};
// in the case user pass string, we need to compile it, do we really need this ?
@ -332,7 +344,7 @@ function $RootScopeProvider(){
watchers,
asyncQueue,
length,
dirty, ttl = 100,
dirty, ttl = TTL,
next, current, target = this,
watchLog = [],
logIdx, logMsg;
@ -359,9 +371,13 @@ function $RootScopeProvider(){
watch = watchers[length];
// Most common watches are on primitives, in which case we can short
// circuit it with === operator, only when === fails do we use .equals
if ((value = watch.get(current)) !== (last = watch.last) && !equals(value, last)) {
if ((value = watch.get(current)) !== (last = watch.last) &&
!(watch.eq
? equals(value, last)
: (typeof value == 'number' && typeof last == 'number'
&& isNaN(value) && isNaN(last)))) {
dirty = true;
watch.last = copy(value);
watch.last = watch.eq ? copy(value) : value;
watch.fn(value, ((last === initWatchVal) ? value : last), current);
if (ttl < 5) {
logIdx = 4 - ttl;
@ -390,7 +406,7 @@ function $RootScopeProvider(){
} while ((current = next));
if(dirty && !(ttl--)) {
throw Error('100 $digest() iterations reached. Aborting!\n' +
throw Error(TTL + ' $digest() iterations reached. Aborting!\n' +
'Watchers fired in the last 5 iterations: ' + toJson(watchLog));
}
} while (dirty || asyncQueue.length);

View file

@ -189,22 +189,26 @@ describe('Scope', function() {
}));
it('should prevent infinite recursion and print watcher expression',inject(
function($rootScope) {
$rootScope.$watch('a', function() {$rootScope.b++;});
$rootScope.$watch('b', function() {$rootScope.a++;});
$rootScope.a = $rootScope.b = 0;
it('should prevent infinite recursion and print watcher expression',function() {
module(function($rootScopeProvider) {
$rootScopeProvider.ttl(100);
});
inject(function($rootScope) {
$rootScope.$watch('a', function() {$rootScope.b++;});
$rootScope.$watch('b', function() {$rootScope.a++;});
$rootScope.a = $rootScope.b = 0;
expect(function() {
$rootScope.$digest();
}).toThrow('100 $digest() iterations reached. Aborting!\n'+
'Watchers fired in the last 5 iterations: ' +
'[["a; newVal: 96; oldVal: 95","b; newVal: 97; oldVal: 96"],' +
'["a; newVal: 97; oldVal: 96","b; newVal: 98; oldVal: 97"],' +
'["a; newVal: 98; oldVal: 97","b; newVal: 99; oldVal: 98"],' +
'["a; newVal: 99; oldVal: 98","b; newVal: 100; oldVal: 99"],' +
'["a; newVal: 100; oldVal: 99","b; newVal: 101; oldVal: 100"]]');
}));
expect(function() {
$rootScope.$digest();
}).toThrow('100 $digest() iterations reached. Aborting!\n'+
'Watchers fired in the last 5 iterations: ' +
'[["a; newVal: 96; oldVal: 95","b; newVal: 97; oldVal: 96"],' +
'["a; newVal: 97; oldVal: 96","b; newVal: 98; oldVal: 97"],' +
'["a; newVal: 98; oldVal: 97","b; newVal: 99; oldVal: 98"],' +
'["a; newVal: 99; oldVal: 98","b; newVal: 100; oldVal: 99"],' +
'["a; newVal: 100; oldVal: 99","b; newVal: 101; oldVal: 100"]]');
});
});
it('should prevent infinite recursion and print print watcher function name or body',
@ -241,11 +245,11 @@ describe('Scope', function() {
$rootScope.$watch('a', function(value) {
log +='.';
expect(value).toBe($rootScope.a);
});
}, true);
$rootScope.$watch('b', function(value) {
log +='!';
expect(value).toBe($rootScope.b);
});
}, true);
$rootScope.$digest();
log = '';
@ -331,7 +335,7 @@ describe('Scope', function() {
$rootScope.$watch(function() { return undefined;}, logger);
$rootScope.$watch(function() { return '';}, logger);
$rootScope.$watch(function() { return false;}, logger);
$rootScope.$watch(function() { return {};}, logger);
$rootScope.$watch(function() { return {};}, logger, true);
$rootScope.$watch(function() { return 23;}, logger);
$rootScope.$digest();