[자바스크립트 개발자라면 알아야 할 33가지 개념] #8 자바스크립트 필수요소: IIFE 마스터하기

이윤우·2023년 4월 18일
0

JavaScript

목록 보기
26/34
post-thumbnail

자연스러운 함수 정의

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

function sayHi() {
	alert('Hello, world!')
}

sayHi();

이렇게 함수를 생성하는 방식은 'a fucntion definition' 으로 불립니다. 일반적으로, 다른 인기있는 프로그래밍 언어의 문법과 닮았기 때문에, 자바스크립트를 처음 접하는 개발자도 이 문법을 사용하는데 별다른 문제를 겪지 않습니다.

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

함수 표현식

이제 자바스크립트에 관한 것들이 재밌어질 때입니다. 함수 표현식이 어떻게 생겼는지 살펴봅시다.

const msg = 'Hello, World!';
const sayHi = function () {
	alert(msg);
};

sayHi();

이 간단한 예제는 우리 자바스크립트 스킬이 다음 단계로 넘어가도록 도와줄 수 있습니다.

  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) 함수 표현식

함수 표현식은 이름을 가질 수 있습니다. 이름붙은 함수 표현식에서 가장 지루하고 가장 잘 알려진 용례는 재귀입니다. 하지만 지금은 걱정하지 마세요. 이름붙은 함수 표현식의 이해 없이도 IIFE를 마스터하는 것은 가능합니다.

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

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

여기서 차이점은 함수 표현식이 "fibonacci"라는 이름을 가졌다는 것입니다. 또 이름을 가졌기 때문에, fibonacci 함수 내부에서 자신을 재귀적으로 호출할 수 있습니다. (사실 이것과 관련된 지식은 많이 있습니다. 함수 이름이 stack-trace 등에서 보여지고 하는 것들요. 그런데 이번 튜토리얼에서는 그런것들은 크게 신경쓰지 않을 겁니다.)

충분해요! 이제 그만 IIFE를 알려주세요! 아니면 떠나겠어요!

인내심 있게 기다려주셔서 감사합니다. 인내심은 자바스크립트를 마스터하는데 가장 중요한 스킬립니다.

함수 정의와 함수 표현식을 배웠습니다. 이젠 IIFE의 비밀스런 세계로 빠져봅시다. IIFE는 몇가지 문체의 방식으로 쓰여집니다. 그게 다 입니다. 누구도 이 alert를 다시 한 번 보여줄 수 없습니다.

이 함수는 생명을 갖자마자 바로 죽여버립니다.

이제 직관적이지 않은 문법에 대해서 이해해봅시다. 첫번째 줄에 "!"가 있었던 것을 보셨을 겁니다. 만일 못보셨으면, 지금 보시면 됩니다.

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

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

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

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

void는 함수를 식으로 다뤄지게 강제합니다.
IIFE에서 반환 값이 필요 없을 때, 위의 모든 패턴들은 실용적입니다.
하지만, 만일 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() {
	// 괄호 없이는 그냥 함수 정의입니다. 표현식이 아닙니다.
  	// 문법 에러가 날 것입니다.
}();

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

IIFE와 private 변수

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

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

(() => {
	let lives;
  	let weapons;
  
  	init();
  
  	function init() {
    	lives = 5;
      weapons = 10;
    }
})()

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

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

우리가 모듈 패턴을 볼 때, 어떻게 이 private 변수들에 IIFE 밖으로 나가는 특수한 권한을 가진, 제어된 접근권한을 주는지에 대해 설명할 것입니다. 그러니 IIFE 닌자가 이미 된 것 같다는 기분을 느꼈다면, 해답을 알기 위해서 계속 읽어보세요.

값을 리턴하는 IIFE

만일, IIFE로부터 반환 값이 필요하지 않다면, 그냥 우리가 처음 사용했던 것처럼 !, +, void와 같은 단항 연산자를 이용한 첫 번째 문체의 IIFE를 계속 사용할 수도 있습니다.

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

const result = (() => {
	return 'From IIFE';
})();

파라미터가 있는 IIFE

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

((msg, times) => {
	for (let i = 0; i < times; i++) {
    	console.log(msg);
    }
})('Hello', 5);

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

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

우리는 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라는 전역 객체를 찾아 돌아다닐 것입니다.

  2. 또한 자바스크립트 축소기는 안전하게 함수 안에 선언된 파라미터의 이름을 축소할 수 있습니다. 우리가 만일 이러한 파라미터를 넘기지 않는다면, 축소기는 documentjQuery에 대한 직접적인 참조를 축소할 수 없습니다. 왜냐하면 그들이 이 함수 스코프 밖에 있으니까요.

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

이제 자바스크립트 IIFE를 마스터했습니다. 이제 IIFE와 클로져가 들어간 끝내주는 모듈 패턴을 봅시다.
우리는 클래식한 Sequence 싱글톤 객체를 구현할 것입니다. 이 객체는 실수로 현재의 값이 오염되거나 하는 것 없이 제대로 작동합니다.
우리는 어떤 일이 일어나는지 순차적으로 이해하기 위해, 2단계에 걸쳐 이 코드를 작성할 것입니다.

const Sequence = (function () {
	let current = 0;
  
  	return {};
})()

console.log(typeof Sequence);
  1. 위의 예제에서 우리는 객체를 반환하는 IIFE를 만들었습니다.
  2. 우리는 또 IIFE 내부에 current라는 이름을 갖는 지역 변수를 만들었습니다.
  3. 이 예제에서 IIFE의 반환 값인 객체는 Sequence라는 변수에 할당됩니다.

이제 우리가 리턴하는 객체에 몇가지 함수를 추가하면서 다음 단계로 가봅시다.

const Sequence = (function (){
	let currnet = 0;
  
  	return {
    	getCurrentValue: function () {
        	return current;
        },
      	getNextValue: function () {
        	current += 1;
          	return current;
        }
    }
})();

console.log(Sequnce.getNextValue()); // 1
console.log(Sequnce.getNextValue()); // 2
console.log(Sequnce.getCurrentValue()); // 2

괄호를 생략할 때

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

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

const result = function () {
	return 'From IIFE';
}()

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

하지만 저자는 언제나 괄호를 적는 것을 선호합니다. 위의 경우처럼 괄호가 필요 없는 경우까지요. 괄호를 사용하는 것은 가독성을 높여줍니다. 읽는 사람이 첫 줄만 보고도 그 함수가 IIFE가 될 것이라는 것을 알 수 있게 문체적으로 넌지시 힌트를 줍니다. 그들은 그들이 읽는 것이 IIFE라는 것을 알아내기 위해 스크롤을 넘길 필요가 없습니다.

0개의 댓글