Frontend

<Frontend> JavaScript / This

이게왜 2024. 3. 27. 16:29

This

this는 JS에서 가장 혼란스러운 개념 중 하나로 꼽힙니다.

상황에 따라 this가 바라보는 대상이 달라지는데, 어떤 이유로 그렇게 되는지를 파악하기 힘든 경우도 있고 예상과 다르게 동작할 때도 있습니다

 

함수와 객체의 구분이 느슨한 JS에서 this는 실직적으로 이 둘을 구분하는 거의 유일한 기능입니다.

 

상황별로 this가 어떻게 달라지는지, 왜 그렇게 되는지 그 원인과 효과적으로 추적하는 방법 등을 작성해 보겠습니다.

 

시작!!


 

This란?

  • 함수가 실행될 때 함수 내부에서 사용되는 값.
  • 함수를 호출한 컨텍스트를 가리킨다.

"실행 컨텍스트" 포스팅에서 다뤘던 컨텍스트 개념의 이해가 필요합니다. 

2024.03.26 - [IT] - JavaScript / 실행 컨텍스트

 

<Frontend> JavaScript / 실행 컨텍스트

2024.03.24 - [IT] - JavaScript 기초 및 용어

rezerocodinglife.tistory.com

 

즉 This는 함수가 실행될 때, 실행되는 함수에 필요한 컨텍스트(환경정보)를 가지고 있습니다.

단, 이는 함수가 호출될 때 동적으로 결정되며, 호출 방식에 따라 this가 가리키는 대상이 달라질 수 있습니다.

 

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

const obj1 = {
  method1: sayThis
};

const obj2 = {
  method2: sayThis
};

obj1.method1(); // obj1을 가리킴
obj2.method2(); // obj2를 가리킴

 

위 코드는 this의 동적 결정 예시입니다.

이 예시에서 sayThis 함수는 다른 객체의 메서드로 사용될 수 있고, 호출될 때마다 다른 this를 가리킵니다.


Func This

어떤 상황에서 this가 다르게 동작하는지 알아보겠습니다.

- Binding 우선순위

  • new 바인딩 > 암시적 바인딩 > 명시적 바인딩 > 기본 바인딩 (">"는 부등호로 사용)

*왼쪽부터 순위가 높습니다.

 

 

- 기본 바인딩

  • 함수 호출 시 가장 낮은 우선순위를 가진다.
  • 함수를 단독으로 호출했을 때, 전역으로 선언된 상태에서 this는 전역 객체를 가리킨다.
  • 브라우저 환경의 전역객체 : window
  • Node.js 환경의 전역객체 : Global
// 단독 호출 Global Execution Context
function test() {
  console.log(this);
}

test();

 

위 코드의 출력입니다.

출력문을 보고 현재 this가 바라보는 객체는 "global"이라는 것을 알 수 있습니다.


암시적 바인딩

  • 객체에 메서드로 호출될 때
  • Object Binding

객체에 메서드로 호출될 때 this를 알아보기 위해 "함수로서 호출"과 "메서드로서 호출"을 비교하며 설명드리겠습니다.

var func = function (x) {
  console.log(this, x);
};

func(1); // global { ... } 1

var obj = { method: func };

obj.method(2); // { method: f } 2

 

func(1)의 this와 obj.method(2)의 this가 다르다는 것을 확인할 수 있습니다.

  • func(1)의 경우 함수를 변수에 담아 호출한 경우입니다.
  • func(2)의 경우 함수를 obj(객체)에 담아 호출한 경우입니다.

즉 var로 선언된 func는 전역이기 때문에 this는 전역객체인 global을 가리킵니다.

또한 func(2)는 obj의 메서드로서 호출됐기 때문에 this는 obj를 가리키는 것입니다.

 

 

- ** "함수로서 호출"과 "메서드로서 호출"을 구분하는 방법 -

 

점 표기법과 대괄호 표기법으로 호출한 함수는 메서드로서 호출이다! 

  • 점 표기법 : 앞에 '.'이 있으면 메서드로서 호출, 없으면 함수로서 호출입니다.
  • 대괄호 표기법 : 객체['함수 이름(property name)']  (ex) obj ['method'](2);

 

- 점 표기법, 대괄호 표기법 예제 -

var obj = {
  methodA: function () {
    console.log(this);
  },
  inner: {
    methodB: function () {
      console.log(this);
    },
  },
};

1. obj.methodA(); // { methodA: [Function: methodA], inner: { methodB: [Function: methodB] } }
2. obj["methodA"](); // { methodA: [Function: methodA], inner: { methodB: [Function: methodB] } }

3. obj.inner.methodB(); // { methodB: [Function: methodB] }
4. obj.inner["methodB"](); // { methodB: [Function: methodB] }
5. obj["inner"].methodB(); // { methodB: [Function: methodB] }
6. obj["inner"]["methodB"](); // { methodB: [Function: methodB] }

그럼 여기까지 학습한 내용을 바탕으로 예제코드를 보고 출력문을 예상해 보겠습니다.

var obj1 = {
  outer: function () {
    console.log(this); //(1)
    var innerFunc = function () {
      console.log(this); //(2) (3)
    };
    innerFunc();

    var obj2 = {
      innerMethod: innerFunc,
    };
    obj2.innerMethod();
  },
};
obj1.outer();

 

위 예시 코드를 보고 (1), (2), (3)에서 this가 가리키는 것을 예상해 봅시다.

더보기

(1) obj1

(2) 전역객체 (window, global)

(3) obj.2

** this 바인딩에 관해서는 함수를 실행하는 당시의 주변 환경(메서드 내부인지, 함수 내부인지등)은 중요하지 않습니다.

오직 해당 함수를 호출하는 구문 앞에 점 또는 대괄호 표시가 있는지 없는지가 관건인 것입니다.


명시적 바인딩

앞서 상황별로 this에 어떤 값이 바인딩되는지 알아보았습니다. 하지만 이러한 규칙을 깨고 this에 별도의 대상을 바인딩하는 방법도 있습니다.

이를 명시적 바인딩이라 칭합니다.

 

- 명시적 바인딩 방법-

  • call 메서드
  • apply 메서드
  • bind 메서드

 

- Call 메서드

call 메서드는 메서드의 호출 주체인 함수를 즉시 실행하도록 하는 명령입니다.

함수를 그냥 실행하면 this는 전역객체를 참조하지만 call 메서드를 이용하면 임의의 객체를 this로 지정할 수 있습니다.

var func = function (a, b, c) {
  console.log(this, a, b, c);
};

func(1, 2, 3); // global 1 2 3
func.call({ x: 1 }, 4, 5, 6); // {x:1} 4 5 6

 

위 코드에서 call을 사용하여 임의의 객체를 this로 지정할 수 있습니다.

 

var obj = {
  a: 1,
  method: function (x, y) {
    console.log(this.a, x, y);
  },
};

obj.method(2, 3); // 1 2 3
obj.method.call({ a: 4 }, 5, 6); // 4 5 6

- apply 메서드

apply 메서드는 call 메서드와 기능적으로 완전히 동일합니다.

반면, apply 메서드는 두 번째 인자를 배열로 받아 그 배열의 요소들을 호출할 함수의 매개변수로 지정한다는 차이가 있습니다.

var func = function (a, b, c) {
  console.log(this, a, b, c);
};

func.apply({ x: 1 }, [4, 5, 6]); // {x: 1} 4 5 6

var obj = {
  a: 1,
  method: function (x, y) {
    console.log(this.a, x, y);
  },
};

obj.method.apply({ a: 4 }, [5, 6]); // 4 5 6

 


- bind 메서드

call과 비슷하지만 즉시 호출하지 않고 넘겨받은 this 및 인수들을 바탕으로 새로운 함수를 반환하기만 하는 메서드입니다.

즉, bind 메서드는 함수에 this를 미리 적용하는 것과 부분 적용 함수를 구현하는 두 가지 목적을 모두 가집니다.

 

bind 메서드의 목적

  • 함수에 this를 미리 적용하는 것
  • 부분 적용 함수를 구현하는 것
var func = function (a, b, c, d) {
  console.log(a, b, c, d);
};

func(1, 2, 3, 4); // 1 2 3 4

var bindFunc1 = func.bind({ x: 1 });
bindFunc1(5, 6, 7, 8); // 5 6 7 8

var bindFunc2 = func.bind({ x: 1 }, 4, 5);
bindFunc2(6, 7); // 4 5 6 7
bindFunc2(8, 9); // 4 5 8 9

var bindFunc3 = func.bind({ x: 1 }, 4, 5, 6, 7);
bindFunc3(8, 9); // 4 5 6 7

 


 

new 바인딩

new 바인딩은 JavaScript에서 생성자 함수를 통해 새로운 객체를 생성할 때 사용되는 바인딩 방식입니다.

생성자 함수를 호출할 때 new 키워드를 사용하면 새로운 객체(instance)가 생성되고, 이때 this는 새로 생성된 객체를 가리킵니다.

이를 통해 생성자 함수 내부에서 this를 사용하여 새로운 객체에 property나 메서드를 추가할 수 있습니다.

function Person(name, age) {
  this.name = name;
  this.age = age;
}

const person1 = new Person("Alice", 25);
console.log(person1.name); // "Alice" 출력
console.log(person1.age); // 25 출력

person1.email = "hi@gmai.com"; // property 추가
console.log(person1.email);

person1.greet = function () { // 메서드 추가
  console.log("Hello, my name is " + this.name);
};
person1.greet(); // "Hello, my name is Alice" 출력

Arrow Func This

화살표 함수(Arrow Func)는 실행 컨텍스트 생성 시 this를 바인딩하는 과정이 제외됐습니다.

즉, 화살표 함수 내부에는 this가 아예 없으며, 접근하고자 하면 스코프체인상 가장 가까운 this에 접근하게 됩니다.

var obj = {
  outer: function () {
    console.log(this); // { outer: [Function: outer] }
    var innerFunc = () => {
      console.log(this); // { outer: [Function: outer] }
    };
    innerFunc();
  },
};

obj.outer();

 

이 코드에서 this는 항상 obj를 가리키게 됩니다.

 

** 화살표 함수를 사용하면 별로의 변수로 this를 우회하거나 call / apply / bind를 적용할 필요가 없어 더욱 간결하고 편리합니다.


this를 정확히 이해하여 코드가 의도와 다르게 동작하지 않도록 합시다!