AngularJS의 더러운 검사
AngularJs에서 더티 검사를 수행하는 방법을 소개합니다.
AngularJS에서 Dirty Checking 구현
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는 $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)
이라는 새 지시문을 생성합니다. 지시어의 컴파일 기능과 관련된 경우 이벤트 핸들러가 요소에 기록되며 이벤트에는 지시문 이름이 포함됩니다.
이 이벤트가 발생하면 $scope.$apply
가 Angular에 의해 실행되어 실행할 기능을 제공합니다.
이것이 $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
와 마지막으로 알려진 값이 동일하지 않은 경우 주기가 더티라고 합니다. 일반적으로 이 주기는 한 번 실행되고 10번 이상 실행되면 오류가 발생합니다.
Angular는 모델 값이 변경될 가능성이 있는 모든 항목에서 $scope.$apply
를 실행할 수 있습니다.
$scope.$apply();
를 실행해야 합니다. Angular 외부에서 $scope
를 업데이트했을 때. 이렇게 하면 범위가 업데이트되었음을 Angular에 알립니다.
기본 버전의 더티 검사를 설계해 보겠습니다.
저장하려는 모든 데이터는 범위
기능에 있습니다. $digest
및 $watch
를 복제하기 위해 함수의 개체를 확장합니다.
Scope
와 관련된 기능을 평가할 필요가 없기 때문에 $apply
를 사용하지 않습니다. $digest
를 직접 사용합니다.
스코프
는 다음과 같습니다.
#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
에 할당합니다. 그런 다음 시계 기능을 등록합니다.
그런 다음 업데이트하고 다이제스트합니다.
#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
를 호출할 수도 있으며 입력 값에 표시됩니다.
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