this가 뭐죠?

JavaScript와 객체지향

가끔씩 웹이나 채팅 등에서 JavaScript에 대한 질문을 하는 사람들을 만나볼 수 있다. 초보적인 질문도 있고 상당히 수준있는 질문을 하시는 분들도 계신데, 내가 답해 드릴 수 있는 질문일 경우 최대한 답변해 드리려고 하는 편이다. 그런데 답변하는 입장에서 굉장히 난감한 질문이 있는데, 바로 this에 대한 질문이다.

tl;dr 한줄요약
this 쓰지 마세요. 다른 방법이 없지 않는 이상.

오해하시는 분이 있을까봐 짚고 넘어가자면 this가 나쁘다고 말하는게 아니다. this가 정확히 어떤건지 알고 사용하는 것이 아니라면 잘못 사용하기 쉽고, this를 사용한다고 해서 반드시 더 좋은 코드가 되는 것도 아니기 때문이다. 사실 특수한 경우가 아니라면 반대에 가깝다.


this는 어디에서 온 건가요?

JavaScriptJava라는 글자를 달고 있는 것은 결코 우연이 아니다. Netscape에서 웹 브라우저에 스크립트 언어를 탑재하기로 결정했을 당시 가장 핫한 언어는 Java였고, 넷스케이프에서는 이 새 언어에 Java 사용자들을 끌여들여야 한다고 생각했다. 그래서 그들은 그냥 언어의 이름을 JavaScript 로 지어버리고 문법도 최대한 자바스럽게 보이도록 설계했다.

Java에서 this 키워드의 의미는 명료하다. 메서드 내부에서 this 키워드는 지금 메서드가 호출된 인스턴스를 의미한다. 모든게 객체고 인스턴스인 자바의 특성상 this가 사용되지 않는 메서드는 거의 없다고 봐도 무방하다.

그런데 JavaScript에는 클래스가 없다.

이게 무슨 소리인가? 그럼 “new” 키워드는 뭐란 말인가? 글쓴이는 과연 JavaScript를 제대로 알기나 하는 것인가? 이런 의문에 답하기 위해서는 우선 JavaScript에서 객체지향이 어떻게 구현되었는지부터 알아봐야 한다.


prototype이 뭔가요?

JavaScript의 가장 큰 특징 중 하나는 타입이 없다는 것이다. 사실 없어도 너무 없다. 객체에 언제든지 원하는 이름의 필드에 어떤 값이든 추가할 수 있다는 사실은 다른 언어에서 온 개발자의 심장에 무리를 주고 퍼포먼스와 씨름하는 JS엔진 개발자에게 두통을 준다.

그럼 JavaScript에서 객체는 단순한 Dictionary인가? 그렇지만은 않다. JavaScript 객체와 Dictionary의 차이점은 객체에서 존재하지 않는 필드를 검색할때 나타난다. 다음과 같은 코드를 생각해 보자.

var myClass = {answer: 42}
var myInstance = Object.create(myClass)
myInstance.question = '1 Answer to the Ultimate Question of Life, the Universe, and Everything'
console.log(myInstance.question) // blabla
console.log(myInstance.answer) // 42

여기서 어떻게 42가 나오는 것일까? 가장 먼저 생각할 수 있는 가설은 Object.create()가 대상 객체를 복사한다는 것이다. 그럼 검증해 보자.

myClass.answer = null
console.log(myInstance.answer) // null

myClass를 변경하니 myInstance의 값도 변경되는 것을 확인할 수 있다. 즉, 복사는 아니라는 것이다. 그럼 실제로는 무슨 일이 일어나는 것일까?

JS엔진은 객체에서 property를 찾을 수 없으면 그 객체의 prototype에서 찾는다.

좀더 유식하게 말하자면 prototype은 property lookup의 fallback으로서 기능한다. 이 문장만 기억해도 어디 가서 스스로 JavaScript Hipster 라고 소개할 수 있을지도 모른다.


그럼 “new” 키워드는 뭔가요?

앞서 말했듯이 JavaScript의 문법(Syntax)은 Java와 비슷한 느낌적 느낌을 내려고 무진장 노력한 결과물이다. 그리고 자바에서는 객체를 새로 만들려면 무조건 new 키워드를 사용해야 한다. Object.create()라니.. 그건 또 뭐야?

그래서 new 키워드가 등장했다. 이놈의 목적은 JavaScriptprototype 기반의 객체지향구조를 자바처럼 쓸 수 있도록 하는 것이다. 예를 들어

var curTime = new Date()

위 코드는 아래의 코드와 의미적으로 정확히 동일하다.

var _newObj = Object.create(Date.prototype)
var _curTime = Date.call(_newObj)
var curTime = (_curTime == null || typeof _curTime === 'boolean' || typeof _curTime === 'number') ? _newObj : _curTime

여기서 명심해야 할 것은, Date.prototype은 Date 객체의 prototype이 아니다. 이 값은 new Date()로 만들어지는 객체들의 prototype으로 사용된다. 위 코드를 풀어서 설명하자면 아래와 같다.

  1. Date.prototype 객체를 prototype으로 하는 새로운 객체 _newObj를 생성한다.
  2. _newObj를 this값으로 하여 Date 함수를 호출한다.
  3. Date 함수의 리턴값이 없거나 원시값일 경우 _newObj객체를, 아니면 리턴값을 반환한다.

자! 드디어 this 가 나왔다. 이쯤 되면 글쓴이도 더이상 글을 빙빙 돌릴 수 없을 것이다. “this가 뭐죠?”라는 제목 보고 들어오신 분들, 낚시글인줄 아셨죠? 아닙니다.


그래서 this가 대체 뭐죠?

Java에서 함수는 메서드이고, 메서드는 클래스에 묶여 있다. 그리고 인스턴스는 클래스에서 생성된다. 그러므로 모든 메서드는 this 키워드가 가리키는 객체는 자신이 속한 클래스의 인스턴스라고 확신할 수 있다.

그런데 JavaScript에는 클래스가 없다. 함수는 그냥 혼자서도 잘 살고, 인스턴스는 원하는 대로 막 만들 수 있다. 그래도 넷스케이프는 JavaScript에서 객체지향을 구현하고 싶었다. 그렇다면 대체 어떻게 하면 1급 시민인 함수를 메서드처럼 쓸 수 있을까?

함수 내부에서 this 키워드는 호출 시점에서 이 함수를 property로 가지는 객체를 의미한다.

코드로 설명하자면 이거다.

function speak () {
console.log(this.sound)
}
var realDuck = {
sound: 'quack!',
speak: speak
}
var rubberDuck = {
sound: 'beep beep',
speak: speak
}
realDuck.speak()   // quack!
rubberDuck.speak() // beep beep

사실 말로는 참 쉬운데, 막상 듣고 이해하기는 좀 힘들다. 경험상으로는 막 쓰다 보면 어느순간 감이 잡히더라.

이 외에도 Function.prototype.call() 이나 Function.prototype.bind() 등의 수단으로 어디에서 호출되느냐에 상관없이 this를 고정시켜 버릴 수도 있다.


그런데 왜 this를 쓰지 말라고 한 거죠?

thisJavaScript에서 객체지향을 구현하기 위한 키워드다. 이 용도로 쓰는건 괜찮다. 사실 마땅한 대체재도 없다. list.len() 과 len(list) 중 전자를 선호한다면 무조건 this를 써야 한다.

문제는 이런 경우가 아닐 때 this를 쓰는 것이다. 예를 들자면 라이브러리에서 함수를 넘겨줄 것을 요구하는데, 함수 내부에서 특정한 작업을 하기 위해 this 밑의 메서드를 호출하도록 하는 경우가 있다. 드물지 않게 보이고 유명 개발자가 만든 널리 쓰이는 라이브러리에서도 발견되는 패턴이지만, 나는 이 패턴이 좋지 않다고 생각한다.

JavaScript에서 함수가 외부에서 입력받을 수 있는 값은 크게 세가지인데, 각각 call arguments, closure scope, thisArg 라고 할 수 있다. 이름은 사실 내가 임의로 붙인 것이지만, 알아보는데 큰 문제는 없을 것이다.

이 셋 중 가장 비직관적인 입력법은 단연 thisArg 이다. closure scope는 바깥 블럭에 선언된 변수가 소스코드에서 바로 보이고, call arguments도 함수 선언에 대놓고 적혀 있다. 반면 this 키워드는 그냥 뜬금없이 하늘에서 떨어진 것처럼 보인다. 이를 처음 접한 초보자는 당연히 혼란스러워 할 수밖에 없다.

이런 경우, this는 단순히 0번째 argument로 넘겨진 것과 같다. 그렇다면 굳이 this를 쓸 이유가 있을까? 만약 argument를 넘기고 싶다면 그냥 argument를 넘기자.


그래서 무슨 말이 하고 싶으신거죠?

객체지향은 매우 강력한 패러다임으로, 데이터와 행동을 캡슐화시켜 내부를 잘 몰라도 그 기능을 가져다 쓸 수 있도록 해 준다. JavaScriptthis 키워드를 통해 이런 기능을 제공하며, 이는 라이브러리 성격의 코드를 짤 때 필수적으로 알아둬야 할 사항이다.

반면 여러 라이브러리에서 제공하는 기능을 엮어 비즈니스 로직을 짜는 입장에선, this 키워드는 큰 의미가 없다. 일일히 클래스를 정의하는 것보다는 원하는 행동을 담은 함수를 바로 던져버리는 쪽이 훨씬 편하기 때문이다.

이처럼 JavaScript는 다른 언어들과 다른 독특한 특성을 많이 가지고 있다. 기존에 쓰던 패턴에 JavaScript를 끼워맞추려 하기 보다는, JavaScript만이 제공할 수 있는 강력함을 즐길 수 있다면 앞으로의 코딩이 더욱 즐거워지지 않을까?

One clap, two clap, three clap, forty?

By clapping more or less, you can signal to us which stories really stand out.