[JavaScript] this : 함수/메서드 내 this 차이

 

자바스크립트에서의 this는 일반 함수 표현식과 화살표 함수 표현식에서 다르게 동작한다.

  • 일반 함수 표현식 : 동적 바인딩 - 함수 호출 방식에 따라 this가 달라짐
  • 화살표 함수 표현식 : 정적 바인딩 - 화살표 함수가 정의된 상위 컨텍스트를 따름

this는 위와 같이 일반 함수 표현식 / 화살표 함수 표현식에서 다르게 동작하지만, 일반 함수 표현식에서의 this는 함수가 어떻게 호출되는지에 따라 또 달라진다. 일반 함수가 함수로 사용되었는지, 메서드로 사용되었는지에 따라 다르게 동작한다.

 

 

01. 일반 함수 표현식에서 this

일반 함수에서의 this는 동적 바인딩을 따른다.

즉, 함수가 어떻게 호출되었는지에 따라 this가 결정된다.

 

#1. 함수 내에서 호출하는 this

기본적으로 함수가 독립적으로 호출되면, this는 전역 객체(window, global)를 참조한다.

 

🔎 ex1) 전역 함수 호출 시 this

function Constructor() {
  this.field = 0; // Constructor 객체 내 this.field = 0
  function inner() {
    console.log(this.field); // 전역 객체의 field (undefined)
  }
  inner(); // 호출 컨텍스트: 전역 객체
}

var object = new Constructor(); // new 키워드를 사용해 Constructor를 객체화

전역에서 호출된 일반 함수의 this는 전역 객체를 참조한다.

inner()는 Contructor 내부에 있지만, 독립적인 함수로 호출되므로 inner() 내부의 this는 전역 객체를 참조한다.

이때 전역 객체에 field 속성이 없으므로 undefined가 출력된다.

 

 

🔎 ex2) 타이머 함수 안에서의 this

function Constructor() {
  this.field = 0; // Constructor 객체 내 this.field = 0
  setInterval(function inner() {
    console.log(this.field); // 전역 객체의 field (undefined)
  }, 1000);
}

var object = new Constructor();

setInterval()과 같이 타이머 함수 내부에서 일반 함수가 호출되면, 그 함수의 this 역시 전역 객체를 가리ㅣㄴ다.

setInterval 내부의 inner() 함수는 일반 함수로 호출되므로, this는 전역 객체를 가리키고, 전역 객체에 field 속성이 없어 undefined가 출력된다.

 

 

💡 함수 내 this는 객체로 초기화되지 않는 한 전역 객체를 가리킨다.

this는 함수가 객체로 초기화되지 않는 한, 전역 객체를 가리키며 일반 함수 표현식에서 내부 함수나 콜백 함수 등은 기본적으로 전역 객체를 가리킨다. 그러나 함수가 객체로 초기화되면, 그 함수 내에서 this는 전역 객체가 아닌 객체화된 함수를 가리킨다.

아래 예시 코드를 보면 객체화되었을 때와 기본적으로 전역 객체를 가리키는 this의 차이를 명확하게 알 수 있다.

function Constructor1() {
  this.field = 0; // Constructor1 객체의 field
  console.log(this.field); // 0, Constructor1 객체의 field
  function doublewrapper() {
    this.field = 1; // 전역 객체의 field
    console.log(this.field); // 1, 전역 객체의 field

    function Constructor2() {
      console.log(this.field); // undefined, Constructor2 객체의 field
    }
    new Constructor2(); 
  }
  doublewrapper(); // 호출 컨텍스트: 전역 객체
}

new Constructor1();
console.log(field); // 전역 객체의 field = 1

 

  • Contructor1에서 this
    • new Counstuctor1()에 의해 Constructor1은 객체로 초기화되었기 때문에, Contructor1에서 this는 Constructor1객체를 가리킴
    • this.field = 0은 Constructor1 객체 내에 속성이 설정된 것. this.field는 0을 출력
  • doublewrapper() 함수 내에서 this
    • doublewrapper()는 객체화되지 않았으므로 doublewrapper() 함수 내 this는 전역 객체를 가리킴
    • this.field = 1은 전역 객체의 field를 1로 설정
    • 전역 객체의 field 값인 1을 출력
  • Constructor2에서 this
    • new Constructor2()에 의해 객체화 되어 this는 Counstructor2 객체를 가리킴
    • Constructor2 객체에는 field 속성이 없기 때문에 undefined가 출력

 

 

 

#2. 메서드 내에서 호출하는 this

메서드는 객체의 속성으로 정의된 함수이다.

메서드에서의 this는 메서드를 소유한 객체를 가리킨다. 즉, 메서드가 속한 객체가 this로 바인딩된다.

const Object = {
  field: 0,
  method() {
    console.log(this.field);
  }
}
Object.method();

Object.method()가 실행되면 this.field의 this는 호출한 객체인 Object 객체를 참조한다. 메서드가 객체의 속성이기 때문이다.

따라서 this.field는 Object 객체의 field 값을 참조하여 0을 출력한다.

 

 

 

02. 화살표 함수 표현식에서 this

화살표 함수에서 this는 정적 바인딩을 따른다. 

이는 화살표 함수가 선언된 위치의 스코프에서 this가 결정된다는 의미이다.

화살표 함수 내부의 this는 상위 스코프의 this를 유지한다.

function Constructor() {
  this.field = 0; // Constructor 객체의 field
  setInterval(() => {
    console.log(this.field); // 화살표 함수의 this는 Constructor의 this를 참조
  }, 1000);
}

var object = new Constructor();

앞서 setInterval() 안에 일반 함수 표현식으로 작성된 inner() 함수 안에서의 this는 전역 객체를 가리켰다.

반면 위의 예시 코드처럼 setInterval()안에 화살표 함수 표현식으로 작성하면, 화살표 함수 내 this는 Constructor 객체를 가리킨다.

따라서 this.field는 Contructor 객체의 field 값을 가리켜 0을 출력한다.

 

즉, 화살표 함수는 setInterval을 감싸고 있는 Contructor 함수의 this를 참조하므로, 화살표 함수 내 this는 고정적으로 Constructor 객체를 가리키는 것이다.

 

 

 

 

 

일반 함수 표현식에서의 함수, 메서드 내 this와 화살표 함수 표현식에서의 this의 차이를 요약하면 다음과 같다.

const object = {
  nickname: 'guswjd',
  method: function() { console.log(this.nickname); }, // 일반 함수, this는 object
  method_shorten() { console.log(this.nickname); },   // 메서드 축약형, this는 object
  arrow_function: () => console.log(this.nickname)    // 화살표 함수, this는 상위 스코프 (전역 객체)
};

object.method();          // 'guswjd' 출력
object.method_shorten();   // 'guswjd' 출력
object.arrow_function();   // undefined (전역 객체에 nickname이 없음)

 

  • 일반 함수: this는 동적으로 호출 컨텍스트에 따라 달라짐
  • 메서드: this는 메서드가 속한 객체를 가리킴
  • 화살표 함수: this는 정적으로 상위 스코프의 this를 고정적으로 참조

 

object.method()와 object.method_shorten()에서는 this가 object를 가리키고, nickname을 잘 출력한다.

하지만 object.arrow_function()에서는 화살표 함수의 this가 상위 스코프인 전역 객체를 가리키기 때문에 nickname을 찾지 못하고 undefined가 출력된다.

 

 

 

 

 

03. 명시적 바인딩(Explicit Binding) - call, apply, bind

명시적 바인딩은 자바스크립트에서 this를 명시적으로 설정하는 방법이다.

함수가 호출될 때 어떤 객체가 this로 설정되어야 하는지를 명시적으로 지정할 수 있다.

이때 주로 사용하는 메서드가 call, apply, bind이다.

 

 

#1. 암시적 바인딩(Implicit Binding)

암시적 바인딩은 객체의 메서드로 호출된 함수에서 자동으로 그 객체를 this로 사용한다.

즉, 메서드가 속한 객체가 자동으로 this로 바인딩된다.

const obj = {
  foo: 42,
  bar() {
    console.log(this.foo); // obj의 foo 값인 42를 출력
  }
};

obj.bar(); // 42 (암시적 바인딩: this는 obj)

 

 

 

 

 

#2. 명시적 바인딩(Explicit Binding)

명시적 바인딩은 call, apply, bind를 사용해 함수 호출 시 this를 명시적으로 지정하는 것이다.

  • call : 함수를 호출하면서 this를 명시적으로 설정
  • apply : call과 비슷하지만, 인자를 배열 형태로 전달
  • bind : this를 설정한 새로운 함수를 반환(호출은 나중에)
function greet() {
  console.log(this.name);
}

const person = { name: 'guswjd' };

greet.call(person);  // guswjd
greet.apply(person); // guswjd
const boundGreet = greet.bind(person);
boundGreet();        // guswjd

 

 

 

#3. 명시적 바인딩과 일반 함수, 화살표 함수의 this 차이

  • 일반 함수 : 동적 바인딩을 따르기 때문에, 명시적 바인딩을 통해 this를 지정할 수 있음
  • 화살표 함수 : 정적 바인딩을 따르기 때문에, call, apply, bind와 상관없이 this는 고정

◼︎ 일반 함수에서의 명시적 바인딩

function createObject() {
  console.log('Inside `createObject`:', this.foo); // 21
  return {
    foo: 42,
    bar: function() { 
      console.log('Inside `bar`:', this.foo); // 42 (this는 호출한 객체)
    },
  };
}

createObject.call({ foo: 21 }).bar(); // 42

 

createObject.call({ foo: 21 })로 createObject 함수의 this를 { foo: 21 }로 명시적으로 설정하였다.

따라서 console.log('Inside `createObject`:', this.foo);는 Inside 'createObject' : 21이 출력된다.

내부 bar 메서드는 createObject가 반환한 객체 내에서 호출되므로, this는 그 객체를 가리키며 Inside 'bar' : 42가 출력된다.

 

 

 

◼︎ 화살표 함수에서 명시적 바인딩

function createObject() {
  console.log('Inside `createObject`:', this.foo); // 21
  return {
    foo: 42,
    bar: () => console.log('Inside `bar`:', this.foo), // 화살표 함수
  };
}

createObject.call({ foo: 21 }).bar(); // 21

bar는 화살표 함수이므로 상위 스코프의 this를 참조하며, this는 정적이다.

따라서 createObject.call에 전달된 { foo: 21 }로 고정되며, createObject 함수의 this를 참조하게 된다.

화살표 함수는 외부 함수(createObject)의 this를 고정으로 사용하므로, 명시적 바인딩으로 this가 바뀌지 않으며 { foo: 21 }의 값이 출력된다.

 

반응형