많은 회사에서 2013년부터 AngularJs를 도입해서 현재도 사용하고 있는 곳이 많이 있습니다.
하지만 AngularJs 최적화가 되어 있는 경우가 드물어 성능에 문제가 있어도 해결 방법을 찾기 어렵습니다.
오늘은 AngularJs의 성능 개선 방법에 대해 알아보겠습니다.
1. AngularJs가 모델 변경 사항을 추적하는 상황
- DOM 이벤트(사용자가 input 필드의 값을 변경하거나 버튼을 클릭을 클릭해서 자바스크립트 함수를 호출하는 경우)
- XHR 응답으로 인한 콜백
- 브라우저의 주소 변경
- 타이머(setTimeout, setInterval)로 인한 콜백
* 실제로 어떤 이벤트도 발생하지 않으면 모델의 변경 사항을 주시하지 않습니다.
* AngularJs는 $scope.$apply() 메서드를 호출해서 모델 변경 사항의 추적을 시작합니다.
* 모델 변경 사항을 감지하는 과정을 $digest (다이제스트) 루프라고 합니다.
2. $digest 루프 존재 이유
- 모델의 어느 부분이 변경됐는지 판단하고 그 결과로 어떤 DOM 프로퍼티가 갱신되어야 하는지를 결정합니다.
- 성능 저하를 일으키고 불필요한 다시 그리기 동작을 제거해서 UI가 깜빡이는 현상을 해결합니다.
- AngularJs는 모델이 안정화(모든 모델 값의 계산이 완료되고 UI를 렌더링할 준비도 끝난 상태)되는 가장
마지막 시점까지 DOM을 다시 그리는 동작을 지연시켜서 UI가 깜빡이는 현상을 해결합니다.
* 웹 브라우저는 싱글 UI 쓰레드로 동작합니다.
(DOM 요소를 렌더링, DOM 이벤트를 처리, 자바스크립트 코드를 실행하는 쓰레드는 오직 한 개!!)
3. dirty checking
$scope.$watch(watchExpression, modelChangeCallback)
$scope.$watch("vm.test", function (newVal, oldVal) { } )
- 스코프에 새로운 $watch가 추가되면 AngularJs는 watchExpression을 평가하고 내부적으로 평가 결과를 저장합니다.
- $digest 루프로 들어간 다음에 watchExpression은 다시 한번 실행되고, 새로운 값가 저장해둔 값을 비교합니다.
- 새로운 값이 이전 값과 다르면 새로운 값을 나중에 비교하기 위해 저장하고 modelChangeCallback이 실행됩니다.
- AngularJs는 변경 사항을 감지하는 watch가 하나도 없으면 모델이 안정적이라고 판단합니다.
- 만약 1개라도 변경 사항을 감지한 watch가 있으면 전체 $digest 루프의 상태는 dirty로 변경하고 AngularJs는 루프를 한번 더
돌립니다.
- 더 이상 변경 사항이 발견되지 않을 때까지 $digest 루프를 계속 돌리면서 전체 스코프의 모든 watch를 재평가합니다.
- $digest 루프를 여러 번 돌려야 하는 이유는 watch 콜백에 따른 side effect 가 있기 때문입니다.
예) $scope.$watch("startDate, function (newVal) {
if (newVal <=$scope.startDate) {
$scope.endDate = oneDayAhead($scope.startDate);
}
})
- $digest 루프는 최소한 한번, 보통 2번 실행됩니다.
- AngularJs는 $digest 루프를 10번 수행해도 모델이 불안정하면 루프를 빠져나온다. 이렇게 빠져나올 때 오류
내용으로 마지막 5번의 불안정한 watch를 보여줍니다.
- $digest 루프가 취소되고 나면 자바스크립트 쓰레드는 렌더링 컨텍스트로 넘어갑니다.
- $digest 루프는 $rootScope부터 시작하여 자식 스코프를 따라 내려갑니다.
[정리]
<input ng-model="name"> {{name}} 일때 사용자가 input 필드에 타이핑을 하기 시작하면 발생되는 과정
1) 새로운 DOM input 이벤트가 보내진다. 브라우저는 자바스크립트 실행 컨텍스트로 들어갑니다.
2) input 디렉티브로 등록된 DOM 이벤트 핸들러가 실행됩니다.
3) 핸들러가 모델 값을 갱신하고 스코프 인스턴스의 $scope.$apply 메소드를 호출합니다.
4) 자바스크립트 실행 컨텍스트는 AngularJs 영역으로 들어오고 $digest 루프가 시작됩니다.
5) 모델 변경 사항을 발견되어 $watch 콜백이 수행됩니다.
6) 인터폴레이션 표현식이 있는 DOM 요소의 text 프로퍼티를 갱신합니다.
7) 두번째 $digest 루프에서 모든 watch를 다시 평가하고 변경 사항이 없음을 확인한 후 AngularJs는 모델이 안정적이라고 판단합니다.
8) 자바스크립트 실행 컨텍스트는 AngularJs가 아닌 다른 자바스크립트 코드를 이어서 처리한다. 대부분은 이러한 코드가 없기
때문에 브라우저는 자바스크립트 실행 컨텍스트를 그냥 빠져나옵니다.
9) UI쓰레드는 렌더링 컨텍스트로 넘어가고 브라우저는 text 프로퍼티가 변경된 DOM 노드를 다시 그립니다.
렌더링이 끝나면 브라우저는 이벤트 루프의 대기 상태로 다시 돌아옵니다.
* AngularJs 개발자 미스코 헤베리는 한 화면에 watch가 2000개가 넘으면 안 된다고 생각합니다.
4. CPU 사용률 최적화
* $digest 사이클이 50ms 안에 수행되어야 합니다.
- 각 watch를 빠르게 만들기
- 각 $diggest 사이클의 일부분으로 평가되는 watch의 수를 제한하기
* watch를 가볍고 빠르게 만들기
$scope.$watch(watchExpression, modelChangeCallback)
- watchExpression이 콜백에 비해 훨씬 자주 실행되기 때문에 신경써야 합니다.
- {{test()}} 과 같은 표현식에서 함수를 호출하지 않아야 한다. 특히 이 함수안에 콘솔이 있으면 급격히 성능 저하가 발생합니다.
- {{ myMode l| myComplexFilter }} 와 같이 필터를 사용하면 성능이 하락됩니다.
* 평가될 watch의 수 제한
- 필요없는 watch 제거 (양방향 데이터 바인딩 남용)
- ng-show 대신 ng-switch 디렉티브를 사용
- 영향을 받는 스코프를 알고 있을 때는 $scope.$apply()보다 $scope.$digest()를 사용합니다.
- 사용되지 않는 watch제거
예) var watchUnregisterFn = $scope.$watch("name", funcition (newVal, oldVal) {
});
// watch가 더 이상 필요 없을 때
watchUnregisterFn();
- 네트워크 호출 수를 줄입니다. (한번의 호출과 하나의 응답으로 처리)
- 타이머 사용시 다음과 같이 사용합니다.
$timeout('사용자 지정 함수', '시간', false); // 매개변수 3번째에 false를 넘기면 $digest 루프 시작을 막는다.
- 마우스 이동과 관련된 이벤트 사용을 줄여야 합니다.
- watch의 대상이 되는 표현식의 크기를 고려합니다.
* deep-watching을 피하기
- $watch 함수의 3번째 매개변수로 true를 넣으면 객체 비교를 한다. 이 경우 속도가 매우 하락됩니다.
* ng-repeat 사용 한계
- ng-repeat는 500개의 행이 넘는 컬렉션을 보여줄 때 사용하지 않아야 합니다.
- 컬렉션의 데이터가 ng-repeat로 넘어가기 전에 필터를 적용하고 데이터의 수를 적게 만들어야 합니다.
- 또는 페이징을 사용해서 합니다.
- 정 안될 경우 직접 ng-repeat와 같은 디렉티브를 개발해야 합니다.
'개발' 카테고리의 다른 글
리눅스 mysql(5.5.9) 설치 방법 (0) | 2020.08.08 |
---|---|
AngularJs 보안 가이드 (0) | 2020.07.28 |
Redis 튜닝하기 (0) | 2020.07.27 |
프리랜서 계약서 양식 (0) | 2020.07.27 |
IntelliJ Memory Option 최적화 (0) | 2020.07.26 |