[Javascript] this에 대한 이해와 call(), apply(), bind() 메소드 사용방법

2020. 11. 1. 19:37Tech Article/Javascript

this와 call(), apply(), bind()

this는 javascript의 이해하기 어려운 내용 중 하나입니다. 저 또한 this를 이해하지 못하였을 때, this에 대한 두려움이 많았습니다.
하지만 단 한번 제대로 이해를 하고 나니 오히려 js가 더 재밌게 느껴졌습니다.
this에 대해 이해를 아직 잘모르신다면 반드시 짚고 넘어가도록 권하고 싶습니다.

Content

크게 아래와 같은 내용으로 나눠서 설명하였습니다.

  1. call()에 대한 이해
  2. apply()에 대한 이해
  3. bind()에 대한 이해
  4. function에서의 this
  5. class에서의 this

Description

this란 MDN에서 이렇게 설명한다.

the value of this is determined by how a function is called (runtime binding)

function에서 사용되는 value임을 알 수 있다. this는 execution context(global, function 등)의 property이다.
default로는 global을 가리키고 있다.

console.log(this)를 해보면 console.log(window)와 같음을 알 수 있다.
=> Chrome의 developer tool의 console에 연습해보도록 하자.

this의 특징은 lexical scope를 갖지 않는 다는 것이다. 이것은 우리의 예측을 빗나가게 만든다.
직접적으로 어떤context인지 지정하지 않으면 항상 global을 가리킨다.


1. call()에 대한 이해

어디에 쓰이는가?

여러 object들이 하나의 method(function)들을 공유할 수 있다. 즉 code를 clean하고 dry하게 작성할 수 있다.

우리는 function을 invoke할때와 같이 한다. 이 때foo()foo.call()과 같은 것이다.

function foo() {
  console.log("foo yes");
}

foo();

foo()의 parenthesis안에는 argument들을 넣는데, call은 첫번째 자리에 object가 들어가게 된다. this로 연결시킬 object를 정해주는 것이다.
따라서 아래와 같이 표현될 수 있다.

function foo(age, gender) {
  console.log(`name: ${this.name}, age: ${age}, gender: ${gender}`);
}

let obj = { name: "cho" };

foo.call(obj, 20, "female"); //name: cho, age: 20, gender: female
foo(30, "male"); //name: , age:30, gender: male
line number 이유
line 7 call로 this로 연결할 object를 지정해줌, 따라서 name에 cho가 출력 됨
line 8 this로 연결될 object가 지정되어 있지 않음, 따라서 undefined로 아무것도 나오지 않음

2. apply()에 대한 이해

call과 차이점이 무엇인가?

apply와 call의 기능은 같다. 단지 syntax의 차이가 있을 뿐이다.

call 사용시 function의 arguments를 하나씩 넣어줘야한다.

function foo(age, gender) {
  console.log(`name: ${this.name}, age: ${age}, gender: ${gender}`);
}

let obj = { name: "cho" };

foo.call(obj, 20, "female"); //name: cho, age: 20, gender: female

apply는 function의 arguments를 array로 넣어준다.

function foo(age, gender) {
  console.log(`name: ${this.name}, age: ${age}, gender: ${gender}`);
}

let obj = { name: "cho" };
let objArgs = [20, "female"];
foo.apply(obj, objArgs); //name: cho, age: 20, gender: female

3. bind()에 대한 이해

bind는 왜 쓰는가?

간혹 function을 그 자리에서 실행하지 않고, variable에 할당하고 저장해두는 경우가 있습니다. 즉 저장의 용도로 사용하는 것입니다.

call과 apply는 그 자리에서 function을 invoke(IIFE, Immediately Invoke Function Expression)하는 반면, bind는 function을 저장하여 나중에 invoke할 수 있습니다.

function foo(age, gender) {
  console.log(`name: ${this.name}, age: ${age}, gender: ${gender}`);
}

let obj = { name: "cho" };
let fooBindO = foo.bind(obj, 20, "female");
let fooBindX = foo;

fooBindX(); //name: , age: 20, gender: female
fooBindO(); //name: cho, age: 20, gender: female
line number 이유
line 9 bind하지 않고, function을 저장한 경우
line 10 object를 bind하고, function을 저장한 경우

fooBindX는 bind가 되지 않았을 경우이고, fooBindO는 bind를 했을 경우입니다.

주석에서 각각의 결과값을 보면, fooBindX는 name 부분이 undefined임을 알 수 있습니다.(비어있음)

따라서 fooBindX와 같이 bind없이 함수만 assign하면 this를 잃어버립니다. 따라서 fooBindX의 형태로 쓰지 않도록 합니다.


4. function context 에서의 this

아래 코드에 실행순서는 주석의 #1.부터 #5.까지 따라가면 됩니다.

function a() {
  //#2. this for function a
  console.log("function a: ", this); //global this
  //#3. this for function b
  function b() {
    console.log("function b: ", this); //global this
  }
  b();
  //#4. this for object and assign to another variable then invoke it
  let bBindX = {
    say: "oh no",
    b: function () {
      console.log("function b: ", this); //global this
      console.log("function b say: ", this.say); //undefined
    },
  };
  let saveB = bBindX.b;
  saveB();
  //#5. this for object and IIFE
  let bBindO = {
    say: "oh yes",
    b: function () {
      console.log("function b binding: ", this); //function b this
      console.log("function b binding say: ", this.say); //oh yes
    },
  };
  bBindO.b();
}

//#1. invoke function a
a();

위 코드에 대해서 console 출력의 이유에 대해서 말해보면

line number 이유
line 3 this는 lexical하지 않기 때문에, global execution context를 갖게 됨
line 6 this는 lexical하지 않기 때문에, function a를 무시하고 global execution context를 갖게 됨
line 13 function을 variable에 assign하면 reference를 잃게 됨, 따라서 lexical하지 못하므로, global을 갖게됨
line 14 function을 variable에 assign하면 reference를 잃게 됨, 따라서 undefined가 나타남
line 23 function을 object(bBindO)로 부터 직접 call했으므로, reference를 잃지않음, 따라서 this가 object에 binding됨

line 17에서 variable에 할당하면서 reference를 잃게 되는 것이 포인트이다.
이를 다시 bind시켜주려면, line17에서 bBindX.b.bind(bBindX)로 항상 bind되도록 해주면서 variable에 assign해야한다.
bind라는 것은 object와 function을 항상 연결시켜주는 것을 말한다.


5. class context 에서의 this

MDN을 보면 이렇게 나와있다.

Within a class constructor, this is a regular object. All non-static methods within the class are added to the prototypes of this

class에서 this는 보통 object와 같으며, static이 아닌 class내의 method들은 this에 prototype으로 추가된다.

아래 예제를 보면 line 의 출력은 myApp{name: "cho"}이다. 즉, this는 class의 non-static에 대한 정보들을 담고 있으며, class execution context 환경에 bind되게 된다.

이는 우리가 React의 class타입에서 자주보는 this.handleClick = this.handleClick.bind(this);관련이 있다. [CodePen 코드 보기]

bind(object)가 되게 되는데, class에서는 this가 regular object이기 때문에, 이렇게 쓰이는 것이다.

class myApp {
  constructor() {
    this.name = "cho";
  }

  myAppfunc() {
    console.log("this: ", this);
  }
}
const myAppInstance = new myApp();
myAppInstance.myAppfunc();

 


Example

bind로 currying function 만들기

 

 

Reference Link