본문 바로가기

화살표 함수와 this

화살표 함수의 축약 문법과 외부 this를 캡처하는 특성을 익히고 메서드·생성자처럼 피해야 할 위치를 구분합니다.

우리는 6장 자바스크립트 심화 I에서 this가 호출 방식에 따라 동적으로 바인딩된다는 점을 학습했습니다.

이 특성은 콜백/중첩 함수에서 혼란을 만들었고, bind, call, apply, self = this 같은 우회 패턴을 자주 요구했습니다.

ES2015(ES6)의 화살표 함수(Arrow Function)는 바깥 this를 유지해야 하는 콜백을 간결하게 표현합니다. 호출된 객체를 this로 받아야 하는 메서드에는 다른 선택이 필요합니다.

화살표 함수는 모던 자바스크립트 코드에서 거의 필수적으로 사용되는 기능이 되었으므로, 정확한 이해가 중요합니다.

이번 장에서는 화살표 함수 기본 문법, this 바인딩 차이, 일반 함수와의 사용 시나리오를 비교해 정리합니다.


화살표 함수의 기본 문법

화살표 함수는 function 키워드 대신 화살표 =>를 사용하여 함수를 정의하는 간결한 문법입니다.

기본 형태

매개변수 => 표현식 또는 { 함수 본문 }

// 기존 함수 표현식
const add = function(a, b) {
    return a + b;
};

// 화살표 함수 (기본 형태)
const addArrow = (a, b) => a + b; // 중괄호와 return이 생략됨
console.log(addArrow(2, 3)); // 결과: 5

다양한 형태

매개변수가 하나일 때: 괄호를 생략할 수 있습니다.

const greet = name => `Hello, ${name}!`;
console.log(greet("Alice")); // 결과: Hello, Alice!

매개변수가 없을 때: 빈 괄호를 사용해야 합니다.

const sayHi = () => "Hi there!";
console.log(sayHi()); // 결과: Hi there!

블록 본문을 쓸 때: 중괄호 안에서 값을 반환하려면 return을 명시합니다. 출력만 하는 함수처럼 반환값이 필요 없으면 생략할 수 있으며, 그 결과는 undefined입니다.

const calculateSum = (numbers) => {
    let sum = 0;
    for (let num of numbers) {
        sum += num;
    }
    return sum;
};
console.log(calculateSum([1, 2, 3, 4, 5])); // 결과: 15

객체 리터럴을 반환할 때: 소괄호로 감싸야 합니다.

(중괄호가 함수 본문으로 해석되는 것을 방지)

const createUser = (name, age) => ({ name: name, age: age });
console.log(createUser("Bob", 28)); // 결과: { name: 'Bob', age: 28 }

화살표 함수와 this 바인딩의 차이점

this 바인딩의 호출 규칙과 비교하면서, 이 함수가 사용할 this를 어느 실행 범위에서 가져오는지 추적합니다.

화살표 함수의 lexical this 경계

DECISION TREE · LEXICAL BINDINGS

화살표 함수의 lexical this 경계

화살표 함수는 호출 대상의 this를 새로 만들지 않고 바깥 값을 사용한다. 동적 this나 생성자 호출이 필요한지 먼저 확인한다.

함수 형태 선택과 바깥 this 참조두 판단은 함수 형태를 선택하는 순서입니다. 호출별 this나 자체 arguments 또는 new가 필요하면 일반 함수·메서드·생성 가능한 class를 사용합니다. 그렇지 않으면 바깥 this를 유지할지 판단합니다. 예는 화살표 함수, 아니오는 인수만 쓰는 콜백의 선택으로 끝납니다. 별도 캡처 영역의 화살표는 안의 화살표 함수에서 바깥 실행의 this로 향합니다. 모바일도 각 결과는 독립적으로 끝나며, 캡처 관계는 선택 순서와 구별합니다. DOM의 currentTarget 사용은 아래 설명에 연결됩니다. 호출마다 새 바인딩 필요?this / arguments 또는 new일반 함수 · 메서드생성 가능한 function / class바깥 this를 유지해야 하나?자체 arguments가 필요한가도 확인인수만 쓰는 콜백둘 다 가능 · 의도를 비교화살표 함수바깥 실행 범위의 this 사용예아니오아니오예캡처 관계: 객체 리터럴이 this 스코프를 만들지는 않음바깥 일반 함수 실행메서드 호출이면 해당 객체그 안의 화살표 함수자체 this 바인딩 없음참조 방향: 화살표 → 바깥 this · call로 교체 불가
함수 형태 선택과 바깥 this 참조두 판단은 함수 형태를 선택하는 순서입니다. 호출별 this나 자체 arguments 또는 new가 필요하면 일반 함수·메서드·생성 가능한 class를 사용합니다. 그렇지 않으면 바깥 this를 유지할지 판단합니다. 예는 화살표 함수, 아니오는 인수만 쓰는 콜백의 선택으로 끝납니다. 별도 캡처 영역의 화살표는 안의 화살표 함수에서 바깥 실행의 this로 향합니다. 모바일도 각 결과는 독립적으로 끝나며, 캡처 관계는 선택 순서와 구별합니다. DOM의 currentTarget 사용은 아래 설명에 연결됩니다.호출별 바인딩this / arguments또는 new가 필요한가?일반 함수·메서드new: 생성 가능function / class바깥 this 유지?예 / 아니오화살표 함수자체 this 없음인수만 쓰는 콜백둘 다 가능예아니오: 다음 판단예아니오: 별도 결과캡처 관계바깥 함수 실행그 실행의 this안의 화살표this를 새로 만들지 않음call로 this 교체 불가
  1. own binding이나 new가 필요한가?

    dynamic this, own arguments/new.target, 동적 this가 필요하면 일반 함수·메서드를 쓴다. new 호출에는 생성 가능한 function 또는 class가 필요하다.

  2. enclosing binding을 유지할 것인가?

    그렇다면 arrow를 쓴다. arrow에는 own binding이 없고 this/arguments/super/new.target을 바깥에서 resolve한다.

  3. parameter만 쓰는 callback인가?

    둘 다 의미가 맞다. lexical capture가 필요하지 않다면 arrow는 간결성 선택이다.

DOM listener

listener의 dynamic this가 필요하면 일반 함수, arrow를 쓰면 event.currentTarget을 명시한다.

class member

prototype method는 공유된다. field arrow는 this를 자동 보존하지만 instance마다 함수 객체를 만든다.

this는 호출 대상이 아닌 바깥에서 결정

화살표 함수는 자체 this를 갖지 않는다. 객체의 메서드로 호출하거나 call·bind로 호출 대상을 바꿔도 그 this를 새로 설정하지 않는다.

this를 고정하는 화살표 함수에는 자체 arguments와 생성자 기능이 없다. DOM의 event.currentTarget 사용과 클래스 필드의 인스턴스별 함수 비용까지 확인한다.

이것이 화살표 함수를 사용하는 가장 큰 이유이자 가장 중요한 특징입니다.

기존의 function 키워드로 선언된 함수는 호출 방식에 따라 this가 동적으로 결정됩니다.

하지만 화살표 함수는 this를 자신이 선언될 때의 상위 스코프(Lexical Scope)에서 가져옵니다.

즉, this가 정적으로 바인딩됩니다.

기존 함수의 this

const person = {
    name: "Minjun",
    // 1. 메서드로서 호출: `.` 앞의 객체(person)가 `this`가 됨
    sayHello: function() {
        console.log(`Hello, my name is ${this.name}.`);
    },
    // 2. 중첩 함수에서 `this` 문제 발생
    greetDelayed: function() {
        // 기존 함수의 `this` (person)를 유지하기 위한 꼼수: `self = this`
        const self = this;
        setTimeout(function() { // 브라우저 timer가 일반 콜백의 this로 window를 전달함 (strict 여부와 무관)
            console.log(`Delayed Hello, my name is ${self.name}. (using self)`);
            // console.log(`Delayed Hello, my name is ${this.name}. (using this - WRONG!)`);
        }, 500);
    }
};

person.sayHello(); // 결과: Hello, my name is Minjun.
person.greetDelayed(); // 결과: Delayed Hello, my name is Minjun. (using self)
                       // WRONG! 줄을 켜면 브라우저의 window.name을 읽으며 person.name을 읽지 않음

브라우저의 setTimeout은 일반 콜백을 호출할 때 window를 this 값으로 전달하므로, 이 콜백이 엄격 모드여도 this는 window입니다. 일반 함수를 직접 독립 호출할 때의 엄격 모드 규칙과 구분해야 합니다.

그래서 this.name은 제대로 된 값을 가져오지 못합니다.

화살표 함수의 this

화살표 함수는 자신이 정의될 때의 상위 스코프의 this를 그대로 가져옵니다.

절대 자기 자신만의 this 바인딩을 생성하지 않습니다.
const personArrow = {
    name: "Seulgi",
    sayHello: () => { // 브라우저 classic script의 최상위라면 this는 window; ES module이면 undefined
        console.log(`Hello, my name is ${this.name}. (WRONG! - window or undefined)`);
    },
    greetDelayed: function() { // 이 함수는 일반 함수이므로 `this`는 `personArrow`를 가리킴
        // 화살표 함수는 자신의 상위 스코프(greetDelayed)의 `this`를 가져옴
        setTimeout(() => {
            console.log(`Delayed Hello, my name is ${this.name}. (using arrow function)`);
        }, 500);
    }
};

personArrow.sayHello(); // classic script에서 window.name이 빈 문자열이면 Hello, my name is . (WRONG! - window or undefined)
                        // ES module에서는 this.name 접근이 TypeError; 객체 리터럴은 자체 this 스코프가 아님

personArrow.greetDelayed(); // 결과: Delayed Hello, my name is Seulgi. (using arrow function)

위 블록을 ES module로 실행하면 첫 sayHello()에서 예외가 발생해 뒤의 greetDelayed()에 도달하지 못합니다. 지연 콜백만 비교하려면 첫 호출을 주석 처리하거나 try/catch로 잡고 계속 실행하세요. classic script에서 읽는 window.name도 페이지 환경의 별도 값입니다.

personArrow.greetDelayed 내부의 setTimeout 콜백은 화살표 함수로 정의되었습니다.

이 화살표 함수는 자신이 선언된 greetDelayed 함수 스코프의 this 값을 그대로 가져옵니다.

greetDelayed는 personArrow 객체의 메서드로 호출되었으므로, greetDelayed 내부의 this는 personArrow를 가리킵니다.

따라서 화살표 함수 내부의 this도 personArrow를 가리키게 되어 원하는 대로 동작합니다.

핵심: 화살표 함수는 this 바인딩에 대한 문제를 해결해주는 강력한 도구입니다.

특히 콜백 함수에서 this 컨텍스트를 유지해야 할 때 유용합니다.


화살표 함수를 사용하지 말아야 할 경우

화살표 함수는 매우 편리하지만, 모든 상황에서 기존 함수를 대체할 수 있는 것은 아닙니다.

this 바인딩 방식의 차이 때문에 특정 상황에서는 기존 함수를 사용해야 합니다.

객체의 메서드를 정의할 때 (최상위 메서드): 객체 리터럴에 넣은 화살표 함수는 객체 자체가 아니라 바깥 this를 사용합니다. 브라우저 classic script의 최상위에서는 window, ES module의 최상위에서는 undefined입니다.

const myObject = {
    value: 10,
    // BAD: 화살표 함수는 바깥 this 사용 (classic script: window, ES module: undefined)
    getValueArrow: () => {
        console.log(this.value); // classic script: window.value; ES module: TypeError
    },
    // GOOD: 일반 함수는 `.` 호출 시 myObject를 this로 바인딩
    getValueNormal: function() {
        console.log(this.value); // 10
    }
};

myObject.getValueArrow();
myObject.getValueNormal();

위 getValueArrow()도 ES module에서는 예외로 다음 호출을 막습니다. 각 호출을 따로 비교하세요. 호출된 객체를 this로 받아야 하는 메서드에는 일반 함수나 메서드 축약형 method() { ... }를 사용합니다. 바깥 this를 유지하려는 콜백에는 화살표 함수가 맞을 수 있습니다.

생성자 함수 (Constructor): 화살표 함수는 prototype 프로퍼티를 가지지 않으며, new 키워드와 함께 호출될 수 없습니다.

new와 함께 호출하면 TypeError가 발생합니다.

// BAD: 화살표 함수는 생성자로 사용할 수 없음
// const MyClass = () => { this.value = 1; };
// const instance = new MyClass(); // TypeError: MyClass is not a constructor

// GOOD: 생성자는 일반 함수 또는 class 키워드로 정의
function MyClass(value) {
    this.value = value;
}
const instance = new MyClass(10);

arguments 객체에 접근해야 할 때: 화살표 함수는 arguments 객체를 바인딩하지 않습니다.

대신 상위 스코프의 arguments를 참조합니다.

기존 함수는 자신에게 전달된 인자들에 arguments 객체를 통해 접근할 수 있습니다.

(ES6에서는 ...rest 매개변수 사용이 더 권장되긴 합니다.)

function sumAllNormal() {
    console.log(arguments); // 1, 2, 3을 담은 arguments 객체 (배열은 아님)
    return Array.from(arguments).reduce((a, b) => a + b);
}
console.log(sumAllNormal(1, 2, 3)); // 결과: 6

const sumAllArrow = () => {
    // console.log(arguments); // ReferenceError: arguments is not defined (상위 스코프에 arguments 없으면)
    // 화살표 함수에서는 ...rest 매개변수를 사용해야 함
    // return [...arguments].reduce((a, b) => a + b); // Error!
};
// console.log(sumAllArrow(1, 2, 3));

addEventListener 등 이벤트 핸들러: 일반 함수를 DOM 이벤트 리스너로 등록하면 this는 그 리스너가 등록된 event.currentTarget을 가리킵니다. 버블링 중에는 이벤트가 시작된 event.target과 다를 수 있습니다.

화살표 함수를 사용하면 이 this 바인딩이 깨져 의도치 않은 결과를 초래할 수 있습니다.

<button id="myButton">클릭하세요</button>
<script>
    const button = document.getElementById('myButton');

    // GOOD: 일반 함수는 this가 버튼 요소 자체를 가리킴
    button.addEventListener('click', function() {
        console.log("일반 함수 this:", this.id); // 결과: myButton
    });

    // BAD: 화살표 함수는 this가 상위 스코프 (window)를 가리킴
    button.addEventListener('click', () => {
        console.log("화살표 함수 this:", this.id); // 결과: undefined (window.id는 없음)
    });
</script>

위 sumAllNormal은 인자가 하나 이상일 때의 예제입니다. 빈 배열의 reduce에 초기값이 없으면 TypeError가 발생합니다. 빈 호출도 합계 0으로 정하려면 const sumAll = (...values) => values.reduce((total, value) => total + value, 0);처럼 정책을 정합니다.

DOM 이벤트의 등록 대상을 읽으려면 화살표 함수에서도 event.currentTarget을 사용할 수 있습니다. this를 사용하는 원문과 구별해 button.addEventListener("click", event => console.log(event.currentTarget.id));를 같은 버튼에 별도로 등록하면 클릭할 때 myButton을 읽습니다. 이 값은 리스너가 실행되는 동안 읽습니다.


예시 화살표 함수가 바깥 this를 유지한다는 것은 그 객체의 프로퍼티 값까지 복사한다는 뜻은 아닙니다.

const source = {
    label: "원본",
    makeReader() {
        return () => this.label;
    }
};
const readLabel = source.makeReader();
console.log(readLabel.call({ label: "다른 객체" }));
source.label = "갱신";
console.log(readLabel());

출력은 원본, 갱신입니다. makeReader()를 호출한 source가 바깥 this입니다. call로 다른 객체를 제공해도 화살표 함수의 this는 바뀌지 않으며, 매번 그 객체의 현재 label을 읽습니다.

스스로 확인하기 반환하는 함수를 return function () { return this.label; };로 바꾼 뒤 첫 호출만 실행하면 무엇을 읽나요? 화살표와 달리 첫 호출의 call 인수가 왜 영향을 주는지 설명해 보세요.

풀이 확인

다른 객체를 읽습니다. 일반 함수의 이번 호출에는 call이 제공한 객체가 this입니다. 이 문제는 첫 호출만 비교하므로, 두 번째 독립 호출의 엄격 모드·비엄격 모드 차이와 섞지 않습니다.

생성자 기능과 바깥 바인딩의 규칙은 MDN 화살표 함수 설명에 정리되어 있습니다.

화살표 함수·this 정리

정리 단계에서는 문법 간결성보다 this 바인딩이 코드 유지보수성에 주는 효과를 우선 확인합니다.

이번 장에서는 ES2015(ES6)의 핵심 기능인 화살표 함수(Arrow Function)의 간결한 문법과, 특히 개발자들을 혼란스럽게 했던 this 바인딩 문제를 어떻게 해결하는지에 대해 심도 있게 학습했습니다.

화살표 함수는 선언 위치의 상위 스코프 this를 그대로 사용하는 렉시컬 this 바인딩을 갖습니다.

콜백이나 중첩 함수에서 this가 바뀌면 안 되는 경우에 이 특성이 도움이 됩니다.

하지만 화살표 함수가 모든 상황에서 기존 함수의 대안이 될 수는 없다는 점도 중요하게 다루었습니다.

객체의 메서드, 생성자 함수, arguments 객체에 접근해야 할 때, 그리고 이벤트 리스너의 this 바인딩이 중요한 경우에는 여전히 기존 함수(또는 class 문법의 메서드)를 사용해야 합니다.

화살표 함수는 간결하지만 생성자 함수나 자체 this가 필요한 메서드에는 맞지 않습니다.

짧은 콜백에는 적합하고, 객체 메서드의 this가 중요한 경우에는 일반 함수와 비교해 선택해야 합니다.

마지막으로 함수 형태를 고를 때는 짧은 문법보다 this가 어디서 정해져야 하는지를 먼저 묻는 습관이 중요합니다.