자바스크립트 필수요소 : IIFE 마스터하기

EJ·2020년 12월 9일
0

JavaScript 개념

목록 보기
8/13

자바스크립트 함수와 함께 자주 사용되는 코딩 패턴 중 하나는 Immediately-invoked Function Expression이라는 이름을 갖고 있다. ( IIFE )

IIFE가 무엇인지, 왜 필요한지 이해하기 전에, 자바스크립트 함수에 관한 핵심적인 개념들을 다시 짚어보자.

자연스러운 함수 정의

자바스크립트를 처음 접하는 개발자는 함수를 다룰 때 다음 문법이 편할 것이다.

function sayHi() {
  alert("Hello, World!");  
}

sayHi();
  1. 1-3번째 줄은 sayHi()라는 이름의 함수를 정의한다.
  2. 5번째 줄에서 ()문법을 이용해 정의한 함수를 불러온다.

이렇게 함수를 생성하는 방식은 a function definition 또는 a function declaration 또는 a function statement로 불린다. 일반적으로, 다른 인기있는 프로그래밍 언어의 문법과 닮았기 때문에 자바스크립트를 처음 접하는 개발자도 이 문법을 별다른 어려움 없이 사용할 수 있다.

이 함수 정의는 항상 function 키워드로 시작한다. 그리고 뒤에는 함수의 이름이 따라온다. 함수의 이름을 생략하면 문법에 어긋나기 때문에 이름을 생략할 수 없다.

함수 표현식

var msg = "Hello, World!";
var sayHi = function() {
  alert(msg);  
};

sayHi(); // 브라우저에서 "Hello, World!"라는 alert 메시지를 띄운다.
  1. 1번째 줄은 msg변수를 선언하고 string값을 할당한다.
  2. 2-4번째 줄은 sayHi변수를 선언하고 function타입의 값을 할당한다.
  3. 6번째 줄은 sayHi함수를 호출한다.

1번째 줄은 이해하기 쉽다. 하지만 자바같은 프로그래밍 언어만 경험한 개발자들에게 2-4번째 줄의 코드들은 예상을 벗어난다.

기본적으로 2-4번째 줄에서는 함수 타입의 값을 sayHi라는 변수에 할당했다.

위의 예제에서, 할당의 right-hand에 있는 함수는 주로 함수 표현식(Function Expression)이라 불린다. 자바스크립트 내 어디에든 있다. 콜백을 작성해봤다면 작성한 대부분의 콜백은 함수 표현식이었을 것이다.

자바스크립트에서 함수는 다른 값들과 거의 비슷하다. 할당 연산자의 right-hand에 올 수 있고, 또 다른 함수에 인자로도 넘겨질 수 있다.

익명 함수 표현식

위의 예제가 익명 함수 표현식이었다. 위의 함수는 function키워드 뒤에 이름이 붙지 않았기 때문에 익명 함수인 것이다.

이름붙은(Named) 함수 표현식

함수 표현식을 이름을 가질 수 있다. 이름붙은 함수 표현식에서 가장 잘 알려진 용례는 재귀이다.

var fibo = function fibonacci() {
  // 여기서 fibonacci() 함수를 호출할 수 있다.
  // 이 함수 표현식이 이름을 갖고 있기 때문이다.
}

// 여기서 fibonacci()를 호출하면 실패한다. 하지만 fibo()는 동작한다.

여기서 차이점은 함수 표현식이 fibonacci라는 이름을 가졌다는 것이다.
이름을 가졌기 때문에 fibonacci함수 내부에서 자신을 재귀적으로 호출할 수 있다.

IIFE

IIFE는 몇가지 문체의 방식으로 쓰여진다.
첫 번째 방식을 살펴보자.

!function() {
  alert("Hello from IIFE!");  
}();
// "Hello from IIFE" 메시지를 보여준다.

위 코드의 2번째 줄에서 alert을 볼 수 있다. 하지만 그게 다이다. 누구도 이 alert을 다시 한 번 보여줄 수 없다.

이 함수는 생명을 갖자마자 바로 죽어버린다.

이제 직관적이지 않은 문법에 대해서 이해해보자. 첫 번째 줄에 !가 있는 것을 볼 수 있다.

이전에 봤듯이, 함수 statement는 언제나 function이라는 키워드로 시작한다. 자바스크립트가 유효한 statement에서 첫 단어로 function키워드를 볼 때마다, 자바스크립트는 함수 정의가 일어날 것이라고 예측한다.
그래서 이런 일이 일어나지 않도록 첫 번째 줄 function키워드 앞에 !를 붙여준 것이다. 이렇게 하면 자바스크립트는 ! 뒤에 무엇이 오든지 표현식으로 다루게 된다.

위 코드에서 생기자마자 바로 호출되는 함수 표현식을 얻을 수 있었다. 이것을 IIFE라 부르고, 어떤 문체의 방식으로 쓰여지는지 상관 없이 효력을 발휘한다.

위의 문체의 방식은 ! 외에도 +, -, ~ 등 다양한 방식으로 작성해도 같은 결과를 보인다. 1진 연산자면 아무 거나 이용해도 된다. 결국 목적은 뒤에 있는 함수를 식으로 만드는 것이기 때문이다.

암묵적 타입변환을 참고하면 이해에 도움이 될 것이다.

위 코드의 첫 번째 문자 !는 작성한 함수가 statement나 정의(definition)가 되지 않게 표현식으로 만들고 그 후에 해당 함수를 즉시 실행하는 것이다.

아래 다른 문체로 작성된 코드를 살펴보자.

void function () {
  alert("Hello from IIFE!");
}();

void는 함수를 식으로 다뤄지게 강제한다.

IIFE에서 반환 값이 필요없을 때, 위의 모든 패턴들은 실용적이다.

하지만, 만일 IIFE에서 반환 값이 필요하다면, 그리고 그 반환 값을 다른 곳에서 사용하길 원한다면 어떻게 할까? (아래 내용으로 이어짐)

클래식한 IIFE 스타일

위에서 쓴 IIFE 패턴은 이해하기 쉽다. 그래서 이해를 돕기 위해 더욱 전통적이고 널리 알려진 스타일보다 위의 스타일을 대신 사용했다.(전통적이고 널리 알려진 스타일은 뭐지>>>??)

위에서 본 IIFE 예제처럼, IIFE패턴의 키는 함수를 만들고 식으로 변환하고 즉시 실행하는 것이다.

다른 방식으로 함수 표현식을 만드는 방법을 살펴보자.

(function() {
  alert("I am not an IIFE yet!");  
});

위 코드의 함수식은 괄호로 감싸져 있다. 위의 함수는 실행되지 않았기 때문에 아직 IIFE가 아니다. 위 코드를 IIFE로 바꾸기 뒤해서 다음 두 가지 문체를 따를 것이다.

// 문체 1
(function () {
  alert("I am an IIFE!");
}());

// 문체 2
(function () {
  alert("I am an IIFE, too!");
})();

1번 문체와 2번 문체의 차이를 살펴보자.

  1. 1번 문체의 4번째 줄에서, 함수 식을 호출하기 위한 ()괄호는 바깥 괄호 안에 포함된다.
    또, 다시 바깥 괄호가 바깥 함수를 함수식으로 만들기 위해서 필요하다.
  2. 2번 문체의 9번째 줄에서, 함수 식을 호출하기 위한 ()괄호는 함수 표현식을 위한 감싸는 괄호 바깥에 있다.

위의 두 가지 문체는 널리 사용된다.
두 가지 문체가 작동하는 방식에서 약간 다르다. 하지만 실용적인 목적에서 아무 문체나 사용할 수 있다.

작동하는 예제와 작동하지 않는 2가지 예제를 살펴보자.

// 유효한 IIFE
(function initGameIIFE() {
  // All your magical code to initialize the game!
}());

// 유효하지 않은 IIFE
function nonWorkingIIFE() {
    // 이제 왜 앞뒤로 괄호가 필요한지 알게 될 것이다.
    // 괄호 없이는 그냥 함수 정의이다. 표현식이 아니다.
    // 문법 에러가 날 것이다.
}();

function () {
    // 여기서도 문법 에러가 날 것이다.
}();

꼭 기억하자! IIFE를 구성하기 위해서는 함수 표현식이 필요하다. 함수 statement나 정의는 IIFE를 만드는데 절대 이용될 수 없다.

IIFE와 private변수

IIFE가 잘하는 것 중 하나는 IIFE를 위한 함수 스코프를 만드는 능력이다.

IIFE내부에 정의된 어떤 변수라도 바깥 세상에서는 보이지 않는다.

예제를 살펴보자.

(function IIFE_initGame() {
  // IIFE 밖에서는 접근할 수 없는 Private 변수들입니다.
  var lives;
  var weapons;
  
  init();
  
  // IIFE 밖에서는 접근할 수 없는 Private 함수입니다.
  function init() {
    lives = 5;
    weapons = 10;
  }
}());

이 예제에서 IIFE 내부에 두 개의 변수를 선언했다. 그리고 이 두 변수들은 IIFE에 private하다. IIFE 밖의 어느 누구도 그 변수들에 접근할 수 없다.비슷하게, init함수가 있고 그 안에 있는 변수들은 IIFE 밖에서 누구도 접근할 수 없다. 하지만 init함수에서는 바깥 변수에 접근이 가능하다.

코드 바깥에서는 사용하지 않는 많은 변수와 함수를 전역에 만들 때마다, 변수와 함수들 모두를 IIFE로 감싸고, 이렇게 함으로써 좋은 자바스크립트 카르마를 얻을 수 있다.
코드는 계속 동작할 것이지만, 전역 스코프는 오염되지 않는다. 그럼으로써 코드 전역 스코프를 실수로 혹은 의도적으로 수정하는 다른 누군가로부터 코드를 보호할 수 있다.

값을 리턴하는 IIFE

IIFE로부터 반환 값이 필요하지 않다면, !, +, void와 같은 단항 연산자를 이용한 첫 번째 문체의 IIFE를 계속 사용할 수도 있다.

하지만 다른 중요하고 강력한 IIFE의 기능 중 하나는 그들이 변수에 할당될 수 있는 값을 리턴할 수 있다는 데에 있다.

var result = (function () {
  return "From IIFE";
}());

alert(result); // "From IIFE" 메시지를 출력합니다.
  1. 위 코드의 2번째 줄에서, statement를 반환하는 IIFE를 가진다.
  2. 위 코드를 실행할 때, 5번째 줄은 IIFE에서 반환된 값과 함께 alert메세지를 보여준다.

기본적으로 IIFE는 즉시 실행된다. 그리고 반환된 값은 result변수에 할당된다.
이는 모듈 패턴 예제처럼 사용하게 될 정말 강력한 패턴이다.

파라미터가 있는 IIFE

IIFE는 값을 리턴할 수 있을 뿐만 아니라, 호출될 때 인자를 받을 수도 있다.

(function IIFE(msg, times) {
  for (var i=1; i<=times; i++){
    console.log(msg);  
  }
}("Hello!", 5));
  1. 위 예제의 첫 번째 줄에서, IIFEmsg, times 각각 두 개의 파라미터를 갖는다.
  2. 5번째 줄에서 IIFE를 실행할 때, 여태까지 사용했던 빈 괄호() 대신에 인자(arguments)를 IIFE로 넘겼다.
  3. 2-3번째 줄은 이 파라미터를 IIFE 내부에서 사용한다.

이것은 정말 강력한 패턴이다. 그리고 jQuery 코드와 여타 라이브러리에서 이러한 형식이 자주 사용된다.

(function($, global, document) {
  // jQuery를 위해 $를 사용하고, window를 위해 global을 사용합니다.
}(jQuery, window, document));

위 예제 3번째 줄에서 jQuery, window, document를 IIFE에 인자로 넘겼다.
IIFE내부의 코드는 $, global, document를 각각 참조할 수 있다.

IIFE에 위의 파라미터를 넘김으로써 다음과 같은 이점을 얻을 수 있다.

  1. 자바스크립트는 항상 현재 함수의 스코프부터 식별자(ID)를 찾을 때까지 계속 더 높은 레벨의 스코프로 올라가며 식별자를 찾아다닌다. 3번째 줄에서 document를 넘겼을 때가 document에 대한 로컬 스코프를 넘어 스코프 탐색을 하는 유일한 때이다. IIFE에서 document로의 어떤 참조도 IIFE의 로컬 스코프 밖에서 찾아질 필요가 없다. jQuery에도 동일하게 적용된다. IIFE코드가 간단한지 복잡한지에 기반하여 얻어지는 성능 향상은 크지 않지만 알아두면 좋은 트릭이다.

추가적으로 설명하자면, document를 이미 파라미터로 넘겼기 때문에 document.메소드() 또는 document.property의 형태에서 추가적으로 전역 객체를 찾아 돌아다니지 않는다는 의미이다. 기본적으로 IIFE에 저렇게 파라미터를 넘기지 않고 document.메소드()document.프로퍼티형태의 코드를 작성하면 IIFE는 내부엔 존재하지 않는 document라는 전역 객체를 찾아 돌아다닐 것이다.

  1. 또한 자바스크립트 축소기(minifiers)는 안전하게 함수 안에 선언된 파라미터의 이름을 축소할 수 있다. 만일 이러한 파라미터를 넘기지 않는다면, 축소기를 documentjQuery에 대한 직접적인 참조를 축소할 수 없다. 그들이 이 함수 스코프 밖에 있기 때문이다.

    minifier를 번역하다보니 억지로 축소기라고 번역했다고 함. 영어 그 자체 minifier로 생각함이 더욱 수월할 것이다.

클래식한 자바스크립트 모듈 패턴

IIFE와 클로져가 들어간 모듈 패턴을 알아보자.

클래식한 Sequence 싱글톤 객체를 구현할 것이다. 이 객체는 실수고 현재의 값이 오염되거나 하는 것 없이 제대로 작동한다.

어떤 일이 일어나는지 순차적으로 이해하기 우해, 2단계에 걸쳐 이 코드를 작성할 것이다.

var Sequence = (function sequenceIIFE() {
  // 현재 counter 값을 저장하기 위한 Private 변수.
  var current = 0;
  
  // IIFE에서 반환되는 객체.
  return {
  };
  
}());

alert(typeof Sequence); // alert("Object");
  1. 위 예제에서 객체를 반환하는 IIFE를 만들었다. 7-8번째 줄을 보면 나온다.
  2. IIFE 내부에 current라는 이름을 갖는 지역 변수를 만들었다.
  3. 이 예제에서 IIFE의 반환 값인 객체는 Sequence라는 변수에 할당된다. 12번째 줄에서 IIFE에 반환한 "object"메시지를 올바르게 출력하고 있다.

이제 리턴하는 객체에 몇 가지 함수를 추가하면서, 다음 단계로 넘어가보자.

var Sequence = (function sequenceIIFE() {

  // 현재 counter 값을 저장하기 위한 Private 변수
  var current = 0;
  
  // IIFE로 부터 반환 받는 객체
  return {
    getCurrentValue: function() {
      return current;  
    },
    
    getNextValue: function() {
      current = current + 1;
      return current;
    }
  };
  
}());

console.log(Sequence.getNextValue()); // 1
console.log(Sequence.getNextValue()); // 2
console.log(Sequence.getCurrentValue()); // 2
  1. 이 예제에서 IIFE가 리턴하는 객체에 2가지 함수를 추가했다.
  2. 8-10번째 줄은 current변수가 가진 값을 반환하는 getCurrentValue 함수를 추가했다.
  3. 12-15번째 줄은 current변수를 1 증가시키고 반환하는 getNextValue함수를 추가했다.

current변수가 IIFE에서 private하기 때문에, 클로저를 통해 여기에 접근할 수 있는 함수 말고는 누구도 current변수의 값에 접근하거나 그 값을 수정할 수 없다.

괄호를 생략할 때

함수 표현식에서 앞뒤에 괄호를 해주는 이유는 기본적으로 함수가 statement의 형태가 아닌 식(expression)의 형태가 되도록 만들어주기 위함이다.

하지만 자바스크립트 엔진이 판단하기에 해당 코드가 명확히 함수 표현식이라면, 기술적으로 감싸는 괄호를 하지 않아도 될 것이다. 아래의 예제처럼.

var result = funciton() {
  return "From IIFE!";
}();

위 예제에서 function키워드는 statement의 첫 번째 단어가 아니다. 그래서 자바스크립트 엔진은 이걸 statement 또는 정의(definition)로 판단하지 않는다. 비슷하게 식이라는게 명확하다면, 다른 곳에도 괄호를 생략할 수 있는 곳들이 있다.

하지만 괄호를 적는 것이 좋다고 본다. 위의 경우처럼 괄호가 필요없는 경우에도 말이다. 괄호를 사용하는 것은 가독성을 높여준다. 읽는 사람이 첫 줄만 보고도 그 함수가 IIFE가 될 것이라는 걸 알 수 있게 문체적으로 넌지시 힌트를 준다. 그들은 그들이 읽는 것이 IIFE라는 것을 알아내기 위해 끝까지 스크롤을 넘길 필요가 없어진다.

IIFE는 코드가 더 정렬되고 더 아름답게 만드는 것을 돕지는 않는다. IIFE는 쓸데 없는 전역 변수를 만드는 것을 피함으로써 버그를 줄이는데 도움을 준다.


profile
주니어 프론트엔드 개발자 👼🏻

0개의 댓글