자바스크립트 함수와 함께 자주 사용되는 코딩 패턴 중 하나는 Immediately-invoked Function Expression이라는 이름을 갖고 있다. ( IIFE )
IIFE가 무엇인지, 왜 필요한지 이해하기 전에, 자바스크립트 함수에 관한 핵심적인 개념들을 다시 짚어보자.
자바스크립트를 처음 접하는 개발자는 함수를 다룰 때 다음 문법이 편할 것이다.
function sayHi() {
alert("Hello, World!");
}
sayHi();
sayHi()
라는 이름의 함수를 정의한다.()
문법을 이용해 정의한 함수를 불러온다.이렇게 함수를 생성하는 방식은 a function definition 또는 a function declaration 또는 a function statement로 불린다. 일반적으로, 다른 인기있는 프로그래밍 언어의 문법과 닮았기 때문에 자바스크립트를 처음 접하는 개발자도 이 문법을 별다른 어려움 없이 사용할 수 있다.
이 함수 정의는 항상 function 키워드로 시작한다. 그리고 뒤에는 함수의 이름이 따라온다. 함수의 이름을 생략하면 문법에 어긋나기 때문에 이름을 생략할 수 없다.
var msg = "Hello, World!";
var sayHi = function() {
alert(msg);
};
sayHi(); // 브라우저에서 "Hello, World!"라는 alert 메시지를 띄운다.
msg
변수를 선언하고 string
값을 할당한다.sayHi
변수를 선언하고 function
타입의 값을 할당한다.sayHi
함수를 호출한다.1번째 줄은 이해하기 쉽다. 하지만 자바같은 프로그래밍 언어만 경험한 개발자들에게 2-4번째 줄의 코드들은 예상을 벗어난다.
기본적으로 2-4번째 줄에서는 함수 타입의 값을
sayHi
라는 변수에 할당했다.
위의 예제에서, 할당의 right-hand에 있는 함수는 주로 함수 표현식(Function Expression)이라 불린다. 자바스크립트 내 어디에든 있다. 콜백을 작성해봤다면 작성한 대부분의 콜백은 함수 표현식이었을 것이다.
자바스크립트에서 함수는 다른 값들과 거의 비슷하다. 할당 연산자의 right-hand에 올 수 있고, 또 다른 함수에 인자로도 넘겨질 수 있다.
위의 예제가 익명 함수 표현식이었다. 위의 함수는 function
키워드 뒤에 이름이 붙지 않았기 때문에 익명 함수인 것이다.
함수 표현식을 이름을 가질 수 있다. 이름붙은 함수 표현식에서 가장 잘 알려진 용례는 재귀이다.
var fibo = function fibonacci() {
// 여기서 fibonacci() 함수를 호출할 수 있다.
// 이 함수 표현식이 이름을 갖고 있기 때문이다.
}
// 여기서 fibonacci()를 호출하면 실패한다. 하지만 fibo()는 동작한다.
여기서 차이점은 함수 표현식이 fibonacci
라는 이름을 가졌다는 것이다.
이름을 가졌기 때문에 fibonacci
함수 내부에서 자신을 재귀적으로 호출할 수 있다.
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패턴의 키는 함수를 만들고 식으로 변환하고 즉시 실행하는 것이다.
다른 방식으로 함수 표현식을 만드는 방법을 살펴보자.
(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번 문체의 차이를 살펴보자.
()
괄호는 바깥 괄호 안에 포함된다.()
괄호는 함수 표현식을 위한 감싸는 괄호 바깥에 있다.위의 두 가지 문체는 널리 사용된다.
두 가지 문체가 작동하는 방식에서 약간 다르다. 하지만 실용적인 목적에서 아무 문체나 사용할 수 있다.
작동하는 예제와 작동하지 않는 2가지 예제를 살펴보자.
// 유효한 IIFE
(function initGameIIFE() {
// All your magical code to initialize the game!
}());
// 유효하지 않은 IIFE
function nonWorkingIIFE() {
// 이제 왜 앞뒤로 괄호가 필요한지 알게 될 것이다.
// 괄호 없이는 그냥 함수 정의이다. 표현식이 아니다.
// 문법 에러가 날 것이다.
}();
function () {
// 여기서도 문법 에러가 날 것이다.
}();
꼭 기억하자! IIFE를 구성하기 위해서는 함수 표현식이 필요하다. 함수 statement나 정의는 IIFE를 만드는데 절대 이용될 수 없다.
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로부터 반환 값이 필요하지 않다면, !
, +
, void
와 같은 단항 연산자를 이용한 첫 번째 문체의 IIFE를 계속 사용할 수도 있다.
하지만 다른 중요하고 강력한 IIFE의 기능 중 하나는 그들이 변수에 할당될 수 있는 값을 리턴할 수 있다는 데에 있다.
var result = (function () {
return "From IIFE";
}());
alert(result); // "From IIFE" 메시지를 출력합니다.
기본적으로 IIFE는 즉시 실행된다. 그리고 반환된 값은 result
변수에 할당된다.
이는 모듈 패턴 예제처럼 사용하게 될 정말 강력한 패턴이다.
IIFE는 값을 리턴할 수 있을 뿐만 아니라, 호출될 때 인자를 받을 수도 있다.
(function IIFE(msg, times) {
for (var i=1; i<=times; i++){
console.log(msg);
}
}("Hello!", 5));
IIFE
는 msg
, times
각각 두 개의 파라미터를 갖는다.()
대신에 인자(arguments)를 IIFE로 넘겼다.이것은 정말 강력한 패턴이다. 그리고 jQuery 코드와 여타 라이브러리에서 이러한 형식이 자주 사용된다.
(function($, global, document) {
// jQuery를 위해 $를 사용하고, window를 위해 global을 사용합니다.
}(jQuery, window, document));
위 예제 3번째 줄에서 jQuery, window, document
를 IIFE에 인자로 넘겼다.
IIFE내부의 코드는 $, global, document
를 각각 참조할 수 있다.
IIFE에 위의 파라미터를 넘김으로써 다음과 같은 이점을 얻을 수 있다.
document
를 넘겼을 때가 document
에 대한 로컬 스코프를 넘어 스코프 탐색을 하는 유일한 때이다. IIFE에서 document
로의 어떤 참조도 IIFE의 로컬 스코프 밖에서 찾아질 필요가 없다. jQuery
에도 동일하게 적용된다. IIFE코드가 간단한지 복잡한지에 기반하여 얻어지는 성능 향상은 크지 않지만 알아두면 좋은 트릭이다.추가적으로 설명하자면,
document
를 이미 파라미터로 넘겼기 때문에document.메소드()
또는document.property
의 형태에서 추가적으로 전역 객체를 찾아 돌아다니지 않는다는 의미이다. 기본적으로 IIFE에 저렇게 파라미터를 넘기지 않고document.메소드()
나document.프로퍼티
형태의 코드를 작성하면 IIFE는 내부엔 존재하지 않는document
라는 전역 객체를 찾아 돌아다닐 것이다.
document
나 jQuery
에 대한 직접적인 참조를 축소할 수 없다. 그들이 이 함수 스코프 밖에 있기 때문이다.minifier를 번역하다보니 억지로 축소기라고 번역했다고 함. 영어 그 자체 minifier로 생각함이 더욱 수월할 것이다.
IIFE와 클로져가 들어간 모듈 패턴을 알아보자.
클래식한 Sequence
싱글톤 객체를 구현할 것이다. 이 객체는 실수고 현재의 값이 오염되거나 하는 것 없이 제대로 작동한다.
어떤 일이 일어나는지 순차적으로 이해하기 우해, 2단계에 걸쳐 이 코드를 작성할 것이다.
var Sequence = (function sequenceIIFE() {
// 현재 counter 값을 저장하기 위한 Private 변수.
var current = 0;
// IIFE에서 반환되는 객체.
return {
};
}());
alert(typeof Sequence); // alert("Object");
current
라는 이름을 갖는 지역 변수를 만들었다.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
current
변수가 가진 값을 반환하는 getCurrentValue
함수를 추가했다.current
변수를 1 증가시키고 반환하는 getNextValue
함수를 추가했다.current
변수가 IIFE에서 private하기 때문에, 클로저를 통해 여기에 접근할 수 있는 함수 말고는 누구도 current
변수의 값에 접근하거나 그 값을 수정할 수 없다.
함수 표현식에서 앞뒤에 괄호를 해주는 이유는 기본적으로 함수가 statement의 형태가 아닌 식(expression)의 형태가 되도록 만들어주기 위함이다.
하지만 자바스크립트 엔진이 판단하기에 해당 코드가 명확히 함수 표현식이라면, 기술적으로 감싸는 괄호를 하지 않아도 될 것이다. 아래의 예제처럼.
var result = funciton() {
return "From IIFE!";
}();
위 예제에서 function
키워드는 statement의 첫 번째 단어가 아니다. 그래서 자바스크립트 엔진은 이걸 statement 또는 정의(definition)로 판단하지 않는다. 비슷하게 식이라는게 명확하다면, 다른 곳에도 괄호를 생략할 수 있는 곳들이 있다.
하지만 괄호를 적는 것이 좋다고 본다. 위의 경우처럼 괄호가 필요없는 경우에도 말이다. 괄호를 사용하는 것은 가독성을 높여준다. 읽는 사람이 첫 줄만 보고도 그 함수가 IIFE가 될 것이라는 걸 알 수 있게 문체적으로 넌지시 힌트를 준다. 그들은 그들이 읽는 것이 IIFE라는 것을 알아내기 위해 끝까지 스크롤을 넘길 필요가 없어진다.
IIFE는 코드가 더 정렬되고 더 아름답게 만드는 것을 돕지는 않는다. IIFE는 쓸데 없는 전역 변수를 만드는 것을 피함으로써 버그를 줄이는데 도움을 준다.