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

fromzoo·2020년 12월 10일
0

자연스러운 함수 정의

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 함수를 호출

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

위의 예제에서, 할당의 right-hand에 있는 함수는 주로 함수 표현식이라 한다. 자바스크립트 내 어디든 있다. 대부분의 콜백은 함수표현식이다.

여기서 기억할 중요한 개념은 자바스크립트에서 함수는 다른 값들과 거의 비슷하다는 점이다. 할당 연산자의 right-hand에도 올 수 있고, 다른 인자로도 넘겨질 수 있다.

익명 함수 표현식

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

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

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

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

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

여기서 차이점은 함수표현식이 "fibonacci"라는 이름을 가졌다는 것

또 이름을 가졌기 때문에, fibonacci 함수 내부에서 자신을 재귀적으로 호출할 수 있다.

(사실 이것과 관련된 지식은 많이 있다. 함수 이름이 stack-trace등에서 보여지고 하는것들)

IIFE 를 알아보자

IIFE는 몇가지 문체의 방식으로 쓰인다.

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

IIFE가 동작한다.

이코드를 복사해서 브라우저 콘솔에서 실행해보려고 할때, 2번째 줄에서 alert를 볼 수 있다. 이게 다고, 다시한반 alert는 보여줄 수 없다.

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

이제 직관적인 문법에 대해서 이해해보자. 첫번째 줄에 '!'가 있다.

  1. 우리가 이전에 봤듯이 함수 statement는 언제나 function이라는 키워드로 시작한다. 자바스크립트가 유효한 statement에서 첫 단어로 function 키워드를 볼 때마다, 자바스크립트는 함수 정의가 일어날 것 이라고 예측한다.

그래서 이러한 일이 일어나지 않도록 하기 위해서, 첫번째 줄 function키워드 앞에 '!'를 붙여준 것이다.

이렇게 하면 자바스크립트는 '!' 뒤에 온게 무엇이든지 표현식으로 다루게 된다

그래서 우리는 생기자마자 바로 호출되는 함수 표현식을 얻었다. 이것은 IIFE라고 불리고 어떤 문체의 방식으로 쓰여지는지 상관없이 효력을 발휘한다.

위의 문체 방식은 '!' 외에도 '+', '-', '~' 등 다양한 방식으로 작성해도 같은 결과를 보인다.

1진 연산자면 아무거나 이용해도 된다. 결국 목적은 뒤에 있는 함수를 식으로 만드는 것이기 때문이다.

'!' 를 안붙이면 SyntaxError

function() {alert('hello')}()
VM600:1 Uncaught SyntaxError: Function statements require a function name

첫번째 문자 '!'는 작성한 함수가 statement나 정의가 되지 않게 표현식으로 만들고 그 후에 우리는 그 함수를 즉시 실행하도록 하는 것이다.

아래는 다른 문체로 작성된 코드이다.

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

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

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

하지만, 만익 IIFE에서 반환값이 필요하다면 그리고 그 반환값을 다른곳에서 사용하길 원한다면?

클래식한 IIFE 스타일

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

그럼 여기 다른 방식으로 함수 표현식을 만드는 방법을 보자.

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

위의 코드에서 1-3번째 줄에서 함수 식이 괄호로 감싸져 있다. 위의 함수는 실행되지 않았기때문에 아직 IIFE가 아니다.

이제 위 코드를 IIFE로 바꾸기 위해 우리는 다음 두가지 문체를 따를것이다.

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

이제 우리는 동작하는 2가지 IIFE를 알았다.
1번째 문체와 2번째 문체의 차이를 알아채는 것은 매우 어렵다.

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

  2. 두번째 문체 9번째 줄에서, 함수 식을 호출하기 위한 ()괄호는 함수 표현식을 위한 감싸는 괄호밖에 없다.

두가지 문체가 널리 사용된다.
핵심을 보자면, 두가지 문체가 작동하는 방식에서 약간다르다.

하지만 실용적인 목적에서 그리고 길어진 이 튜토리얼을 짧게 유지하기 위해 좋아하는 것 아무거나 쓸 수 있다고 말하겠다.

이쯤에서 작동하는 다음 예제와 작동하지 않는 2가지 예제를 보자. 우리는 지금부터 IIFE의 이름을 지을 것이다. 익명함수를 사용하는 것은 그다지 좋은 아이디어가 아니기 때문이다.

// 유효한 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밖의 어느 누구도 함수 안에 있는 변수에 접근 할 수 없다. private하다.

비슷하게 우리는 init 함수가 있고 그 안에 있는 변수들은 IIFE밖에서 누구도 접근할 수 없다. 하지만 init 함수에서는 바깥변수에 접근이 가능하다.

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

우리가 모듈 패턴을 볼 때, 어떻게 이 private 변수들에 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. 5번째 줄에서 IIFE를 실행할때 여태까지 사용했던 빈 괄호 () 대신에 인자를 IIFE로 넘겼다.
  2. 2번째 3번째 줄은 이 파라미터를 IIFE 내부에서 사용한다.
(function($, global, document) {
  // jQuery를 위해 $를 사용하고, window를 위해 global을 사용합니다.
}(jQuery, window, document));

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

  1. 자바스크립트는 항상 현재 함수의 스코프로부터 식별자(ID)를 찾을때까지 계속 더 높은 레벨의 스코프로 올라가며 식별자를 찾아다닌다.

3번째 줄에서 우리가 document를 넘겼을때가, document에 대한 로컬 스코프를 너머 스코프 탐색을 하는 유일한 때이다.

IIFE에서 document로의 어떤 참조도 IIFE의 로컬 스코프 밖에서 찾아질 필요가 없다. jQuery에도 동일하게 적용된다. IIFE코드가 간단한지 복잡한지에 기반하여 얻어지는 성능 향상은 크지 않지만 알아두면 좋다.(???)

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

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

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

IIFE와 클로져가 들어간 모듈 패턴을 볼 것이다.

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

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

alert(typeof Sequence); // alert("Object");
  1. 위의 예제에서 객체를 반환하는 IIFE를 만들었다.
  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 = function () {
	return 'From IIFE';
}()

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

하지만 저자는 언제나 괄호를 적는것을 선호한다. 위의 경우처럼 괄호가 필요없는 경우까지. 괄호를 사용하는 것은 가독성을 높여준다. 읽는 사람이 첫 줄만 보고도 그 함수가 IIFE가 될 것이라는 것을 알 수 있게 문체적으로 힌트를 주기 때문이다.

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

🔍 출처 블로그
자바스크립트 개발자라면 알아야 할 33가지 개념 #8 자바스크립트 필수요소 : IIFE 마스터하기

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

0개의 댓글