자바스크립트 this 완벽 분석: 초보자도 마스터하는 핵심 개념과 활용법 (면접 대비)

자바스크립트 this, 대체 넌 누구냐? 완벽 이해와 활용 가이드 (초보자를 위한 상세 설명)

안녕하세요, 개발자 여러분! 자바스크립트를 공부하다 보면 가장 많은 질문과 혼란을 야기하는 키워드 중 하나가 바로 this입니다. 다른 프로그래밍 언어의 this와는 다르게 동작하는 녀석이라 "도대체 this는 언제 무엇을 가리키는 거지?"라는 의문을 품게 되죠.

하지만 걱정 마세요! 오늘 이 글을 통해 this의 모든 것을 파헤쳐 보고, 언제 어떻게 사용해야 하는지 명확하게 알려드리겠습니다. this 개념을 완벽하게 이해하고 나면 자바스크립트 코드의 흐름을 훨씬 더 잘 파악하고 능숙하게 다룰 수 있게 될 겁니다.


📚 목차

  1. this란 무엇인가? (다른 언어와의 차이점)
  2. this 바인딩 5가지 핵심 규칙
  3. this, 언제 사용하면 좋을까? (실제 활용 사례)
  4. this 마스터를 위한 팁!

1. this란 무엇인가? (다른 언어와의 차이점)

자바스크립트에서 this함수가 어떻게 호출되었는지에 따라 동적으로 결정되는 특별한 키워드입니다. 이게 핵심입니다! C++, Java 등 다른 언어의 this가 대개 "현재 객체 인스턴스 자신"을 명확히 가리키는 것과 달리, 자바스크립트의 this함수의 정의 시점이 아닌, 호출 시점의 문맥(Context)에 따라 유연하게 바뀝니다.

이러한 유연성 때문에 처음에는 어렵게 느껴질 수 있지만, 이 특징을 이해하면 자바스크립트의 강력한 동적 특성을 활용할 수 있게 됩니다.


2. this 바인딩 5가지 핵심 규칙

this가 무엇을 가리킬지는 주로 함수가 호출되는 5가지 방식에 따라 결정됩니다. 이 규칙들을 정확히 아는 것이 this를 이해하는 지름길입니다.

1) 일반 함수 호출 (Default Binding)

가장 기본적인 형태의 함수 호출입니다. 어떤 객체의 메서드가 아닌, 독립적인 함수로 호출될 때 this가 어떻게 되는지 살펴봅시다.

  • 비엄격 모드 (Non-strict mode): this전역 객체를 가리킵니다.
    • 브라우저 환경: window 객체
    • Node.js 환경: global 객체
  • 엄격 모드 (Strict mode): thisundefined가 됩니다. 이는 개발자의 실수로 전역 객체의 속성을 오염시키는 것을 방지하기 위한 조치입니다.

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

// 브라우저 환경 (비엄격 모드)
showThis(); // ⇨ window 객체

// Node.js 환경 (비엄격 모드)
// showThis(); // ⇨ global 객체

'use strict';
function showThisStrict() {
  console.log(this);
}
showThisStrict(); // ⇨ undefined

2) 메서드 호출 (Implicit Binding)

함수가 객체의 프로퍼티로서 호출될 때, 즉 객체.메서드() 형태로 호출될 때 this는 해당 메서드를 호출한 객체 자체를 가리킵니다. 이 경우가 가장 직관적으로 this가 사용되는 방식입니다.


const person = {
  name: 'Alice',
  sayHello: function() {
    // 여기서 this는 person 객체를 가리킵니다.
    console.log(`Hello, my name is ${this.name}`);
  }
};

person.sayHello(); // ⇨ Hello, my name is Alice

// 다른 객체에 할당해도 호출하는 객체에 따라 this가 바뀝니다.
const anotherPerson = {
  name: 'Bob',
  greet: person.sayHello // 메서드를 다른 객체에 할당
};

anotherPerson.greet(); // ⇨ Hello, my name is Bob (this는 anotherPerson 객체를 가리킴)

3) 생성자 함수 호출 (New Binding)

함수가 new 연산자와 함께 호출될 때 (즉, 생성자 함수로 사용될 때), this새롭게 생성된 인스턴스 객체를 가리킵니다. new 키워드를 사용하면 다음과 같은 일들이 자동으로 일어납니다.

  1. 새로운 빈 객체가 생성됩니다.
  2. 이 빈 객체가 this로 바인딩됩니다.
  3. 생성자 함수의 코드가 실행되면서 this에 속성이나 메서드가 추가됩니다.
  4. 생성된 객체가 반환됩니다 (명시적으로 다른 객체를 반환하지 않는 한).

function Car(make, model) {
  // 여기서 this는 새롭게 생성될 Car 인스턴스를 가리킵니다.
  this.make = make;
  this.model = model;
  this.getInfo = function() {
    console.log(`This is a ${this.make} ${this.model}.`);
  };
}

const myCar = new Car('Hyundai', 'Sonata'); // new 연산자로 호출
myCar.getInfo(); // ⇨ This is a Hyundai Sonata. (this는 myCar 인스턴스를 가리킴)

const yourCar = new Car('Kia', 'K5');
yourCar.getInfo(); // ⇨ This is a K5 Kia.

4) call, apply, bind를 통한 명시적 바인딩 (Explicit Binding)

때로는 this의 값을 우리가 원하는 특정 객체로 강제로 지정해야 할 필요가 있습니다. 이때 Function.prototype에 있는 call(), apply(), bind() 메서드를 사용합니다.

  • call(thisArg, arg1, arg2, ...): 함수를 즉시 호출하면서 첫 번째 인수로 this 값을 지정합니다. 나머지 인수는 쉼표로 구분하여 전달합니다.
  • apply(thisArg, [argsArray]): 함수를 즉시 호출하면서 첫 번째 인수로 this 값을 지정합니다. call과 다르게 인수를 배열 형태로 전달합니다.
  • bind(thisArg, arg1, arg2, ...): 함수를 호출하지 않고, thisArgthis 값이 고정된 새로운 함수를 반환합니다. 이 반환된 함수를 나중에 호출할 수 있습니다.

const person1 = { name: 'Alice' };
const person2 = { name: 'Bob' };

function introduce(age, occupation) {
  console.log(`Hello, my name is ${this.name}. I am ${age} years old and a ${occupation}.`);
}

// call 사용: person1을 this로, 인수는 개별적으로
introduce.call(person1, 30, 'Engineer');   // ⇨ Hello, my name is Alice. I am 30 years old and a Engineer.

// apply 사용: person2를 this로, 인수는 배열로
introduce.apply(person2, [25, 'Designer']); // ⇨ Hello, my name is Bob. I am 25 years old and a Designer.

// bind 사용: person1을 this로 고정된 새로운 함수 생성
const introduceAlice = introduce.bind(person1, 30); // 30은 미리 고정할 수도 있음
introduceAlice('Student'); // ⇨ Hello, my name is Alice. I am 30 years old and a Student.
                           // bind 시점에 전달된 인수가 먼저 적용됩니다.

5) 화살표 함수 (Arrow Functions)에서의 this (Lexical this)

ES6에서 도입된 화살표 함수는 기존 함수와 this 바인딩 방식에서 큰 차이를 보입니다. 화살표 함수는 자신만의 this 바인딩을 생성하지 않습니다. 대신, 자신이 선언(정의)된 스코프의 this를 그대로 상속받습니다. 이를 "렉시컬 this"라고 부릅니다.

즉, 화살표 함수는 일반 함수와 달리 호출 시점에 this가 결정되는 것이 아니라, 정의 시점에 이미 상위 스코프의 this를 가져와 고정시킵니다.


const obj = {
  name: 'Developer',
  regularFunction: function() {
    console.log('Regular function this:', this.name); // ⇨ obj 객체를 가리킴 (메서드 호출)
  },
  arrowFunction: () => {
    // 화살표 함수는 자신의 스코프를 가지지 않으므로,
    // 이 obj의 상위 스코프(이 경우 전역 스코프)의 this를 상속받습니다.
    console.log('Arrow function this:', this.name); // ⇨ undefined (브라우저 window.name, Node.js global.name)
  },
  nested: {
    name: 'Nested Developer',
    // 이 화살표 함수의 상위 스코프는 obj.nested 이지만,
    // obj.nested 자체는 this를 가지지 않으므로 한 단계 더 위(obj)를 보고,
    // obj의 this는 전역이므로 결국 전역을 가리키게 됩니다.
    arrowFunctionInNested: () => {
      console.log('Nested arrow function this:', this.name); // ⇨ undefined
    },
    regularFunctionInNested: function() {
      // 이 일반 함수는 nested 객체의 메서드이므로 this는 nested 객체를 가리킵니다.
      // 이 안의 화살표 함수는 상위 스코프인 regularFunctionInNested의 this를 상속받습니다.
      const innerArrow = () => {
        console.log('Inner arrow function this:', this.name); // ⇨ Nested Developer
      };
      innerArrow();
    }
  }
};

obj.regularFunction();      // ⇨ Regular function this: Developer
obj.arrowFunction();        // ⇨ Arrow function this: undefined

obj.nested.arrowFunctionInNested(); // ⇨ Nested arrow function this: undefined
obj.nested.regularFunctionInNested(); // ⇨ Inner arrow function this: Nested Developer

화살표 함수의 이러한 특징은 특히 비동기 콜백 함수나 중첩된 함수 내에서 this 바인딩 문제가 발생할 때 유용하게 사용됩니다.


3. this, 언제 사용하면 좋을까? (실제 활용 사례)

this는 자바스크립트에서 객체 지향 프로그래밍과 동적인 코드 작성을 위해 필수적인 요소입니다. 다음 상황들에서 this가 유용하게 사용됩니다.

  1. 객체의 메서드 내부에서 해당 객체의 다른 속성/메서드에 접근할 때
    • 가장 흔하고 직관적인 사용법입니다. 객체의 메서드가 자신(객체)이 가진 데이터를 다룰 때 this를 사용합니다.

const user = {
  firstName: 'John',
  lastName: 'Doe',
  fullName: function() {
    // user 객체 내의 firstName과 lastName에 this로 접근
    return `${this.firstName} ${this.lastName}`;
  }
};
console.log(user.fullName()); // ⇨ John Doe
  1. 생성자 함수(또는 클래스)를 통해 새로운 인스턴스를 만들고, 해당 인스턴스의 속성을 초기화하거나 메서드를 정의할 때
    • new 키워드를 사용하여 객체를 생성할 때, this는 생성될 인스턴스 자신을 가리키며, 인스턴스별로 고유한 값을 설정할 수 있게 합니다.

// 생성자 함수
function Product(name, price) {
  this.name = name;      // 생성될 인스턴스의 name 속성
  this.price = price;    // 생성될 인스턴스의 price 속성
  this.displayInfo = function() {
    console.log(`${this.name}: $${this.price}`);
  };
}

const laptop = new Product('Laptop', 1200);
laptop.displayInfo(); // ⇨ Laptop: $1200

// 클래스 (ES6)
class Book {
  constructor(title, author) {
    this.title = title;
    this.author = author;
  }
  getDetails() {
    console.log(`${this.title} by ${this.author}`);
  }
}
const myBook = new Book('The Great Gatsby', 'F. Scott Fitzgerald');
myBook.getDetails(); // ⇨ The Great Gatsby by F. Scott Fitzgerald
  1. 이벤트 핸들러에서 이벤트를 발생시킨 DOM 요소에 접근할 때
    • DOM 요소를 클릭하거나 다른 이벤트가 발생했을 때, 해당 이벤트를 처리하는 함수 내에서 this는 기본적으로 이벤트를 발생시킨 바로 그 DOM 요소를 가리킵니다.

<button id="myButton">클릭하세요</button>

<script>
  document.getElementById('myButton').addEventListener('click', function() {
    // 여기서 this는 클릭된 <button> 요소를 가리킵니다.
    console.log(this.id); // ⇨ 'myButton' 출력
    this.textContent = '클릭됨!'; // 클릭된 버튼의 텍스트 변경
    this.style.backgroundColor = 'lightblue';
  });
</script>
  1. call, apply, bind를 사용하여 특정 함수를 특정 객체의 문맥(Context)으로 강제 실행해야 할 때
    • 특히, forEach, map과 같은 고차 함수(Higher-Order Function)의 콜백 함수 내에서 this 바인딩이 원하는 대로 되지 않을 때 유용합니다.

const calculator = {
  value: 10,
  add: function(num) {
    this.value += num;
  }
};

function processNumbers(arr) {
  // 일반 함수로 콜백을 사용하면 this는 window 또는 undefined를 가리킬 수 있습니다.
  // 따라서 calculator 객체의 add 메서드를 호출할 수 없습니다.
  // arr.forEach(function(num) {
  //   this.add(num); // 에러 발생 가능!
  // });

  // bind를 사용하여 콜백 함수의 this를 calculator 객체로 고정합니다.
  arr.forEach(function(num) {
    this.add(num);
  }.bind(calculator)); // this를 calculator로 명시적으로 바인딩
}

processNumbers([1, 2, 3]);
console.log(calculator.value); // ⇨ 16 (10 + 1 + 2 + 3)
  1. 화살표 함수를 사용하여 상위 스코프의 this를 그대로 유지하고자 할 때
    • 비동기 코드, 콜백 함수, 또는 클래스 내부에서 this가 갑자기 바뀌는 것을 방지하고자 할 때 화살표 함수가 빛을 발합니다.

class DataFetcher {
  constructor() {
    this.data = [];
  }

  fetchData() {
    console.log('Outer this (DataFetcher instance):', this); // ⇨ DataFetcher 객체

    // 일반 함수를 사용하면, setTimeout 내부의 this는 window (브라우저) 또는 Timer 객체 (Node.js)가 됩니다.
    // setTimeout(function() {
    //   console.log('Inside setTimeout (classic) this:', this); // ⇨ window 또는 Timer
    //   this.data.push('fetched data'); // ⇨ TypeError: this.data is not a function
    // }, 1000);

    // 화살표 함수를 사용하면, fetchData() 스코프의 this를 그대로 상속받습니다.
    setTimeout(() => {
      console.log('Inside setTimeout (arrow) this:', this); // ⇨ DataFetcher 객체
      this.data.push('fetched data'); // DataFetcher 인스턴스의 data 배열에 접근 가능
      console.log('Updated data:', this.data); // ⇨ Updated data: ["fetched data"]
    }, 1000);
  }
}

const fetcher = new DataFetcher();
fetcher.fetchData();

4. this 마스터를 위한 팁!

  • 가장 중요한 것은 this가 "어떻게" 호출되었는가를 파악하는 것입니다. 함수가 어디에 정의되었는지는 중요하지 않습니다.
  • 화살표 함수는 this 바인딩 문제를 해결하는 데 매우 유용합니다. 특히 콜백 함수나 클래스 메서드에서 this를 고정해야 할 때 우선적으로 고려해보세요.
  • 엄격 모드('use strict') 사용을 습관화하세요. this 바인딩 관련 실수를 미연에 방지하고 더 예측 가능한 코드를 작성할 수 있습니다.
  • 콘솔에 console.log(this)를 찍어보세요. this의 값이 헷갈릴 때는 항상 this가 무엇을 가리키는지 직접 확인하는 것이 가장 좋은 방법입니다.

마무리하며

자바스크립트의 this는 처음에는 혼란스러울 수 있지만, 오늘 설명드린 5가지 this 바인딩 규칙과 실제 활용 사례들을 반복해서 익히다 보면 어느새 자유자재로 다루게 될 겁니다. 특히 면접에서 this에 대한 질문은 단골 문제이니, 이 글의 내용을 충분히 숙지하시어 좋은 결과를 얻으시길 바랍니다!

궁금한 점이 있다면 언제든지 댓글로 남겨주세요!

#자바스크립트 #JavaScript #this #JS_this #this_바인딩 #화살표함수 #콜백함수 #프론트엔드 #개발공부 #코딩 #웹개발 #면접대비 #초보개발자

댓글 없음:

댓글 쓰기

댓글 폭탄 해결! 자바스크립트 댓글 접기/펼치기로 가독성 200% 높이는 법(Solve Comment Chaos: Elevate Readability 200% with JS Comment Folding/Unfolding)

내 웹사이트에 적용! 초간단 자바스크립트 댓글 펼치기/숨기기 튜토리얼 내 웹사이트에 적용! 초간단 자바스크립트 댓글 펼치기/숨기기 튜토...