[JS] 일반함수 vs 화살표 함수 (feat. this)

GyungHo Go·2023년 1월 13일

자바스크립트(ES6)에서 함수를 정의하는 방법은 크게 두가지로 나뉜다.
일반함수화살표 함수 이다.

function main() { // 일반 함수
	return 'function';
};

const main = () => 'function'; // 화살표 함수

일반함수 - function

일반 함수는 function 키워드를 가지고 정의하는 방식으로
함수 선언식(Function Declartions)함수 표현식(Function Expresstion)으로 정의할 수 있다.

함수 선언식(function declartions)

일반 적인 프로그래밍 언어에서 함수 선언과 비슷한 형식이다. 자바스크립트에서 함수를 선언하는 가장 기본적인 방법이기도 하다.

function 함수명() {
  ...로직
};

함수명();

함수 표현식(function expresstion)

유연한 자바스크립트 언어의 특징을 활용한 선언 방식으로 function을 별도의 변수에 할당하는 방식이다.

const main = function () {
  console.log('main');
};
main();

함수 선언식과 함수 표현식 차이

이 둘의 차이점은 호이스팅(hoisting)의 발생 여부이다.
함수 선언식은 함수 전체를 호이스팅 한다. 작성한 위치와 상관없이 호이스팅 되어 최상단으로 끌어 올려진다. 따라서 함수 선언 전에 함수를 호출해도 정상적으로 호출이 된다.
함수 표현식은 별도의 변수에 할당하는데, 호이스팅에 영향을 받지 않는다.

main(); // 실행 전 호출 가능 main출력
function main() {
  console.log('main');
};

main(); // Uncaught TypeErro: main is not a function
const main = function () {
  console.log('main');
}

화살표 함수 (Arrow Function)

화살표 함수는(Arrow Function)은 ES6문법에서 처음 소개되어 function 키워드 대신 화살표=>를 사용하여 이전의 함수 표현식보다 더 간결한 문법으로 작성할 수 있게 해준다.

const sum = (a, b) => console.log(a+b);
sum(3,5); // 8

화살표 함수는 함수 표현식와 같이 함수의 이름이 필요가 없어 익명할수로만 사용할수 있다. 따로 이름없이 선언된 함수를 변수에 할당해주면 이후에 원할때마다 호출해서 사용할 수 있다.

일반 함수와 화살표 함수의 차이는 문법의 간결함이다.
그리고 또다른 차이는 arguments와 가변인자 이다.

일반 함수를 다시 보자.

function main() {
  console.log(arguments);
};
main(1,2,3);

main()함수에 매개변수를 따로 정의해 주지 않아도 함수 내에서 arguments라는 변수를 전달 받는다.
아래 그림처럼 인자로 전달해준 1,2,3이 배열 형태로 담겨있는 것을 알 수 있다.
즉, argumant 변수는 main()함수가 전달받은 인자를 담고있는 배열 형태의 객체이다.

하지만 화살표 함수는 arguments 라는 변수를 전달 받지 않는다.
그렇다면 화살표 함수는 가변인자를 처리할 수 없을까?
그렇지 않다. 이를 사용하고 싶으면 매개변수로 아무단어나 넣으면 사용이 가능하다.

const main = (...args) => {
  console.log(args);
};
main(4,5,6);

아래 그림처럼 [4,5,6]이 배열로 담겨져있는걸 볼 수 있다.

👉🏻 this!

일반함수화살표함수의 가장 큰 차이는 this이다.

일반 함수의 this

자바스크립트는 함수 호출 방식에 따라 this에 바인딩될 어떤 객체가 동적으로 결정이 된다. 즉, 함수를 호출할 때 함수가 어떻게 호출 되었느냐에 따라 this에 바인딩할 객체가 결정이 된다.

아래 코드를 보면, 일반함수에서 this가 어떻게 동작하는지 살펴보기 위해 객체를 하나 정의했다. obj 라는 객체를 하나 생성했다. name은 'go'이고, main이라는 메소드도 만들었다. 메소드는 객체의 속성으로 넣어진 함수이다.

일반함수에서의 this는 어떻게 호출 하느냐에 따라 달라진다.

function main() {
  console.log(this);
};
const obj = {
  name: 'go',
  main: main,
};
const obj2 = {
  name: 'kim',
  main: main,
};

obj.main(); // obj
obj2.main(); // obj2

obj.main() 함수를 호출하면 this 값은 위에서 만들어준 obj 객체라는 것을 알 수 있다.

main() 함수를 다른 객체의 메소드로 호출하면 this는 달라진다. obj2.main()을 호출하면 name이 kim인 객체가 호출되는 것을 알 수 있다. 이는 main() 함수를 호출한 객체는 obj2가 되기 때문이다.

💡 일반 함수에서 this는 함수가 어디에 선언되었든 상관 없이 this값은 함수를 호출한 객체를 가리키게 된다.
즉, 일반함수의 this는 함수가 선언된 위치가 아닌 호출되는 방법에 따라서 this가 결정된다.

이러한 것을 런타임 바인딩이라고도 하는데, 이유는 일반함수의 this는 런타임시 즉, 함수가 호출될 때 결정되기 때문이다.

화살표 함수의 this

화살표 함수는 함수를 선언할 때 this에 바딩인할 객체가 결정된다. 화살표 함수의 this는 언제나 상위 스코프의 this를 가리킨다. 이를 Lexical this라고도 한다.

일반함수가 자신만의 this를 갖는것과 달리, 화살표 함수는 자신만의 this를 갖지 않는다. this를 감싸고 있는 스코프의 this를 가지고 온다.
다시말해서 화살표 함수 내부에서 this를 접근하면 외부로 부터 this를 가져와서 사용하게 된다.

const obj = {
  name: 'go',
  main: function () {
    console.log(this);
  },
  mainArrow: () => {
    console.log(this);
  },
};

obj.mainArrow(); // window

obj.mainArrow() 함수를 호출하면 더이상 obj객체가 아니라 window를 가리키는 것을 알 수 있다.

🤔 this는 어떻게 window를 갖는 것일까?
obj객체에 mainArrow: this로 놓고, 콘솔 찍어보면 마찬가지로 window가 나온다.
화살표 함수는 함수가 선언된 위치에서 this가 결정된다고 했는데, 이처럼 화살표함수 ()=> {}를 감싸고 있는 스코프, 즉 선언된 위치의 thiswindow이기 때문에 window가 나오는 것이다.

✍🏻 정리해 보면 객체의 메소드로는 일반 함수를 사용하는 것이 좋다. 화살표 함수로 메소드를 정의하면 생성한 객체에 접근할 수 없기 때문이다.

const person = {
  name: 'go',
  sayHi: function () { // 일반함수
    console.log(this.name);
  },
};

const person2 = {
  name: 'kim',
  sayHi: () => { // 화살표 함수
    console.log(this.name);
  },
};
person.sayHi(); // go
person2.sayHi(); // undefined

일반 함수 안에 내장 함수를 만들어 보자

main()함수 안에 innerFunction() 이라는 일반함수 형태의 내장함수를 만들었다.
obj.main()을 호출하면 그 안에 내장함수 innerFunction()가 호출이 된다.
호출해 보면 thiswindow가 된다.

const obj = {
  name: 'go',
  main: function () {
    const innerFunction = function () { // 일반함수
      console.log(this);
    };
    innerFunction();
  },
};
obj.main(); // this = window

위에서 일반함수에서 this는 호출해준 객체를 갖게 된다고 했는데, 왜 window를 바인딩 한것일까?
👉 일반 함수의 this는 함수를 호출한 객체가 된다고 했는데, innerFunction() 호출 문을 보면 어떤 객체로 부터 호출된게 아니다.
그렇기 때문에 innerFunction 안에 있는 this값은 자동으로 window가 된다.

💡 일반함수에 this 값을 원하는 값으로 정하고 싶으면 bind메소드를 사용하면 된다.

const obj = {
  name: 'go',
  main: function () {
    const innerFunction = function () { // 일반함수
      console.log(this);
    }.bind({ age: 29}); // bind메소드 사용해서 객체 지정
    innerFunction();
  },
};
obj.main(); // this = {age: 29}

이렇게 bind를 사용해서 원하는 객체를 만들어서 넣어주면 함수가 어떻게 호출되든 상관없이 this를 원하는 객체로 정할 수 있다.

💡 하지만 화살표 함수는 이런것이 불가능하다.
즉, 화살표 함수는 bind메소드를 사용해도 this를 객체로 정의하는 것이 불가능하다는 말이다.

왜냐하면 화살표 함수는 bind해줄 자신만의 this를 갖고있지 않기 때문이다.
그리고 화살표 함수의 this는 함수를 정의되는 위치에 의해 결정이 되고, 이후에 변경이 되지 않기 때문이다.

화살표 함수 안에 내장 함수를 만들어 보자

이번에는 main() 함수 안에 화살표 함수로 innerFunction() 내장함수를 만들었다.

const obj = {
  name: 'go',
  main: function () {
    const innerFunction = () => { // 화살표 함수
      console.log(this);
    };
    innerFunction();
  },
};
obj.main(); // this = {name: 'go', main: func}

obj.main()을 호출하면 this는 어떤 값을 바인딩 할까?
이때 this는 생성한 obj 객체가 된다.


👉 화살표 함수의 this는 화살표 함수를 감싸고 있는 외부 스코프의 this를 따른다고 했다.
위 코드에서 화살표 함수(innerFunction)를 감싸고 있는 함수는 main()함수가 된다. main()함수는 obj.main() 이렇게 호출해 주었고, 이때 this는 호출해준 obj객체가 되기 때문에 화살표 함수의 this가 obj객체가 된다.

참조

profile
기록하는 습관

0개의 댓글