icon
8장 : 최신 ECMAScript 기능

화살표 함수와 this

우리는 6장 "자바스크립트 심화 I"의 this 장에서 자바스크립트의 this 키워드가 함수가 어떻게 호출되었는지에 따라 동적으로 바인딩되는 복잡한 특성을 가지고 있음을 학습했습니다. 이러한 this의 예측 불가능한 동작은 많은 개발자들에게 혼란을 주었고, 특히 콜백 함수나 중첩된 함수에서 this 값을 유지하기 위해 bind, call, apply 또는 self = this와 같은 번거로운 패턴을 사용해야 했습니다.

ES2015(ES6)에서 도입된 화살표 함수(Arrow Function) 는 이러한 this 바인딩 문제를 근본적으로 해결하는 동시에, 함수를 선언하는 문법을 훨씬 간결하게 만들어 주었습니다. 화살표 함수는 모던 자바스크립트 코드에서 거의 필수적으로 사용되는 기능이 되었으므로, 정확한 이해가 중요합니다.

이번 장에서는 화살표 함수의 기본 문법부터 시작하여, 가장 중요한 특징인 this 바인딩 방식의 차이점, 그리고 기존 함수와 화살표 함수의 사용 시나리오를 비교하며 깊이 있게 알아보겠습니다.


화살표 함수의 기본 문법

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

기본 형태

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

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

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

다양한 형태

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

    const greet = name => `Hello, ${name}!`;
    console.log(greet("Alice")); // 결과: Hello, Alice!
  2. 매개변수가 없을 때: 빈 괄호를 사용해야 합니다.

    const sayHi = () => "Hi there!";
    console.log(sayHi()); // 결과: Hi there!
  3. 함수 본문이 여러 줄일 때: 중괄호를 사용하고 명시적으로 return 키워드를 사용해야 합니다.

    const calculateSum = (numbers) => {
        let sum = 0;
        for (let num of numbers) {
            sum += num;
        }
        return sum;
    };
    console.log(calculateSum([1, 2, 3, 4, 5])); // 결과: 15
  4. 객체 리터럴을 반환할 때: 소괄호로 감싸야 합니다. (중괄호가 함수 본문으로 해석되는 것을 방지)

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

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

이것이 화살표 함수를 사용하는 가장 큰 이유이자 가장 중요한 특징입니다. 기존의 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() { // setTimeout의 콜백 함수는 일반 함수이므로 `this`가 전역 객체(window)를 가리킴
            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! 줄은 undefined가 출력되거나 에러 발생

위 예시에서 setTimeout 내부의 function() { ... }은 일반 함수이므로 thiswindow (브라우저) 또는 undefined (엄격 모드)를 가리킵니다. 그래서 this.name은 제대로 된 값을 가져오지 못합니다.

화살표 함수의 this

화살표 함수는 자신이 정의될 때의 상위 스코프의 this를 그대로 가져옵니다. 절대 자기 자신만의 this 바인딩을 생성하지 않습니다.

const personArrow = {
    name: "Seulgi",
    sayHello: () => { // 화살표 함수가 최상위 스코프에 정의되면 `this`는 `window`를 가리킴
        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(); // 결과: Hello, my name is . (window.name이 없거나 undefined)
                        // 주의: 객체의 메서드로 최상위 화살표 함수 사용은 권장되지 않음

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

personArrow.greetDelayed 내부의 setTimeout 콜백은 화살표 함수로 정의되었습니다. 이 화살표 함수는 자신이 선언된 greetDelayed 함수 스코프의 this 값을 그대로 가져옵니다. greetDelayedpersonArrow 객체의 메서드로 호출되었으므로, greetDelayed 내부의 thispersonArrow를 가리킵니다. 따라서 화살표 함수 내부의 thispersonArrow를 가리키게 되어 원하는 대로 동작합니다.

핵심: 화살표 함수는 this 바인딩에 대한 문제를 해결해주는 강력한 도구입니다. 특히 콜백 함수에서 this 컨텍스트를 유지해야 할 때 유용합니다.


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

화살표 함수는 매우 편리하지만, 모든 상황에서 기존 함수를 대체할 수 있는 것은 아닙니다. this 바인딩 방식의 차이 때문에 특정 상황에서는 기존 함수를 사용해야 합니다.

  1. 객체의 메서드를 정의할 때 (최상위 메서드): 위 personArrow.sayHello() 예시에서 보았듯이, 객체 리터럴의 최상위 메서드를 화살표 함수로 정의하면 this가 객체 자신이 아닌 전역 객체를 가리키게 되어 의도치 않은 결과를 초래합니다.

    const myObject = {
        value: 10,
        // BAD: 화살표 함수는 this가 상위 스코프 (window/global)를 가리킴
        getValueArrow: () => {
            console.log(this.value); // undefined (또는 window.value)
        },
        // GOOD: 일반 함수는 `.` 호출 시 myObject를 this로 바인딩
        getValueNormal: function() {
            console.log(this.value); // 10
        }
    };
    
    myObject.getValueArrow();
    myObject.getValueNormal();

    객체의 메서드는 항상 일반 함수(또는 ES6의 메서드 축약형 method() { ... })로 정의해야 합니다.

  2. 생성자 함수 (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);
  3. arguments 객체에 접근해야 할 때: 화살표 함수는 arguments 객체를 바인딩하지 않습니다. 대신 상위 스코프의 arguments를 참조합니다. 기존 함수는 자신에게 전달된 인자들을 arguments 객체로 접근할 수 있습니다. (ES6에서는 ...rest 매개변수 사용이 더 권장되긴 합니다.)

    function sumAllNormal() {
        console.log(arguments); // [1, 2, 3]
        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));
  4. addEventListener 등 이벤트 핸들러: 특정 DOM 요소에 이벤트 리스너를 붙일 때, this는 기본적으로 이벤트를 발생시킨 요소를 가리킵니다. 화살표 함수를 사용하면 이 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>

마무리하며

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

여러분은 화살표 함수가 자신이 선언될 때의 상위 스코프 this를 그대로 가져오는 렉시컬 this 바인딩 특성을 이해했습니다. 이는 콜백 함수와 중첩 함수에서 this 컨텍스트를 유지해야 할 때 bindself = this와 같은 번거로운 작업 없이 코드를 더욱 간결하고 예측 가능하게 작성할 수 있도록 해줍니다.

하지만 화살표 함수가 모든 상황에서 기존 함수의 대안이 될 수는 없다는 점도 중요하게 다루었습니다. 객체의 메서드, 생성자 함수, arguments 객체에 접근해야 할 때, 그리고 이벤트 리스너의 this 바인딩이 중요한 경우에는 여전히 기존 함수(또는 class 문법의 메서드)를 사용해야 합니다.

화살표 함수는 모던 자바스크립트 코드에서 거의 모든 곳에서 사용될 정도로 보편화되었습니다. 이 장의 내용을 통해 여러분은 화살표 함수의 강력함과 한계를 명확히 이해하고, 코드의 가독성과 안정성을 높이는 데 올바르게 활용할 수 있을 것입니다.