js의 함수내에서 this를 사용할 때 this의 의미가 호출되는 문맥에 따라 변한다는 사실은 때때로 매우 혼란스럽다. this의 의미를 알아내기 위해 디버깅을 해보기도 하고 console에 로그를 남겨보기도 하면서 그때 그때 문제를 해결해 나가고 있지만 문제 해결을 하고 나면 싹 잊어버리고 지내다가 다시 문제를 만나면 그제서야 다시 공부를 하곤 한다.

이 글에서는 모든 케이스에서 this의 의미가 변하는 걸 보기 보다는 이벤트 핸들러 내부에서 this가 사용될 때 this가 무얼 가리키게 되는지에 대해서만 살펴보려고 한다.

우선 아래의 tag를 보자

<input type='file' onchange='handleFile(this.files);' />

handleFile은 이렇게 생겼다.

function handleFile(files) {
	console.log(this);
	...
}

여기서 handleFile에서 this가 가리키는 것은 window 이다. 사실 나는 this가 input element를 가리킬 줄 알았다. 그래서 이 이벤트 핸들러 내부에서 this.files를 참조하여 파일 작업을 할 수 있을거라 생각했지만 this는 input이 아니다. 왜 그럴까?

이벤트가 발생되면 onchange 내부의 문자열은 this가 이벤트가 발생한 DOM으로 설정된 함수의 body처럼 동작한다(DOM on-event handlers).

function (event) {
	handleFile(this.files);
}

attribute의 내용이 body가 된다고요? 잠깐만요! 이해가 안된다면 onchange를 다음처럼 변경하면 더 확실해 진다.

onchange='console.log(this); handleFile(this.files)'

이 attribute는 다음 코드처럼 실행된다.

function (event) {
	console.log(this); // input type=file
	handleFile(this.files);
}

그런데 handleFile내부에서는 왜 this가 input 이 아닐까. javascript는 함수가 object에 바인드 되지 않은 상태에서 호출되면 이 함수 내부의 this는 브라우져의 window object가 된다(this). 그렇기 때문에 이 경우 handleFile 내부에서는 this === Window 이다.

그럼 handleFile내부의 this를 input으로 설정하려면 어떻게 해야 할까. bind나 call을 사용하면 된다.

onchange=’(handleFile.bind(this))(this.files);’

onchange=’handleFile.apply(this, [this.files]);’

addEventListener를 사용하거나 onchange에 직접 함수를 할당하는 방법은 attribute에 함수 내용을 써 넣을 때와 달리 다른 함수로 wrapping 되지 않으므로 this의 의미가 변경되는 일에 대해서 걱정하지 않아도 된다.

file = document.getElementsByTagName('input')[0];
/// 이렇게 하거나,
file.addEventListener('change', function() {
  console.log('event listener');
  console.log(this); // 여기서는 input type=file
});
/// 이렇게 하면 된다.
file.onchange = function() {
  console.log('onchange');
  console.log(this); // input type=file

}