본문 바로가기
language/javascript

함수 선언 vs 표현식, this 바인딩 완벽 정리 (call, apply, bind 포함)

by 죄니안죄니 2025. 4. 21.

들어가며

JavaScript의 함수는 선언 방식에 따라 동작 방식이 다르고, this 바인딩도 호출 방식에 따라 동적으로 결정됩니다.
이러한 차이를 정확히 이해하면 콜백, 메서드, 이벤트 핸들러 등에서 발생하는 this 관련 버그를 방지할 수 있습니다.

이 글에서는 함수 선언 vs 표현식 차이부터 this의 동적 바인딩 원리, 그리고 call, apply, bind를 활용한 명시적 바인딩까지 완벽 정리합니다.

 

1. 함수 선언 vs 함수 표현식

✅ 함수 선언 (Function Declaration)

function greet() {
  console.log("Hello");
}
  • 호이스팅 O: 선언 전에도 사용 가능
  • 스코프 최상단으로 끌어올려짐

✅ 함수 표현식 (Function Expression)

const greet = function () {
  console.log("Hi");
};
  • 호이스팅 X: 선언 이후에만 사용 가능
  • const, let, var 등 변수에 담기는 함수

✅ 화살표 함수 (Arrow Function)

const greet = () => console.log("Arrow!");
  • this 바인딩이 없음 → 상위 스코프의 this를 계승 (렉시컬 this) (일반 함수는 호출한 객체에 this가 바인딩되지만, 화살표함수는 선언 당시 상위 스코프의 this를 렉시컬하게 바인딩함)
  • 메서드(객체에 속한 함수)로는 사용하지 않는 것이 일반적(일반 함수 표현식_function키워드 혹은 function생략한 일반함수방식_으로 만든 함수가 메서드를 정의하는 데 적합함. 일반메서드에서는 this바인딩 없어서 부적합. 일반함수로 메서드를 정의하는 것이 안전 )
const obj = {
  name: "Hoon",
  sayHi: function () {
    console.log(`Hi, ${this.name}`);
  },
};

obj.sayHi(); // Hi, Hoon → sayHi는 일반 함수이므로 this는 obj를 가리킴.

const obj2 = {
  name: "Hoon",
  sayHi: () => {
    console.log(`Hi, ${this.name}`);
  },
};

obj2.sayHi(); // Hi, undefined (❌) → this는 obj가 아닌, **상위 스코프의 this (보통 window 또는 undefined)**를 가리켜요.
  • 화살표함수는 콜백 클로저 등 함수 내부에서 this 공유할 때 적합. 
function Timer() {
  this.seconds = 0;
  setInterval(() => {
    this.seconds++;
    console.log(this.seconds);
  }, 1000);
}

new Timer(); // this는 Timer 인스턴스를 가리킴. 정의시점의 상위 스코프의 this를 받으니까

// 일반함수의 경우, 호출한 애를 가르키는데 시간이 지난 후 실행하는 경우 실행주체가 없게 됨
function Timer() {
  this.seconds = 0;

  setInterval(function () {
    this.seconds++;  // ❌ 예상한 this가 아님! 에러나거나 NaN 출력 (호출컨텍스트 따라감)
    console.log(this.seconds);
  }, 1000);
}

new Timer();

// 일반함수로 할 경우, 한 번 우회해서 구현은 되지만 불편하고 가독성 떨어짐
function Timer() {
  this.seconds = 0;
  const self = this; // 💡 Trick
  setInterval(function () {
    self.seconds++;  // 우회해서 해결
    console.log(self.seconds);
  }, 1000);
}
// 방법2 bind사용
function Timer() {
  this.seconds = 0;
  setInterval(function () {
    this.seconds++;
    console.log(this.seconds);
  }.bind(this), 1000);
}

 

 

 

 


2. this의 결정 방식

호출 방식 this 값
일반 함수 호출 전역(window) 또는 undefined (strict mode)
메서드 호출 (obj.fn()) obj
생성자 호출 (new) 새로 생성된 인스턴스
화살표 함수 외부 스코프의 this (고정)
call/apply/bind 사용 명시적으로 지정한 값
 

3. 예시로 비교해보기

일반 함수

function showThis() {
  console.log(this);
}
showThis(); // 브라우저: window / strict: undefined

객체 메서드

const user = {
  name: "Tom",
  sayHello() {
    console.log(this.name); // Tom
  },
};
user.sayHello();

화살표 함수 내부의 this

const user = {
  name: "Alice",
  sayHi: () => {
    console.log(this.name); // undefined (상위 스코프는 window)
  },
};

→ 화살표 함수는 메서드로 사용 시 this가 예상과 다르게 동작함

 

4. call, apply, bind로 명시적 바인딩

✅ call

function greet() {
  console.log(this.name);
}
const user = { name: "Jane" };
greet.call(user); // Jane

✅ apply (call과 유사하나 인자를 배열로 받음)

function add(a, b) {
  return a + b;
}
console.log(add.apply(null, [2, 3])); // 5

✅ bind (새로운 함수 반환)

const person = {
  name: "Lee",
};

function say() {
  console.log(this.name);
}

const bound = say.bind(person);
bound(); // Lee

→ bind는 콜백으로 넘길 때 유용 (예: 이벤트 핸들러 내부에서 this 유지)

 

5. this 바인딩 문제 실전 예시

setTimeout에서 this가 엉킬 때

const obj = {
  count: 0,
  start() {
    setTimeout(function () {
      console.log(this.count); // undefined or window.count
    }, 1000);
  },
};

해결 1: 화살표 함수로

setTimeout(() => {
  console.log(this.count); // obj.count
}, 1000);

해결 2: bind로 고정

setTimeout(function () {
  console.log(this.count);
}.bind(this), 1000);

마치며

JavaScript 함수는 어떻게 선언되었는지, 어떻게 호출되었는지에 따라 this가 달라지는 동적 언어입니다.
특히 call, apply, bind는 this를 정확히 컨트롤할 수 있는 유용한 도구이며,
화살표 함수의 렉시컬 this와의 차이를 꼭 구분해서 써야 합니다.

📂 다음 글에서는:

  • new와 생성자 함수 내부 this 구조
  • 클래스 메서드에서의 this 고정 문제
  • bind vs 화살표 함수의 실제 차이

등을 이어서 다루겠습니다.

댓글