함수

se-een·2022년 9월 8일
0
post-thumbnail

이 글은 '이웅모'님의 '모던 자바스크립트 Deep Dive' 책을 통해 공부한 내용을 정리한 글입니다. 저작권 보호를 위해 책의 내용은 요약되었습니다.

함수 선언문

function f(x, y) {
  return x+y;
}

console.log(f(1,2)); // 3

함수 f를 선언문으로 정의하면 위와 같다. 함수 선언문은 다음과 같은 특징을 같는다.

  • 함수 이름 생략 불가
  • 표현식이 아닌 문이기 때문에 완료 값으로 undefined 출력

위 코드를 보면 함수 이름을 통해 함수를 호출하는 것처럼 보인다. 하지만 함수 이름은 함수 몸체 내부에서만 유효한 식별자이기에 외부에서 호출하는 것은 불가능하다. 사실 우리는 함수 이름과 동일한 식별자를 호출하고 있는 것이다. 즉, 자바스크립트 엔진이 암묵적으로 함수 이름과 동일한 식별자를 생성하였고 우리는 그 식별자를 사용하고 있는 것이다. 그림으로 보면 다음과 같다.

let x = function hello() {
  console.log("hello");
};

x(); // hello
hello(); //ReferenceError

위 코드를 보면 변수 x에 함수 hello를 할당하고 있다. 그렇다면 우리는 함수 이름인 hello로 함수를 호출(hello())할 것이 아니라 함수 hello를 할당 받은 식별자 x를 통해 함수를 호출(x())해야할 것이다. 이는 함수 표현식과 닮았다.(정확히는 기명 함수 표현식이라고 한다.) 결과적으로 함수 선언문은 자바스크립트 엔진에 의하여 함수 표현식으로 변환되어 함수를 생성한다고 볼 수 있지만, 함수선언문과 함수표현식이 완전히 동일하게 동작하지는 않는다.


함수 표현식

함수 f를 표현식으로 정의하면 다음과 같다.

let f = function(x,y) {
  return x+y;
};

console.log(f(1,2)); // 3

함수 표현식은 다음과 같은 특징이 있다.

  • 함수 이름 생략 가능(익명함수)
  • 표현식인 문이기에 완료 값으로 표현식이 평가되어 생성된 함수 출력

함수 호이스팅

console.dir(a); // f a(x,y)
console.dir(b); // undefined

console.log(a(1,2)); // 3
console.log(b(1,2)); // TypeError

function a(x,y) {
  return x+y;
}

var b = function(x,y) {
  return x+y;
};

위 코드를 보면 함수 선언문으로 정의한 함수는 함수 선언문 이전에 호출이 가능하다. 하지만 함수 표현식으로 선언한 함수는 그렇지 않다. 이 두 함수의 생성 시점이 다르기 때문인데, 모든 선언문(함수 선언문 포함)은 런타임 이전 자바스크립트 엔진에 의해 먼저 실행된다. 따라서 함수 객체와 함수 이름과 동일한 식별자를 암묵적으로 생성하고, 식별자를 해당 함수 객체에 할당한다. 즉, 함수 선언문이 코드 최상위로 끌어 올려져 실행된 것과 같은 효과를 보이는데 이를 함수 호이스팅이라 한다.

그러면 함수 표현식으로 선언된 함수는 왜 저런 결과를 나타낼까? 함수 표현식은 변수에 할당되는 값이 함수 리터럴인 문이다. 즉, 변수 할당문이 실행되는 시점(런타임)에 값이 평가된다. 따라서 함수 호이스팅이 발생하는 것이 아닌 변수 호이스팅이 발생한다. 따라서 위 코드에선 var 키워드에 따른 변수 호이스팅이 발생하여 undefined 값을 반환한다. 만약 let 키워드로 선언되었다면 ReferenceError를 반환했을 것이다.


화살표 함수

const plus = (x,y) => x+y;
console.log(plus(2,3)); // 5

화살표 함수는 ES6에서 새롭게 추가된 방식으로, function 키워드 대신 '=>'를 사용하여 함수를 간략하게 정의할 수 있는 함수이다. 일반 함수와는 다른 차이점이 여럿 존재하는데 다음과 같다.

1. non-constructor

const Foo = () => {};

new Foo(); // TypeError

인스턴스를 생성할 수 없으므로 prototype 프로퍼티도 없고 프로토타입도 생성하지 않는다.

2. 중복된 매개변수 이름 선언 불가

const plus = (a, a) => a+a; // SyntaxError
function normal(a, a) { return a+a; }

strict mode가 아닌 일반 함수에선 중복 매개변수 선언이 가능하지만 화살표 함수는 그렇지 않다.

3. 함수 자체의 this, arguments, super, new.target 바인딩이 존재하지 않음
이는 화살표 함수를 쓰는 중요한 이유 중 하나라고 할 수 있겠다. 화살표 함수의 this, arguments 등을 참조하면 스코프 체인을 통해 상위 스코프의 this, arguments 등을 참조한다.

// 콜백함수를 일반함수로 선언할 경우
class WhatNum {
  constructor(num) {
    this.num = num
  }
  
  plus(arr) {
    return arr.map(function (i) {
      return this.num + i;
    });
  }
}

const one = new WhatNum(1);
console.log(one.plus([2,3])); // TypeError

// 콜백함수를 화살표함수로 선언할 경우
class WhatNum {
  constructor(num) {
    this.num = num;
  }

  plus(arr) {
    return arr.map((i) => {
      return this.num + i;
    });
  }
}

const one = new WhatNum(1);
console.log(one.plus([2, 3])); // (2) [3, 4]

또한 화살표함수 작성 시 소괄호와 중괄호의 차이점에 유의해서 사용해야한다. ()=>()처럼 소괄호를 사용할 경우 return을 자동으로 해주지만 ()=>{}처럼 중괄호를 사용할 경우 return을 자동으로 해주지 않는다.

getLottoNumber(); // (7) [5, 12, 22, 24, 39, 41, 20]
const lottoNumbers = () => {getLottoNumber()}; // undefined
const lottoNumbers = () => (getLottoNumber()); // (7) [5, 12, 22, 24, 39, 41, 20]

즉, '()=>(a);' 문은 '()=>a;' 와 '()=>{ return a; };'랑 동일하다.


즉시 실행 함수

함수 정의와 동시에 즉시 호출되어 실행되는 함수를 즉시 실행 함수(IIFE)라고 한다.

let x = 10;
let y = 2;

const multiply = (function (x, y) {
  return x * y;
})(x, y);

console.log(multiply); // 20

즉시 실행 함수는 반드시 그룹 연산자 ()로 감싸야 하며 감싸지 않을 경우 에러가 발생한다. 감싸는 방법은 다양하다.

전역 변수의 무분별한 사용은 메모리 리소스 효율을 떨어뜨리며 의도치 않은 오류가 생기기 쉽다. 즉시 실행 함수는 전역 변수 사용 대안으로 쓸 수 있는데 다음과 같다.

// 전역 변수 사용
let num = 10;
console.log(num); // 10
// IIFE 사용
(function () {
  let num = 10;
  console.log(num); // 10
})();
console.log(num); // TypeError

num을 전역 변수로 선언한 코드는 변수 num이 긴 생명 주기를 가지는 반면 즉시 실행 함수를 사용한 코드는 변수 num이 함수 내 지역 변수가 되면서 전역 변수의 사용을 제한하였다. 이처럼 실행하고자 하는 모든 코드를 즉시 실행 함수로 감싸면 모든 변수는 즉시 실행 함수의 지역 변수가 된다. 또한 즉시 실행 함수를 활용하여 자바스크립트가 접근 제한자를 제공하지 않는 단점을 모듈 패턴을 통해 정보 은닉을 구현할 수 있다. 이는 클로저 편에서 살펴보도록 하겠다.

profile
woowacourse 5th FE

0개의 댓글