[javascript] 당신은 this를 잘 쓰고 있습니까?

도비굴·2023년 3월 7일
0
post-thumbnail

아뇨, 못 쓰고 있는데요?

"여",
뭔 개소리여...

사투리를 쓴 한 문장에서 '걔'를 부르는 데, 지칭하는 대상이 각기 다른 것처럼

자바스크립트에서도 엇비슷한 녀석이 있다.
부르는 방법에 따라 가리키는 대상이 달라지는 골 때리는 놈,
바로 this다.


"전역 객체야. 아니, 호출한 객체야. 아니, 이벤트를 받는 엘리먼트라니깐···."



이 글을 지금 구글링하여 보고 있다면,
똑같이 this를 써도 결과물이 달라지는 것에 혼선을 느꼈기 때문일 것이다.

왜 this는 쓰는 방법에 따라 결과물이 달라질까?

자바스크립트에서 this는 호출하는 방법에 따라 가리키는 대상이 달라지는
'실행 컨텍스트'이기 때문이다.

선생님 실행 컨텍스트가 뭔가요?
다메 설명해쥼,,

아무리 this래도 가리키는 방식이 달라지는 종류는 한정적이다.
그럼 this는 어떤 방식으로 호출하면 어떤 것을 가리킬까?
오늘 우리는 this의 사투리를 마스터할 것이다.










this가 바인딩되는 5가지의 모습


1. 전역 범위에서 사용하는 경우 전역 객체(Global Object)로 바인딩 되는 this

전역 범위에서 사용 시 this는 전역 객체 Window로 바인딩된다.

console.log(this); // 전역 범위에서 최상위는 Window다.
console.log(this === window); // true

전역 범위에서 사용하는 함수 역시,
상위는 window이므로 함수 안의 this는 Window로 바인딩된다.

function foo(){
  console.log(this); // 상위 객체 Window를 가리킨다
}
foo(); // Window



2. 객체 안 메서드에서 사용한 경우, 객체에 바인딩 되는 this

객체 안 메서드에서 호출하는 경우, 객체 메서드를 소유하고 있는 상위는 객체이므로,
객체에 바인딩된다.

const foo = {
      bar : function(){
          console.log(this); // 상위 객체 foo를 가리킨다.
      },
};

console.lo1g(foo.bar()); // {print: f}



const foo2 = {
    name : 'Joshua',
    bar: function(){
      console.log(this.name); /* 상위 객체 foo2의 name을 가리킨다. */
    }
}

const name = 'Hedy'; // 전역 객체의 name을 가리킨다.

console.log(this.name); /* Hedy */
console.log(foo.bar()); /* Joshua */



3. 생성자 함수 안에서 사용한 경우, 생성된 객체를 가리키는 this

생성자 함수 안에서 this를 사용한 경우, 생성하는 객체에 this가 바인딩된다.
new() 생성자 함수의 동작 방식이, 빈 객체를 만들고
생성된 객체 안에 메서드와 프로퍼티를 정의하여 반환하기 때문이다.

아래의 코드를 예시로 보자,

function KoreaName(lastName, firstName) {
  this.lastName = lastName;
  this.firstName = firstName;
}

const dobby = new KoreaName('PARK', 'SE YEON');

console.log(dobby.lastName); // PARK
console.log(dobby.firstName); // SE YEON

dobby라는 객체를 만들고 정의한 메서드와 프로퍼티를 추가해주므로,
객체 안 메서드에서 사용한 경우와 동일하게 this는 상위 객체 dobby를 가리키게 된다.


const dobby = {
 	lastName:  'PARK',
  	firstName : 'SE YEON',
}

비유하자면 위와 동일 구조가 된 셈이기 때문이다.





4. "this는 이거로 사용할거야!" 명시적 바인딩을 한 경우의 this

파라미터처럼, 함수 안에서 지칭할 this를 명시해줄 수 있지 않을까?
마치 아래의 코드처럼 말이다.

function foo(){
  var this = 'bb';
  console.log(this);
}
foo(); // Uncaught SyntaxError: Unexpected token 'this'
안타깝게도 bb가 나오지 않고, 에러 메세지를 띄운다.

this는 변수명으로 선언할 수 없기 때문에 문법이 잘못되었다는 뜻이다.

그럼 함수에서 사용할 this를 지정해주는 방법은 없는 것일까?

있다.

바로 call, apply, bind 메서드다. 이를 '명시적 바인딩'이라 한다.



4.1. call

function foo() {
  console.log(this);
}

const jenna = 'Seung';
 
foo.call(jenna); // Seung

위 코드처럼
function.call(this)형식으로,
함수 내에서 사용할 this를 지정해주는 것이 가능하다.

function foo(name) {
  console.log(this);
  console.log(name);
}

const jenna = 'Seung';
 
foo.call(jenna, 'Yeon'); // Seung Yeon

함수에 파라미터가 있다면,
function.call(this, params, params, ...)
지정된 인자를 뒤에 문자열로 이어 추가함으로 사용할 수 있다.



4.2. apply

function foo() {
  console.log(this);
}

const jenna = 'Seung';

foo.apply(jenna); // Seung

apply도 call과 동일하게
function.apply(this)형식으로,
함수 내에서 사용할 this를 지정해주는 것이 가능하다.

엥? 그러면 call이랑 apply랑 왜 나뉘어져 있나요?

apply는 call과 다르게 함수에 파라미터를 전달하는 방식이 다르다.

아래를 확인해보자

function foo(name) {
  console.log(this);
  console.log(name);
}

const jenna = 'Seung';
 
foo.call(jenna, 'Yeon'); // Seung Yeon
foo.apply(jenna, 'Yeon'); // Uncaught TypeError: CreateListFromArrayLike called on non-object

call은 정상적으로 인자가 전달되어 읽히는데, apply 사용시 에러 메세지를 띄운다.
받은 인자가 배열이 아니라는 의미인데,

function.apply(this, [params, params, ...])
위처럼 apply는 함수로 전달할 파라미터를 배열 형식으로 전달하기 때문이다.

function foo(name) {
  console.log(this);
  console.log(name);
}

const jenna = 'Seung';
 
foo.call(jenna, 'Yeon'); // Seung Yeon
foo.apply(jenna, ['Yeon']); // Seung Yeon


4.3. bind

function foo() {
  console.log(this);
}

const jenna = 'Seung';

foo.bind(jenna); // f foo() { console.log(this); }

bind도 call || apply와 비슷하게 사용하면 될 줄 알았으나,
애석하게도 foo가 찍어주는 this는 foo 함수를 가리킨다.


하지만, foo 안의 this가 가리키는 foo는 foo가 아니다.


이건 또 뭔 갸소리여..


bind는 call & apply와는 다르게 this를 바인딩한 새로운 함수를 반환한다.
즉 기 선언된 foo와, console에 나오는 bind된 foo는 엄밀히 말하면 다른 함수라는 것이다.

함수를 반환하기 때문에, 바로 실행되지 않는다.
실행하려면 반환한 함수를 실행시켜주어야 한다.

function foo() {
  console.log(this);
}

const jenna = 'Seung';

foo.bind(jenna)(); // Seung

함수의 반환이기 때문에 call & apply에선 할 수 없었던 콜백 함수로 사용할 수 있다.




5. 이벤트 핸들러에서의 this

이벤트 핸들러에서 사용한 경우
이벤트 핸들러에서 this는 호출한 Element를 가리킨다.

document.getElementById('foo').addEventListener('click', function(){
	console.log(this); // Element #foo
})



번외. 화살표 함수(Arrow function)에서의 this

화살표 함수에서의 this는 조금 다르다.

document.getElementById('foo').addEventListener('click', function(){
	console.log(this); //Element #foo
})

위 코드에서, this는 이벤트 핸들러 엘리먼트를 가리켰지만,

document.getElementById('foo').addEventListener('click', () => {
	console.log(this); // Window
})

화살표 함수로 변경하니 this는 Window를 가리킨다.
그 이유가 무엇일까?

화살표 함수의 this는 렉시컬 스코프이기 때문에, 자신의 this가 선언되지 않았으므로 확인하지 않고, 상위 객체의 this를 참조한다.
이벤트 핸들러 함수안에서 this를 호출했지만, this를 선언 및 가지고 있지 않으므로 이벤트 핸들러의 상위 객체인 Window를 참조하게 된다.




this ..

마침.

profile
개발굴

3개의 댓글

comment-user-thumbnail
2023년 3월 8일

개쩐다

답글 달기
comment-user-thumbnail
2023년 3월 8일

걍미쳤다

답글 달기
comment-user-thumbnail
2023년 3월 8일

미친도비

답글 달기