Java에서의 this vs JS에서의 this

Java에서의 this는 '객체 자기 자신'이라는 뜻으로 사용되고 있으며, Java를 처음 접했을 때에도 this에 대한 이해는 크게 어렵지 않았던 것 같아요.

비슷하게 JS에도 this가 있지만, Java의 this와는 꽤나 달랐기에 혼란스러운 부분들이 있었는데요. 그래서 이번에는 You Dont Know JS라는 책의 도움으로 이해했던 내용들을 정리해보았습니다.

this?

먼저 간단한 예제가 있습니다.

function hello(name) {
    this.who = name;
}

처음에 저는 이 예제에서 this.who가 Java에서 보통 클래스 내의 who라는 변수를 가리키고 있는 것처럼 hello라는 함수를 가리키는 것으로 생각했어요.

function hello(name) {
    this.who = name
}
hello.who = "Science Of Love";

hello("Scatterlab");

console.log(hello.who); // Science Of Love

제 생각대로라면, 위의 예제에서 hello.who의 값은 "Scatterlab"이 되어야 했지만, 결과는 "Science Of Love"였습니다. 왜일까요? 사실 this.whohello함수 객체를 보고 있는 게 아니었고, hello.who = "Science Of Love";this.who의 값을 바꾸는 게 아니었던 것입니다! 😨

via GIPHY

this!!

그렇다면 this.who는 도대체 누구일까요? hello.who는 도대체 누구를 바꾸고 있었던 걸까요? 이를 확인하기 위해 크롬의 개발자 도구를 이용하여 로그를 찍어 보도록 할게요.

screenshot

기본적인 바인딩은 window 전역 객체입니다. 여기서 hello를 호출한 시점을 보면 있는 그대로. 즉, 전역의 위치에서 호출을 하고 있고, 따라서 hellothis도 기본 바인딩인 window 전역 객체를 보고 있던 것이죠.

window.who // Scatterlab

사실 hello.who에 직접 변수를 넣는 경우는 거의 없겠지만, 조금 더 명시적으로 값을 넣을 순 있을 것 같습니다.

hello.call(hello, "Scatterlab!");
hello.who // Scatterlab!

바인딩 (Binding)

이제 hello.call과 같이 직접적 혹은 암시적으로 바인딩하는 방법에 대해 좀 더 알아보겠습니다.

바인딩 방법은 몇 가지가 있는데, 먼저 암시적 바인딩이라고 소개한 예제를 함께 보겠습니다.

function hello() {
    console.log(this.who);
}
const obj = {
    who: "Science Of Love",
    hello, // hello: hello 와 똑같이 작동합니다!
}
obj.hello(); // Science Of Love

이러한 경우 따로 바인딩을 하지 않아도 obj.hello를 부르는 시점에 objthis로 적용이 되어 this.whoobj.who"Science Of Love"를 출력하게 되는 것이죠.

하지만! 이러한 암시적인 바인딩은 의도치 않게 소실되는 경우가 있어서 조심해야 한답니다. 😭

function hello() {
    console.log(this.who);
}
const obj = {
    who: "Science Of Love",
    hello, // hello: hello 와 똑같이 작동합니다!
}
const helloWho = obj.hello;
helloWho();  // undefined

helloWho는 같은 obj.hello를 부른 줄 알았지만, 결과는 undefined였습니다.

helloWhoobj.hello를 참조하는 것처럼 보이지만, 사실 hello함수를 직접 가리키고 있고 전역의 위치에서 평범하게 호출을 하고 있으므로, hello함수의 this.who는 결국 전역 객체를 바라보고 있던 것이죠. 🤭

다음은 콜백함수를 전달하는 과정에서 나온 소실에 대한 소개입니다.

function hello() {
    console.log(this.who);
}
function doHello(helloFunc) {
    helloFunc();
}
const obj = {
    who: "Science Of Love",
    hello, // hello: hello 와 똑같이 작동합니다!
}
doHello(obj.hello);  // undefined

doHello에 넘기는 콜백함수 helloFunc은 인자로 넘기면서 바로 전 예제와 같이 hello함수를 직접 가리키고 있게 됩니다. 이처럼 의도치 않게 this가 다른 곳을 가리키게 되는 경우가 있기 때문에, 조심해서 사용해야 하는 거죠.

다음으로는 명시적 바인딩에 대한 예제입니다.

function hello() {
    console.log(this.who);
}
const obj = {
    who: "Science Of Love",
    hello, // hello: hello 와 똑같이 작동합니다!
}
const who = "Scatterlab";
hello.call(obj);  // Science Of Love

이전에 봤던 call을 사용하면 hello함수와 obj객체를 직접 바인딩하게 되며, 이렇게 바인딩된 hello함수의 thisobj가 됩니다.

여기서 callFunction.prototype으로부터 상속받은 내장함수로서, 첫번째 파라미터가 해당 함수의 this가 됩니다.

비슷하게 apply, bind가 있는데 callapply의 차이는 두 번째 파라미터가 Array냐 아니냐의 차이입니다.

function hello(name) {
    console.log(this.who + ", " + name);
}
const obj = {
    who: "Science Of Love",
    hello, // hello: hello 와 똑같이 작동합니다!
}
hello.call(obj, "Scatterlab"); // Science Of Love, Scatterlab
hello.apply(obj, ["Scatterlab!"]); // Science Of Love, Scatterlab 

bind의 경우는 call이나 apply와 달리 함수를 실행하지 않습니다. 그래서 call을 부르듯이 부르면 objthis로 바인딩한 hello함수를 리턴하게 되죠.

function hello(name) {
    console.log(this.who + ", " + name);
}
const obj = {
    who: "Science Of Love",
    hello, // hello: hello 와 똑같이 작동합니다!
}
hello.call(obj); // Science Of Love
hello.bind(obj); // ƒ hello(name) {	console.log(this.who + ", " + name); }

이러한 bind함수는, 이전 예제에서 보았던 암시적으로 바인딩했을 때 소실되는 경우를 보완할 수 있습니다.

function hello() {
    console.log(this.who);
}
const obj = {
    who: "Science Of Love",
    hello, // hello: hello 와 똑같이 작동합니다!
}
const helloWho = hello.bind(obj);
helloWho();  // Science Of Love

마지막으로는 new를 이용한 바인딩입니다. new를 선언한 함수는 새로운 객체를 생성한 후, 객체의 Prototype이 연결되며, 생성된 객체는 호출시 this로 바인딩이 됩니다.

function hello(name) {
    this.who = name
}
const helloWho = new hello("Scatterlab");

console.log(helloWho.who); // Scatterlab

hellonew로 선언을 하게 되면 새로운 hello객체가 리턴되고, 이 hello객체의 thishelloWho라는 리턴받은 객체와 바인딩이 되는 것이죠.

마치며...

지금까지 본 js의 this는 이 글에서는 다루지 못한 strict mode에서의 this, 그리고 각 바인딩의 우선순위까지 고려할 부분들이 많기 때문에 java의 this와는 다르게 실수할 여지가 많지만, 대다수가 논리적 에러이므로 그것을 잡아내는 것 또한 어렵습니다.

그래서 저희 연애의 과학 팀에서는 이러한 this를 실수 없이 다루기 위해 빡센 테스트 코드와 코드 리뷰를 진행하고 있답니다! 하지만 무엇보다 중요한 것은, 먼저 this를 제대로 이해하는 것이겠죠? 😎

🙌
연애의 과학 프로덕트팀에서는 프론트엔드 엔지니어를 채용중이에요 프로덕트와 리엑트, 웹을 사랑하고 능력있는 Pro-duck들이 모여, 연애의 과학을 만들고 있어요. 지금 연애의 과학팀에 합류하세요! → 채용공고 바로가기