자바스크립트 핵심컨샙33 #15. this, call, apply, bind

김동욱·2021년 6월 21일
0

자바스크립트

목록 보기
15/25
post-thumbnail

this란 메서드를 호출할 때 사용되는 객체를 말합니다.

자바스크립트의 함수는 호출될 때, this를 암묵적으로 전달 받습니다.

다시말하자면 this란 '자신이 속한 객체 또는 자신이 생성할 인스턴스를 가리키는 '자기 참조 변수'입니다. this를 통해 자신이 속한 객체 또는 자신이 생성할 인스턴스의 프로퍼티나 메소드를 참조할 수 있습니다.

하지만 자바스크립트의 this는 조금 특별합니다.
자바스크립트의 this는 자신이 호출한 함수나 상황에 따라 유동적으로 변경됩니다.


1.this

//1번
name = "kim";

console.log(this, this.name);

const person = {
  name: "lee",
  callName() {
    console.log(this, this.name);
  },

  callName2() {
    function callNameIn() {
      console.log(this, this.name);
    }

    callBack();
  }
}

//2번
person.callName()

//3번
person.callName2()

let personCall = person.callName

//4번
personCall()
  • 1번 함수를 호출하면 window(node는 golbal), "kim"이 나옵니다.
  • 2번 함수를 호출하면 "lee"가 나옵니다.
  • 3번 함수를 호출하면 window(node는 golbal), "kim"이 나옵니다.
  • 4번 함수를 호출하면 window(node는 golbal), "kim"이 나옵니다.

1번 2번은 이해가 가실겁니다. 하지만 3번 4번은 보자마자 멘붕이 오기 시작합니다. 하나씩 살펴보겠습니다.

this의 특징

this는 위에도 말했다싶이 자신이 속한 객체를 가리키는 변수임으로 1번 this는 전역객체(글로벌)를 가르킵니다. 1번 this.name은 당연히 글로벌 스코프영역에 있는 변수 name를 가르키므로 window, "kim"이 출력됩니다.

2번 this는 자신이 속한 객체 person을 가르킵니다. 그럼으로 당연히 this는 person this.name은 person의 프로퍼티중 하나인 name을 가르키므로 person, "lee"가 출력됩니다.

3번은 메소드의 내부함수입니다. 내부함수의 경우 this는 외부함수가 아닌 전역객체에 바인딩됩니다.
내부함수는 일반 함수, 메소드, 콜백함수 어디에서 선언되었든 관게없이 this는 전역객체를 바인딩합니다.왜냐하면 callName은 메소드이고 callName2는 함수로서 호출되었기때문입니다.(그냥 그렇게 시스템이 되어있다고 생각하면 편합니다.🤓)

4번은 person의 메소드를 변수 person2에 담고 호출을습니다. 당연히 this가 person인줄 알았지만 전역객체를 가르킵니다. 메소드를 변수에 담은 순간 그 변수는 객체의 메소드만을 참조하며 호출시에는 글로벌 영역에서 호출되기때문입니다.

여기서 알 수 있는 사실은 기본적으로 'this는 전역객체에 바인딩된다! 입니다.' 그리고 메소드 외부함수의 this만이 자신의 부모 객체를 가르킵니다. 위의 현상은 '생성자 함수'에서도 똑같습니다.

반면에 '엄격 모드("use strict")'에서 this 값은 실행 문맥에 진입하며 설정되는 값을 유지하므로 다음 예시에서 보여지는 것 처럼 this는 undefined로 남아있습니다.

"use strict";

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

f1()// undefined

🤔여기서 조금 다른점은 DOM API인 addEventListener()의 콜백함수의 this는 부모 객체를 가르킵니다. 자바스크립트에서 이벤트리스너를 호출할때 내부적으로 this를 부모 객체로 바인딩하기때문입니다. (물론 콜백함수 내의 내부함수의 this는 전역객체를 가르킵니다. 햇갈려죽겠습니다.🤤)

button.eddEventListener('click',function(){
	console.log("this는 "this)
})
//this는 button

이처럼 this는 매우 유동적이며 그 동작원리를 알기가 처음엔 햇갈립니다.
하지만 this를 사용자 임의대로 바인딩하는 방법이 있습니다.

const person = {
  callThis() {
    let _this = this;

    function inner() {
      console.log(_this)
    }

    inner()
  },
  name: "lee",
}

person.callThis()

this를 _this란 변수에 담은것처럼 this를 바인딩하면 _this는 어디서는 person을 가르킬것입니다.
저것또한 좋은 방법이지만 조금더 유연하고 고급스러운 방법이 있습니다.

바로 함수의 기본 메소드인 call, apply, bind를 입니다.


2.call

func.call(thisArg[, arg1[, arg2[, ...]]])
호출할 함수뒤에 메서드 구문으로 call을 적습니다. 여기서 thisArg란 func 호출에 제공되는 this의 값. 즉 func의 this가 될 객체값을 의미합니다. 뒤에나오는 arg(매개변수)들은 없어도 func에 들어갈 변수들이며 없어도 호출이 됩니다.

const person = {
  name: "lee",
}

let say = function(x){
  console.log(this.name + " " + x)
}

say.call(person, "HI!")//"lee HI!"

let sayVal = say.call(person)

sayVal//"lee HI!"

function say2(){
  say()
}
say2()//"lee HI!"

분명say라는 글로벌 영역에 있는 함수를 호출했는데 person객체의 프로퍼티인 "lee"가 출력됩니다. 그 이유는 call메서드를 이용 person을 '바인딩'했기 때문입니다.(person을 자신에게로 부르다(call)라고 생각하면 좋을거같습니다.) 전에 안됬던 내부함수또한 작동이 잘 됩니다. sayVal이란 변수에 넣고 출력해도 잘 됩니다.


3.apply

func.apply(thisArg, [argsArray])
apply는 거의 call구문과 유사합니다. 근본적인 차이점은 call은 함수에 전달될 인수 리스트를 받는데 비해, apply는 인수들의 단일 배열을 받는다는 점입니다. 바인딩할 객채값의 인수를 단일로 받을것이냐 배열로 받을것이냐의 차이입니다.

const list = {
  a : 1,
  b : 2,
  c : 3
}

function sum(x, y){
  let res = 0;

  for(i in this){
    res += this[i]
  }

  console.log(res + x + y)
}

sum.apply(list, [1, 2])//9

apply를 활용한 객체의 프로퍼티들을 더하는 함수를 만들었습니다. 인자값을 배열로 준다는것 외엔 큰 차이가 없습니다.


4.bind

func.bind(thisArg[, arg1[, arg2[, ...]]])
구문은 call과 차이가 없습니다. 하지만 bind는 함수를 호출하는 것이 아닌 새로운 함수를 만들어 리턴을 해줍니다. 그렇기 때문에 bind 함수 사용 시 바로 함수가 호출되지 않습니다.

const person = {
  name: "lee",
}

let say = function(x){
  console.log(this.name + " " + x)
}

say.bind(person, "HI!")// 호출안됨

let sayVal = say.bind(person, "Hi!")

sayVal()

마무리

자바스크립트를 햇갈리게 만드는 원흉중 하나인 this에 대해 알아보았습니다. this가 가리키는 객체와 또 this를 바인딩 하는 메서드들을 잘 활용하면 중복없는 효율적인 코드를 작성하는데 큰 도움이 되실겁니다.

profile
안녕하세요. 부산에서 근무하고 있는 프론트엔드 개발자 김동욱입니다. 영어 공부 겸 개발 공부를 위해서 글을 작성하고있습니다.

0개의 댓글