JeanJaures326

jaures.egloos.com

포토로그 마이가든



브라우저에서의 타이머 정확성 Development

브라우저에서 타이머(setTimeout 또는 setInterval)를 사용해 함수를 호출하도록 할때, 다음과 같이 호출될 함수와 호출될 시간을 밀리세컨드로 지정한다.

setTimeout(function() { ... }, nTimeMS);
setInterval(function() { ... }, nTimeMS);

그런데, 보통은 타이머에 지정한 시간대로 호출되겠지라는 생각을 갖는 것이 대다수일 텐데, 타이머를 0으로 주면 그 즉시 실행이 되지는 않는다. 브라우저에서의 작업은 single thread로 처리되기 때문에, 타이머를 통해 등록된 함수는 당장 실행되지 않는다.
먼저 작업 큐에 추가되고, 브라우저에서 별도의 다른 작업이 실행중이지 않으면 큐에 등록된 함수가 실행되게 된다.


이때 타이머 호출을 위해 지정한 시간은 과연 얼마나 정확한가? 라는 의문이 생기게 된다. 타이머 호출의 정확성에 대해 좋은 글이 있어 간략히 아래와 같이 정리해 봤다. (정확히는 크롬 브라우저에서의 타이머 처리에 관한 글이다.)


윈도우 어플리케이션들은 아키텍처상 Event Loops의 상위에 일반적으로 위치해, 어떤 작업이 나중에 실행되도록 스케줄링하기 위해 큐에 넣고 작업이 실행되는 시점에 프로세스가 깨어날 수 있도록 하게 된다.

이와 같은 처리를 위해 윈도우에선 기본적으로 WinAPI의 WaitForMultipleObjects()를 사용해 처리한다. 이 API는 UI 이벤트, 파일 이벤트 그리고 커스텀 이벤트를 위해 사용되는데, 이러한 이벤트들에 대해서 윈도우는 실행 주기의 기본값으로 15ms를 갖는다. 즉, 1ms로 timeout을 설정 하더라도 15ms의 주기를 갖기 때문에 15ms 아래로 설정하는 것은 의미가 없게 됨을 의미한다.

이러한 기본값을 변경하기 위해서 윈도우의 멀티미디어 타이머 API에 속해 있는 timeBeginPeriod()를 사용할 수 있는데, 이 함수는 클럭 주파수를 변경할 수 있게 한다. 하지만 이것도 최소 설정할 수 있는 주기의 값은 1ms 이다.

그러나 timeBeginPeriod()를 사용하는 것에는 아래와 같이 2가지의 위험성이 존재한다.

1) 자신의 프로세스만이 아닌 시스템 전체에 영향을 미치게 되는점
2) 시스템이 절약모드 상태 전환에 영향을 미칠 수도 있는점

그러나 Windows Media Player, and even QuickTime에서 이미 사용되고 있고, 이 API의 사용으로 인한 위험성이 크게 걱정할 수준이 아닌것으로 판단되어 크롬에서 빠른 주기의 타이머에는 timeBeginPeriod()를 사용하도록 처리한다.
(Windows Power APIs를 통해 배터리를 사용하는 노트북에서는 빠른 timer를 사용하지 않도록 스위칭하도록 설정)

WebKit 코드에서는 자체적으로 10ms 보다 빠른 처리는 실행되지 않도록 처리하고 있으며, 그 외 브라우저 들도 대체로 이러한 제한을 두고 있는데, 이렇게 설정한 이유는 아래와 같다.

1) 관례적
2) 제대로 작성되지 않은 웹 사이트의 경우 timer를 과도하게 사용 또는 잘못 사용하게 되면 CPU의 사용률과 브라우저에 영향을 미침 (이런 현상은 결국 브라우저의 잘못이라고 사용자들은 생각하게 될수 있기 때문에 일부러 제한을 설정)
3) 과도한 타이머의 사용은 싱글 프로세스 브라우저 구조를 갖는 브라우저에서는 브라우저의 다운을 가져올 수 있음

각 브라우저에 따라 최소로 설정될 수 있는 타이머의 주기는 아래와 같다.

IE8 및 그 이하 : 15.625ms
IE9+, Chrome : 4ms
FF, Safari : ~10ms


Javascript prototype (3) Development

객체를 상속받아 생성하게 되면, 원 객체와 새로 생성된 객체간에 보이지 않는 secret link가 생기게 된다.
바로 "__proto__"가 그러한데, __proto__는 생성된 객체와 원 객체를 이어지는 역할을 한다.

* __proto__는 WebKit 계열 브라우저와 Firefox에서만 지원된다.

아래와 같이 Dog란 객체를 생성한 경우를 가정해 보도록 하자.

var Dog = function(sBreed) {
this.breed = sBreed;
};

Dog.prototype.bark = function() {
return "bowwow!";
}

var zzing = new Dog("schnauzer");


이제 zzing.bark()와 같이 실행해 보면, prototype chaining을 통해 Dog.prototype.bark()를 실행하게 된다.

zzing.__proto__.bark()를 하더라도 동일한 결과를 얻을 수 있다.

인스턴스가 생성되어 있는 상태에서 아래와 같이 Dog 객체의 prototype을 다음과 같이 변경해 보자.

Dog.prototype = {
color : "black",
weight : "15kg"
};

이제 다시 zzing.bark()를 실행해 보자. 결과는 생각했던 것과 다르다. 일반적으로 새로운 값으로 overriding 해버리면 이전의 값은 더 이상 사용할 수 없는게 정상이다. 하지만 결과는 그렇지 않다.

zzing.bark();  // bowwow!
zzing.color;   // undefined

왜 그런 것일까? 이 글 처음에 언급했던 __proto__ 때문인데, 원 객체의 prototype을 변경하더라도 인스턴스가 생성되었던 시점의 객체와는 __proto__를 통해 연결된다. 그래서 실제로는 Dog에 해당 속성/메소드가 존재하지 않더라도 호출이 가능한 것이다.

그런데 또 한가지 이상한 점이 있다. (constructor 속성은 현 객체의 생성자 function을 반환하는 속성이다.)

zzing.constructor == Dog;  // true
zzing.constructor;  // Dog

위와 같이 zzing의 constructor는 Dog가 맞다(Dog로 부터 생성되었으므로). 하지만 Dog.prototype을 overriding하고 나서 아래와 같이 새로운 객체를 생성하면 다른 결과를 볼수 있다.

var benji = new Dog("dachsfund");

benji.bark();  // undefined (새로운 prototype에는 bark 메소드가 없으므로)
benji.color;   // black
benji.weight;  // 15kg

새로 정의된 prototype에 따라 올바르게 값을 호출하고 있다. 이제 다시 constructor를 확인해 보자.

benji.constructor == Dog;  // false
benji.constructor;  // Object

benji의 constructor는 Dog가 아니란 말인가? Dog.prototype을 변경하고 나서 새로 생성된 인스턴스는 이상하게도 Dog를 가리키지 않는다. 사실 이 부분은 버그라고도 볼수 있는데, constructor 속성은 readonly가 아니라 read/write이 가능한 속성이기 때문에 constructor를 통해 객체의 생성자를 확인하는 것은 위험할 수 있다.

따라서 prototype을 변경하는 경우에는 원 생성자를 가리킬 수 있도록 constructor 속성의 값도 아래와 같이 재지정 해주는것이 필요하다.

benji.constructor = Dog;

__proto__ 속성은 위에서 언급했던 것처럼 WebKit 계열과 Firefox에서만 사용이 가능하다. 그렇다면 다른 브라우저에서는 어떻게 해야할까?

자바스크립트의 native 객체중 하나인, Object 객체에는 getPrototypeOf() 메소드가 존재한다. 이 메소드는 __proto__ link를  통해 현 객체의 prototype chaining을 통해 원 객체의 prototype에 접근할 수 있도록 해준다. (이 또한 IE에서는 IE9 표준모드에서만 지원된다.)

위의 zzing은 이제 더이상 Dog의 prototype과 연결되어 있지 않다. __proto__가 지원되는 경우라면 zzing.__proto__를 사용할 수 있겠지만, 그렇지 않다면 Object.getPrototypeOf(zzing)과 같이 할 수 있다.

Object.getPrototypeOf(zzing).constructor == Dog
zzing.__proto__.constructor == Dog

1 2 3 4 5 6 7 8 9 10 다음