this

JS에서의 this

JS에서 함수의 this는 실행 컨텍스트에 따라 다양한 값을 가진다.

this가 쓰이는 함수를 어떤 방식으로 실행하는지에 따라 역할이 구분된다.

이를 바인딩이라하며, 함수 호출 방식에 따라 this에 바인딩할 객체가 동적으로 결정된다.

함수 호출 방식은 4가지가 있다.

  1. 함수 호출
  2. 메소드 호출
  3. 생성자 함수 호출
  4. apply / call / bind 호출

함수 호출

글로벌 영역에 선언한 함수는 전역객체의 프로퍼티로 접근할 수 있는 전역변수의 메소드가 된다.

전역객체는 모든 객체의 최상위 객체이며, 브라우저에선 window, 서버에선 global이 된다.

function foo() {
  console.log(this); // window
  function bar() {
    console.log(this); // window
  }
  bar();
}

foo();
window.foo();

즉, 글로벌 영역에 선언한 함수는 전역객체(window, global)의 프로퍼티로 접근이 가능한 메소드가 된다.

글로벌 영역에 선언한 foo 함수는 window의 메소드가 되어 window.foo() 처럼 사용할 수 있다.

foo 함수의 내부함수인 barthis도 전역객체에 바인딩된다.

내부함수의 this는 일반 함수, 메소드, 콜백 함수 관계없이 전역객체를 바인딩한다.

메소드 호출

함수가 객체의 프로퍼티 값이면 메소드로 호출된다. 메소드 내부의 this는 해당 메소드를 소유한 객체에 바인딩된다.

var foo = {
  name: 'Lee',
  sayName: function () {
    console.log(this.name);
  },
};

var bar = {
  name: 'Kim',
};

bar.sayName = foo.sayName;

foo.sayName();
bar.sayName();

위 컨텍스트의 실행 결과는 Lee, Kim 이다.

sayName 함수는 foo 객체의 프로퍼티 값이므로 메소드로 호출된다. 메소드 내부의 this는 해당 메소드를 소유한 객체, 즉 foo 객체에 바인딩된다.

그러므로 foo.sayName(); 의 결과는 Lee가 된다.

다음으로 bar 객체에 sayName이라는 키를 생성했고, 그 값에 함수를 할당했다. bar 함수의 sayName은 함수이므로, 메소드로 호출되며 sayName 메소드는 bar 객체가 소유하고 있다.

그러므로 bar.sayName(); 의 결과는 Kim이 된다.

생성자 함수 호출

JS의 생성자 함수는 객체를 생성하는 역할을 한다. 기존 함수에 new 연산자를 붙여 호출하면 그 함수는 생성자 함수로 동작한다.

function FOO(name) {
  this.name = name;
}

var me = new FOO('Kim');
console.log(me); // FOO {name: 'Kim'}

new 연산자로 생성자 함수를 호출하면 아래와 같은 순서로 동작한다.

  1. 생성자 함수의 코드가 실행되기 전, 그러니까 위에서 this.name = name; 이 실행되기 전에 빈 객체가 생성된다.

    생성자 함수가 이 빈 객체를 새로 생성하고, 생성자 함수 내에서 사용되는 this는 빈 객체에 바인딩된다.

  2. 생성된 빈 객체에 this를 사용하여 동적으로 프로퍼티나 메소드를 생성한다. 위에선 name 이라는 프로퍼티를 생성했다.

    이렇게 생성된 프로퍼티는 새로 생성된 객체 me에 추가된다.

  3. 생성된 함수를 반환한다. 위에선 생성자 함수에 반환문(return)이 없으므로, this에 바인딩된 새로 생성한 객체가 반환되어 FOO {name: 'Kim'}가 결과로 나오게 된다.

    생성자 함수에 반환문이 있는 경우, this를 반환하지 않고 반환하므로, 생성자 함수에선 반환문을 명시적으로 사용하지 않는다.

new 연산자를 붙이지 않으면 오류가 발생할 수 있다.

new 연산자를 붙이면 위 순서와 같이 생성자 함수가 생성한 빈 객체에 this가 바인딩된다.

그러나 붙이지 않으면 this는 전역객체에 바인딩된다.

apply / call / bind 호출

this를 특정 객체에 명시적으로 바인딩할 수 있다. 모든 함수 객체의 프로토 타입 객체인 Function.prototype 의 메소드인 apply, call, bind로 수행할 수 있다.

apply

apply()this를 특정 객체에 바인딩할 함수를 호출한다.

var Person = function (name) {
  this.name = name;
};

var foo = {};

Person.apply(foo, ['John']);
console.log(foo); // {name: 'John'}

apply 메소드의 첫 번째 인자로 빈 객체 foo, 두번째 인자로 Person 함수에 전달 할 매개변수의 배열을 할당한다.

이렇게 되면 Person 함수의 thisfoo 객체에 바인딩된다. foo 객체에 name 프로퍼티가 없으므로, 동적으로 생성된다. 이후 John이라는 값이 할당된다.

call

callapply와 기능은 같다. 차이점은 두번째 인자에서 배열 형태를 각각 하나의 인자로 전달하는 것이다.

Person.apply(foo, [1, 2, 3]);

Person.call(foo, 1, 2, 3);

bind

bind는 인자로 전달한 this가 바인딩된 새로운 함수를 반환한다. applycall은 바로 함수를 실행하지만, bind는 새로운 함수를 반환하므로 다시 호출해야한다.

var Person = function (name) {
  this.name = name;
};

var foo = {};

Person.bind(foo, 'John')();
console.log(foo); // {name: 'John'}

Reference

YUNSU BAE

YUNSU BAE

주니어 웹 개발자 배윤수 입니다!

예술의 영역을 동경하고 있어요. 🧑‍🎨