화살표 함수와 this

Sheryl Yun·2022년 9월 22일
0
post-thumbnail

대망의 화살표 함수에서의 this이다. 이 개념도 일반 함수에서의 this와 매번 헷갈리는데 책을 읽으면서 이번에 확실히 정리해본다.

화살표 함수는 익명 함수

화살표 함수는 이름이 없는 익명 함수이다.
참조할 함수명이 필요하다면 함수 표현식처럼 사용하면 된다.

const greeting = (name) => `hello ${name}`;

greeting("Tom"); // 함수 표현식: 변수를 함수처럼 호출하여 사용

화살표 함수의 this는 상위 스코프

일반 function 함수와 달리, 화살표 함수의 this가 가리키는 대상은 무조건 상위 스코프로 고정되어 있다.

이에 관해 일반 함수와 비교한 내용을 가져와보았다. 출처

❗️일반 함수

아래는 일반 함수에서 this가 바인딩 되는 상황이다.

  1. 함수 실행 시 - 전역(window)객체를 가리킨다. (단독 바인딩)
  2. 메소드 실행 시 - 메소드를 소유하고 있는 객체를 가리킨다. (암시적 바인딩)
  3. 생성자 실행 시 - 새롭게 만들어진 객체를 가리킨다. (new 바인딩)

일반 함수는 함수를 호출할 때 함수가 어떻게 호출되었는지에 따라 this에 바인딩할 객체가 동적으로 결정된다.

❗️화살표 함수

화살표 함수는 선언할 때 this에 바인딩할 객체가 정적으로 결정된다.
화살표 함수의 this는 언제나 상위 스코프의 this를 가리킨다.(Lexical this)
call, apply, bind 메소드를 사용하여 this를 변경할 수 없다. (명시적 바인딩 불가)

예제

<div class="box open">
	This is a box
</div>
.opening {
	width: 100px;
    height: 50px;
	background-color: red;
}
const box = document.querySelector('.box');

box.addEventListener('click', function() {
	this.classList.toggle('opening');    
    
    setTimeout(function() {
    	this.classList.toggle('opening');
    }, 500);
});

이 예제는 CodePen에 쳐서 직접 실습해보았다.

비동기 메서드를 다룰 때 addEventListener의 경우, 이벤트 리스너가 붙어 있는 객체(여기서는 box)에 this가 바인딩된다는 점은 이미 알고 있었다.
따라서 this.classList.toggle('opening') 의 this는 box에 바인딩될 것으로 추측했고, 실제로 클릭 이벤트를 발생시켜보니 box에 toggle이 먹어서 빨간색으로 잘 바뀌었다.


하지만 setTimeout 부분이 동작하지 않았다. 클릭을 하고 나서 빨간 박스로 변한 뒤 0.5초 뒤에 박스가 다시 사라져야 하는데 이 두 번째 동작이 발생하지 않았다.

setTimeout 동작 원리에 대한 나의 추측

아래는 위의 현상에 대해 내가 추측한 내용이다.

  • setTimeout은 비동기 메서드여서 실행되고 나면 setTimeout 내부의 실행문이 타이머와 함께 콜백 큐에서 대기한다. 이후 나머지 코드들이 모두 실행되고 나면 타이머의 시간이 지난 뒤 대기하고 있던 실행문이 호출 스택에 올라가 동작한다.

  • 이 때 실행문인 function 함수가 함수 선언문으로서 호이스팅이 되면서 전역 공간에 배치된다. 이로 인해 function 함수의 this가 전역 객체인 window 객체에 바인딩된다.


호이스팅 부분은 내 추측이다. (책에는 동작 원리에 대한 설명이 없음)

문제 해결

아무튼 setTimeout의 function 함수를 화살표 함수로 바꾸면 코드가 의도대로 동작한다.

const box = document.querySelector('.box');

box.addEventListener('click', function() { 
	this.classList.toggle('opening');    
    
    setTimeout(() => { // 여기가 바뀜!
    	this.classList.toggle('opening');
    }, 500);
});

화살표 함수는 호이스팅이 되지 않으며,
선언될 때의 자신의 상위 스코프를 '기억'해두었다가 무조건 그 상위 스코프에 바인딩된다.

그래서 콜백 큐에 들어가있던 화살표 함수가 전역 객체가 아닌 자신의 원래 상위 스코프였던 addEventListener의 function 함수의 환경(즉, this가 box였던 환경)을 계속 기억하고 있어서 정상적으로 box 객체에 바인딩된 것이다.

화살표 함수가 좋지 않은 경우

보통 신문법이라고 하면 이전 문법의 단점을 수정해서 나온다. 그래서 ES6 신문법인 화살표 함수도 function 함수보다 무조건 더 좋을 줄 알았다. 하지만 화살표 함수를 쓸 때 오히려 더 안 좋거나 유의해야 하는 경우가 몇 가지 있다.

1. 이벤트 리스너에서 사용할 때

앞에 나온 setTimeout 예제에서 addEventListener의 함수는 일반 function 함수였다. 그래서 호출할 때 this가 동적으로 결정되었다. (=> 이벤트 리스너가 붙은 객체에 바인딩)

하지만 이와 달리 화살표 함수선언될 때의 '상위 스코프'를 기억하고 그 상위 스코프에 무조건 바인딩되기 때문에 아래 예제의 경우 이벤트 리스너가 붙은 객체(button)가 아닌 전역 객체 window에 바인딩이 돼 버린다.

const button = document.querySelector('btn');

button.addEventListener('click', () => {
	this.classList.toggle('on');
});

상황에 따라 다르겠지만, addEventListener를 쓸 때는 화살표 함수가 아닌 function 함수를 쓰는 게 낫다고 기억하고 있으면 될 것 같다.

2. 객체의 메서드로 사용할 때

객체의 메서드로 화살표 함수를 사용할 때도 주의해야 한다.

function 대신 화살표 함수를 사용하면, function이 갖고 있던 암시적 바인딩 기능이 제대로 동작하지 않기 때문이다.

const person1 = {
	age: 10,
    grow: function() {
    	this.age++;
        console.log(this.age);
    },
};

person1.grow(); // 11

---------------------------------------------------------------------

const person2 = {
	age: 10,
    grow: () => {
        this.age++;
        console.log(this.age);
    },
};

person2.grow();

객체 블록은 스코프로 간주되지 않는다.
따라서 person2의 화살표 함수의 this는 자연스럽게 전역 객체 window를 찾아 바인딩된다.

3. arguments 키워드 사용 시

이 부분을 공부하며 처음 알았는데 자바스크립트에는 arguments라는 키워드(예약어)가 있다. arguments는 따로 함수의 소괄호 안에 명시하지 않아도 알아서 인자들을 배열로 받아주는 키워드이다.

function example() {
	console.log(arguments[0]); // 배열처럼 각 인자 값에 인덱스로 접근한다
}

example(1, 2, 3); // 1

주의할 점: function을 화살표 함수로 바꾸면 더 이상 arguments를 예약어로 인식하지 못하게 된다.

const showWinner = () => {
	const winner = arguments[0];
    console.log(`${winner} was the winner`);
};

showWinner('Usain Bolt', 'Justin Gatlin', 'Asafa Powell');
// ReferenceError: arguments is not defined 

ReferenceError가 발생하는 걸 보니 화살표 함수에서는 arguments를 키워드가 아닌 일반 변수로 인식하는 듯 하다.


따라서 arguments 개념을 사용하려면 다음 2가지 방법이 있다.

  1. 일반 함수 function에 사용
  2. 화살표 함수를 굳이 쓰려 한다면 함수의 소괄호 안에 인자를 명시 (spread 문법 사용)

다음은 화살표 함수에서 arguments 개념을 사용한 예제이다.

const showWinner = (...args) => { // args라는 명시적 변수로 인자를 받음 (spread 사용)
	const winner = args[0]; // 받아온 args를 배열처럼 사용
    console.log(`${winner} was the winner`);
};

showWinner('Usain Bolt', 'Justin Gatlin', 'Asafa Powell');

다음은 일반 function 함수에서 arguments를 사용한 예제이다.

const showWinner = () => { // 명시적 인자가 없음
	const winner = arguments[0]; // arguments를 키워드로 사용 가능
    console.log(`${winner} was the winner`);
};

showWinner('Usain Bolt', 'Justin Gatlin', 'Asafa Powell');
profile
데이터 분석가 준비 중입니다 (티스토리에 기록: https://cherylog.tistory.com/)

0개의 댓글