[JS] 화살표 함수 (Arrow Function)의 this, arguments에 관하여...

Shy·2024년 5월 13일
0

JavaScript

목록 보기
1/5

화살표 함수

화살표 함수(arrow function)는 JavaScript에서 도입된 새로운 함수 선언 방법이다. ES6(ECMAScript 2015)에서 처음 도입되었으며, 기존의 함수 선언 방식에 비해 더 간결하고 가독성이 높은 코드를 작성할 수 있게 해준다. 다음은 화살표 함수에 대한 주요 특징과 사용 예제이다.

특징

  • 간결한 문법
    • 화살표 함수는 function 키워드를 사용할 필요가 없으며, => 기호를 사용하여 정의된다.
  • 🌟⭐️this 바인딩⭐️🌟
    • 화살표 함수는 자신만의 this 컨텍스트를 가지지 않고, 자신을 감싸고 있는 외부 함수 또는 객체의 this를 그대로 사용합니다. 이로 인해 this와 관련된 오류를 줄일 수 있다.
  • arguments 객체 없음
    • 화살표 함수는 arguments 객체를 가지지 않습니다. 대신 필요하다면 나머지 매개변수(rest parameters)를 사용할 수 있다.
  • 생성자 함수로 사용 불가
    • 화살표 함수는 생성자 함수로 사용할 수 없으며, new 키워드와 함께 사용할 수 없다.

화살표 함수의 예제

// 매개변수가 하나인 경우
const square = x => x * 2;
console.log(square(5)); // 10

// 매개변수가 여러개인 경우
const add = (a, b) => a + b;
console.log(add(2, 3)); // 5

// 매개변수가 없는 경우
const greet = () => 'Hello, World!';
console.log(greet()); // 'Hello, World!'

// 블록바디 사용
// 화살표 함수의 함수 본문이 여러 줄인 경우, 중괄호 {}를 사용하고 return 키워드를 명시해야 한다.
const multiply = (a, b) => {
    const result = a * b;
    return result;
};
console.log(multiply(3, 4)); // 12


// 객체 반환
// 객체를 반환할 때는 객체 리터럴을 괄호로 감싸야 한다.
const getPerson = (name, age) => ({ name, age });
console.log(getPerson('Alice', 25)); // { name: 'Alice', age: 25 }
// 일반 함수로 바꾸면 다음과 같다.
function getPerson(name, age) {
    return { name, age };
}

화살표 함수의 this 바인딩

JavaScript에서 this 키워드는 함수가 호출된 방식에 따라 달라지는 동적 바인딩이다. 그러나 화살표 함수는 자신만의 this 바인딩을 가지지 않고, 외부 컨텍스트의 this를 상속받는다. 이는 화살표 함수의 중요한 특징 중 하나로, 특히 콜백 함수나 비동기 코드에서 유용하다.

this의 동작 방식

출처 화살표 함수와 this를 자세히 설명해주는 벨로그!! 이곳의 설명을 참조하고 제 나름대로 정리해봤습니다.

Java를 사용하던 나로서는, JS의 this를 접했을 때, 혼란스러움이 생겼다.

function Person(name) {
    this.name = name;
}

const person1 = new Person('Alice');
console.log(person1.name); // Alice

// 일반 함수의 `this`
function sayName() {
    console.log(this.name);
}

const person2 = {
    name: 'Bob',
    sayName: sayName
};

person2.sayName(); // Bob

const unboundSayName = person2.sayName;
unboundSayName(); // undefined (또는 엄격 모드에서는 에러)

unboundSayNameundefined로 출력되는 것일까?

이는 바로 this의 컨텍스트 의존성 때문이다.

JavaScript에서 함수 내의 this 키워드는 함수가 어떻게 호출되었는지에 따라 다르게 바인딩된다. 일반 함수(화살표 함수 제외)의 경우, this는 함수를 호출하는 객체에 바인딩된다. 하지만 함수를 다른 변수에 할당할 경우 그 함수를 담은 변수는 원래 객체와의 연결이 끊어진 상태로 존재하게 된다. 따라서 그 변수를 통해 함수를 호출하면 this는 전역 객체(브라우저에서는 window, Node.js에서는 global)나, 엄격 모드('strict mode')에서는 undefined에 바인딩된다.

JS의 this는 상황에 따라 다르게 바인딩 되는데, 대표적으로 this에 바인딩되는 값들은 아래와 같다.

  1. 전역 공간의 this: 전역 객체
  2. 메소드 호출 시 메소드 내부의 this: 해당 메소드를 호출한 객체
  3. 함수 호출 시 함수 내부의 this: 지정되지 않음
  4. ...

3번에 대해서 주의깊게 봐야 하는데, 함수를 호출 했을 때 그 함수 내부의 this는 지정되지 않는다. 그리고, this가 지정되지 않은 경우, this는 자동으로 전역 객체를 바라보기 때문에 함수를 호출하면, 함수 내부에서의 this는 전역객체가 된다고 할 수 있다.

이 부분은 자바스크립트 개발자 중 한명인 더글라스 크락포드도 설계상 오류라고 지적하였다...

const cat = {
  name: 'meow',
  foo1: function() {
    const foo2 = function() {
      console.log(this.name);
    }
    foo2();
  }
};

cat.foo1();	// undefined
  • cat.foo1() 메소드 호출 시 내부 함수 foo2가 실행됨
  • 함수가 호출됐으므로 foo2 내부의 this는 지정되지 않아서 곧 전역 객체를 가리킴!!
  • 전역 객체에 name이란 속성은 존재하지 않으므로 undefined가 출력된다.

this.namecat객체의 내부에 지정한 meow일 줄 알았는데 undefined가 나왔다. 함수를 호출할 때, 그 함수를 호출한 객체의 this가 나오도록 설계했지만... 함수를 호출 했을때, 그 함수 내부의 this는 지정되지 않으므로 실패한 것이다.

하지만 화살표 함수는 위와 같은 상황을 깔끔하게 해결해준다.

const cat = {
  name: 'meow',
  foo1: function() {
    const foo2 = () => {
      console.log(this.name);
    }
    foo2();
  }
};

cat.foo1();	// meow

화살표 함수의 this바인딩

화살표 함수에서 this가 존재하지 않는다.

이게 가능한 이유는 화살표 함수에는 this가 아예 없다. 즉, function으로 선언한 함수를 실행할 땐 this가 존재하긴 하지만 값을 지정하지 않는데, 화살표 함수로 선언한 함수에는 this가 없다.

상세하게 설명하자면, 여기서 this가 존재하지 않는다고 말하는 것은, 화살표 함수가 자신만의 this바인딩을 생성하지 않는다는 의미이다. 즉, 화살표 함수 내부에서 사용되는 this는 함수가 작성된 렉시컬 환경(lexical context)this를 그대로 상속받는다. 이는 화살표 함수의 중요한 특징 중 하나로, 여러 상황에서 유용하게 사용된다.

  • 화살표 함수의 this란...
    • 1.상속받는 this
      • 화살표 함수는 자신을 포함하고 있는 외부 함수의 this 값을 상속받는다. 이는 화살표 함수가 정의될 때 결정되며, 호출 방식이나 호출 시점, 위치에 상관없이 변경되지 않는다!!
    • 2.렉시컬 바인딩
      • 일반적인 함수에서 this는 호출 시점에 따라 결정된다. 예를 들어, 같은 함수를 다른 객체의 메소드로 할당하거나 독립적으로 호출할 때 this 값이 달라진다. 반면, 화살표 함수의 this는 언제나 그 함수가 생성된 스코프의 this를 참조한다(렉시컬 바인딩).

화살표 함수의 렉시컬 바인딩?

JavaScript에서는 어떤 식별자(변수)를 찾을 때 현재 환경에서 그 변수가 없으면 바로 상위 환경을 검색한다. 그렇게 점점 상위 환경으로 타고 타고 올라가다가 변수를 찾거나 가장 상위 환경에 도달하면 그만두게 되는 것다. 화살표 함수에서의 this 바인딩 방식도 이와 유사하다. 화살표 함수에는 this라는 변수 자체가 존재하지 않기 때문에 그 상위 환경에서의 this를 참조하게 된다.

JS에서 화살표 함수의 this바인딩 방식은 변수가 스코프 체인을 통해 검색되는 방식과 유사하게 작동한다. 이를 렉시컬 스코핑(lexical scoping) 또는 정적 스코핑(static scoping)이라고 한다. 화살표 함수의 경우, this는 함수가 정의된 시점의 렉시컬 환경에서 결정된다.

function Person() {
    this.age = 0;

    setInterval(() => {
        this.age++;  // 여기서 `this`는 Person 인스턴스를 가리킴
        console.log(this.age);
    }, 1000);
}

const p = new Person();  // `this.age` 값이 1초마다 증가

이 예제에서 setInterval 내의 화살표 함수는 Person 생성자 함수의 this를 상속받습니다. 일반 함수를 사용했다면, setInterval은 일반적으로 thiswindow (브라우저 환경)나 undefined (엄격 모드)로 설정하므로, this.age에 접근할 수 없게 된다. 화살표 함수 덕분에 Person 객체의 age 속성에 접근하고 조작할 수 있다.

화살표 함수를 쓰면 안되는 경우!! (this오류)

1.메소드

const cat = {
  name: 'meow',
  callName: () => console.log(this.name)
}

cat.callName();	// undefined

이 같은 경우, callName 메소드의 this는 자신을 호출한 객체 cat이 아니라 함수 선언 시점의 상위 스코프인 전역객체를 가리키게 된다. 어차피 일반 함수를 사용해도 메소드로 호출하면 자신을 호출한 객체를 가리키기 때문에 메소드에서 화살표 함수를 쓸 필요는 없다. 위 함수를 옳게 바꾸면 다음과 같다.

const cat = {
  name: 'meow',
  callName: function() {
    console.log(this.name);
  }
};

cat.callName();  // "meow"

이 예제에서는 callName 메소드를 일반 함수로 정의했다. 일반 함수에서는 this가 해당 함수를 호출하는 객체에 동적으로 바인딩되기 때문에, cat 객체의 callName 메소드를 호출할 때 thiscat 객체를 가리키고 this.name은 'meow'를 정상적으로 출력한다.

또 다른 방법으로, ES6의 짧은 메소드 구문을 사용하여 더 간결하게 표현할 수 있다

const cat = {
  name: 'meow',
  callName() {
    console.log(this.name);
  }
};

cat.callName();  // "meow"

이 방법은 함수를 정의할 때 function 키워드를 생략하고, 객체 리터럴 내에서 메소드를 정의하는 직관적인 방법이다. 이 경우에도 this는 메소드가 호출된 객체인 cat을 가리킨다.

2.생성자 함수

const Foo = () => {};
const foo = new Foo()	// TypeError: Foo is not a constructor

애초에 생성자 함수로 사용할 수 없게 제작되었다.

3.addEventListener()의 콜백함수

const button = document.getElementById('myButton');

button.addEventListener('click', () => {
  console.log(this);	// Window
  this.innerHTML = 'clicked';
});

button.addEventListener('click', function() {
   console.log(this);	// button 엘리먼트
   this.innerHTML = 'clicked';
});

원래 addEventListener의 콜백함수에서는 this에 해당 이벤트 리스너가 호출된 엘리먼트가 바인딩되도록 정의되어 있다. 이처럼 이미 this의 값이 정해져있는 콜백함수의 경우, 화살표 함수를 사용하면 기존 바인딩 값이 사라지고 상위 스코프(이 경우엔 전역 객체)가 바인딩되기 때문에 의도했던대로 동작하지 않을 수 있다. 물론 상위 스코프의 속성들을 쓰려고 의도한 경우라면 사용할 수 있다.

요약

  • 화살표 함수는 자체적인 this를 가지지 않습니다. 대신 정의된 위치의 스코프에서 this를 상속받는다.
  • 이 특성은 this가 일관되게 유지되어야 하는 경우 (예: 콜백 함수, 타이머, 이벤트 핸들러 등) 매우 유용하다.
  • 화살표 함수로 인해 this 바인딩 관련 일반적인 오류를 줄일 수 있다.

이러한 이유로 화살표 함수는 특히 JavaScript의 비동기 코드나 콜백 패턴에서 매우 유용하게 사용된다.

arguments

자바스크립트에서 일반 함수와 화살표 함수가 arguments 객체를 다루는 방식에는 차이가 있다. arguments 객체는 함수로 전달된 모든 인수를 배열 형태로 제공하는 유사 배열 객체이다. 일반 함수에서는 arguments 객체를 사용할 수 있지만, 화살표 함수에서는 사용할 수 없다.

일반 함수에서의 arguments 사용

일반 함수에서는 arguments객체를 사용할 수 있다.

function regularFunction() {
    console.log(arguments);
    console.log('Number of arguments:', arguments.length);
    for (let i = 0; i < arguments.length; i++) {
        console.log(`Argument ${i}:`, arguments[i]);
    }
}

regularFunction(1, 'two', true, null, { key: 'value' });

화살표 함수에서의 arguments 사용

화살표 함수에서는 arguments 객체를 사용할 수 없다. 대신, rest parameters를 사용할 수 있다.

자바스크립트의 Rest Parameters는 함수에 전달된 인수들을 배열로 다루기 위한 문법이다. 이 문법을 사용하면 함수가 받는 인수의 수가 고정되지 않고, 동적으로 처리할 수 있다. 이는 함수가 정해지지 않은 수의 인수를 받아야 할 때 매우 유용하다.

Rest Parameters는 이름 앞에 세 개의 점(...)을 붙여서 사용한다. 이 문법은 함수의 마지막 매개변수로 사용되어야 하며, 그 함수로 전달된 나머지 모든 인수를 배열로 모아준다.

Rest Parameters의 주요 특징

  • 가변 인수 처리
    • 함수를 정의할 때 몇 개의 인수가 전달될지 모를 경우 유용하게 사용할 수 있다.
  • 배열 메소드 접근
    • Rest parameters로 받은 인수는 배열이므로, forEach, map, reduce 등의 배열 메소드를 직접 사용할 수 있다.
  • 명확한 의도 표현
    • 함수 시그니처에서 ...를 사용하면, 이 함수가 여러 인수를 받을 수 있음을 명확히 나타내어 코드의 가독성을 향상시킨다.
  • arguments 객체 대체
    • 전통적인 arguments 객체보다 훨씬 직관적이고 편리하다. arguments 객체는 유사 배열 객체이므로, 배열 메소드를 직접 사용할 수 없는 반면, Rest parameters는 진정한 배열이다.

예시코드

const arrowFunction = (...args) => {
    console.log(args);
    console.log('Number of arguments:', args.length);
    args.forEach((arg, index) => {
        console.log(`Argument ${index}:`, arg);
    });
}

arrowFunction(1, 'two', true, null, { key: 'value' });

우회해서 화살표함수에서 arguments를 사용하는 방법...?

function regularFunction() {
  const arrowFunction = () => {
    console.log(arguments);
  };
  arrowFunction();
}

regularFunction(1, 2, 3); // [1, 2, 3]

위 예에서 regularFunction의 arguments 객체는 arrowFunction에서 접근 가능하다. 그러나 이는 화살표 함수가 자체적으로 arguments를 가지고 있기 때문이 아니라, regularFunction의 arguments를 렉시컬 스코프를 통해 "상속"받기 때문이다.

결론

  • 일반 함수에서는 arguments 객체를 사용할 수 있으며, 이는 함수 내에서 사용할 수 있는 유사 배열 객체이다.
  • 화살표 함수에서는 arguments 객체를 사용할 수 없고, 대신 rest parameters를 사용하여 함수 인수를 배열로 받을 수 있다.

화살표 함수는 설계상 자신의 arguments 객체를 생성하지 않고, 필요한 경우 렉시컬 상위 스코프의 arguments를 참조하거나, 보다 명확하고 예측 가능한 나머지 매개변수를 사용하여 인수를 처리한다. 이는 코드를 더 간결하고 이해하기 쉽게 만들어 준다.

profile
신입사원...

0개의 댓글