iScroll contribution을 하다가 우연히 알게된 이슈가 있어 몇자 적어본다.

Chrome 49부터 EventListenerOptions 옵션을 지원한다.

기존 addEventListener의 3번째 파라미터로 캡쳐링/버블링 여부를 제어할 수 있는 부분이 EventListenerOptions이라는 객체형태의 추가 옵션을 받을수 있게 되었다.

jQuery를 쓸 이유가 하나씩 줄고 있다. 허허허

EventListenerOptions 사용 전
1
document.addEventListener('touchstart', handler, false);
EventListenerOptions 사용 후
1
2
3
4
5
document.addEventListener("touchstart", handler, {
capture: false,
once: false,
passive: false
});

현재 크롬에서 지원하는 EventListenerOptions 옵션은 다음과 같다.

  • capture: 이벤트 캡쳐링 적용 여부. 크롬 49부터 지원
  • once: 이벤트를 한번만 호출하고 해제되는 옵션. 크롬 55부터 지원
  • passive: 스크롤 성능 향상을 위한 옵션으로 true일 경우, 스크롤을 위해 블록되는 것을 방지한다. 이 경우, preventDefault를 사용할 수 없다. 크롬 51부터 지원

이 중, passive 속성은 성능향상을 위해, 브라우저의 기능을 프로그래밍으로 제어할수 있다.

오~~

passive 속성에 대한 링크
https://github.com/WICG/EventListenerOptions/blob/gh-pages/explainer.md

passive 속성이 false인 경우에 touchstart, touchmove와 같은 이벤트가 발생하면 preventDefault를 이용하여 실제 이벤트 자체를 막을 수 있기 때문에, 브라우저는 scroll을 계속 할지 안할지를 매번 감시해야만 한다.
하지만, passive 속성이 true일 경우에는 preventDefault를 이용하여 scroll 이벤트를 막지 않겠다고 브라우저에게 이야기하는 것과 같다. 따라서, 이 룰을 어기면 브라우저는 가차없이 다음과 같은 에러를 던진다.

다행이 passive 속성의 기본값은 false 이기 때문에, 기존 코드는 문제가 되지 않는다.

하지만…

Chrome 54+ 부터 EventListenerOptions의 passive 속성이 특별한 상황일 경우에는 기본값이 true로 설정된다.

document또는 body에 이벤트 리스너를 추가할때, touchstart, touchmove와 같이 스크롤이 블록되는 이벤트인 경우, passive의 기본 속성값은 true가 된다.

예를 들어 다음과 같은 코드는

1
2
3
4
5
6
7
document.addEventListener(
"touchmove",
function(e) {
e.preventDefault();
},
false
);

아래와 같은 무시무시한 에러가 발생한다.

혹시 이런 코드가 있으면 아래 같이 수정하시면 된다. 짜잔~!

1
2
3
4
5
6
7
8
9
10
11
12
document.addEventListener(
"touchmove",
function(e) {
e.preventDefault();
},
isPassive()
? {
capture: false,
passive: false
}
: false
);

여기 사용한 isPassive 함수는 passive 속성 여부를 확인하는 유틸 함수이고, 상세 구현은 다음과 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function isPassive() {
var supportsPassiveOption = false;
try {
addEventListener(
"test",
null,
Object.defineProperty({}, "passive", {
get: function() {
supportsPassiveOption = true;
}
})
);
} catch (e) {}
return supportsPassiveOption;
}

https://github.com/cubiq/iscroll/blob/master/demos/demoUtils.js#L2-L12

앞으로는 브라우저 내부를 점점 프로그램으로 제어할수 있는 API들이 쏟아질 것 같다.
웹개발을 하는 입장에서는 굉장히 좋은 방향성 같다.

Comment and share

서비스 개발시에, 테스트용으로 console.log를 찍으시나요?
실수로 또는 의도적으로 실서비스에서도 이런 로그가 찍힐 수도 있는데요.

테스트나 디버깅용으로 쓰는 이런 로그로 인해, 실서비스에서는 메모리 릭이 발생할 수 있습니다.

저도 우연히 개발 중에 알게되었답니다…

아래는 Observable 코드를 1000개 만드는 코드입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function makeObservable() {
return Rx.Observable.generate(
performance.now(),
x => x <= startTime + duration,
x => performance.now(),
x => (x - startTime) / duration,
Rx.Scheduler.animationFrame
);
}
function onMemory() {
for (let i = 0; i < 1000; i++) {
console.log("create observable", i, makeObservable());
}
}

demo link : http://jsbin.com/hohoxifipu/1

makeObservable()을 통해 instance를 반환하고, onMemory에서 1000번 반복합니다.
코드 상으로는 instance 변수가 사용되지 않기 때문에, 메모리가 정상적으로 릴리즈 되어야합니다.

하지만, 결과는 다음과 같이 메모리 릭이 발생합니다.

보시는 바와 같이 System Object가 굉장히 많이 증가하는 것을 보실 수 있습니다.

이유는 바로 console.log 때문입니다.

저도 이슈 문의했다가 알게된 사항입니다.

Chrome Devtool에서 정보를 표현하기 위해,
console.log를 사용할때 내부적으로 reference를 저장하게 된답니다.
그래서, 결국은 메모리가 해제되지 않고. 메모리 릭이 발생하게 됩니다.

실제로 console.log만 지워보았습니다.

짜잔~!

메모리가 정상적으로 릴리즈되는 것을 확인 할수 있습니다.

우리 모두 서비스에서 찍는 console.log…. 꼭! 지웁시다~ ^^

Comment and share

  • page 1 of 1
Author's picture

sculove

아내와 아들 그리고 딸밖에 모르는 남편


FrontEnd Developer


대한민국/서울