이 포스팅은 https://medium.com/@vvkchandra/essential-javascript-mastering-immediately-invoked-function-expressions-67791338ddc6 에 있는 포스팅들을 번역한 것입니다. 오역이나 의역이 있을 수 있습니다. 지적해주시면 확인 후 바로 정정하겠습니다.
original source of this posting is from https://medium.com/@vvkchandra/essential-javascript-mastering-immediately-invoked-function-expressions-67791338ddc6 If the original author requests deletion, it will be deleted immediately.
자바스크립트 개발자라면 알아야 할 33가지 개념 #8 자바스크립트 필수요소 : IIFE 마스터하기
함수를 확실하게 이해하고, 현대적이고 깔끔한 자바스크립트 코드로 작성할 수 있는 방법을 배우는 것은 자바스크립트 닌자가 되기 위한 필수 스킬입니다.
자바스크립트 함수와 함께 자주 사용되는 코딩 패턴 중 하나는 Immediately-invoked Function Expression이라는 멋진 이름을 갖고 있습니다. IIFE 로 잘 알려져 있고 발음할 때는 "iffy" 처럼 발음합니다.
IIFE가 무엇인지 왜 필요한지 이해하기 전에, 자바스크립트 함수에 관한 핵심적인 몇가지 개념들을 빠르게 다시 짚고 넘어갈 필요가 있습니다.
자바스크립트를 처음 접하는 개발자들은 함수를 다룰 때 다음 문법이 편할 것입니다.
function sayHi() {
alert("Hello, World!");
}
sayHi();
이렇게 함수를 생성하는 방식은 "a function definition" 또는 "a function declaration" 또는 "a function statement"로 불립니다. 일반적으로, 다른 인기있는 프로그래밍 언어의 문법과 닮았기 때문에, 자바스크립트를 처음 접하는 개발자도 이 문법을 사용하는데 별다른 문제를 겪지 않습니다.
이 함수 정의는 항상 function 키워드로 시작합니다. 그리고 뒤에는 함수의 이름이 따라옵니다. 함수의 이름을 생략하면 문법에 어긋나기 때문에 이름을 생략할 수 없습니다.
이제 자바스크립트에 관한 것들이 재밌어질 때입니다. 함수 표현식이 어떻게 생겼는지 살펴봅시다.
var msg = "Hello, World!";
var sayHi = function() {
alert(msg);
};
sayHi(); // 브라우저에서 "Hello, World!"라는 alert 메시지를 띄웁니다.
이 간단한 예제는 우리 자바스크립트 스킬이 다음 레벨로 넘어가도록 도와줄 수 있습니다.
1번째 줄은 이해하기 매우 쉽습니다. 하지만 개발자들이 2-4번째 줄을 처음 볼 때, 자바같은 프로그래밍 언어만 경험한 개발자들에게는 그 코드들은 예상을 벗어납니다.
기본적으로, 2-4번째 줄에서는 함수 타입의 값을 sayHi 라는 변수에 할당했습니다.
위의 예제에서, 할당의 right-hand에 있는 함수는 주로 "함수 표현식(Function Expression)" 이라 불립니다. 자바스크립트 내 어디든 있습니다. 콜백을 작성해봤다면 작성한 대부분의 콜백은 함수 표현식이었을 것입니다.
여러분은 아마 깊은 이해 없이 함수 표현식 자체는 많이 사용해봤을 것입니다. 하지만 함수 표현식을 마스터하는 것은 여러분에게 어떤 비밀스런 자바스크립트 초능력을 줄 것입니다.
그래서 여기서 기억할 중요한 개념은 자바스크립트에서 함수는 다른 값들과 거의 비슷하다는 것입니다. 할당 연산자의 right-hand 에도 올 수 있고, 또 다른 함수에 인자로도 넘겨질 수 있습니다.
아마 익명 함수 표현식이 무엇인지 이미 알고 있는 사람들도 있을 것입니다. 위의 예제가 익명 함수 표현식이었습니다. 위의 함수는 function 키워드 뒤에 이름이 붙지 않기 때문에 익명함수입니다.
함수 표현식은 이름을 가질 수 있습니다. 이름붙은 함수 표현식에서 가장 지루하고 가장 잘 알려진 용례는 재귀입니다. 하지만 지금은 걱정하지마세요. 이름붙은 함수 표현식의 이해 없이도 IIFE를 마스터하는 것은 가능합니다.
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를 다시 한번 보여줄 수 없습니다.
이 함수는 생명을 갖자마자 바로 죽어버립니다.
이제 직관적이지 않은 문법에 대해서 이해해봅시다. 첫번째 줄에 "!" 가 있던 것을 보셨을 겁니다. 만일 못보셨으면, 지금 보시면 됩니다.
그래서 우리는 생기자마자 바로 호출되는 함수 표현식을 얻었습니다. 우리 친구는 IIFE라 불리고 어떤 문체의 방식으로 쓰여지는지 상관 없이 효력을 발휘합니다.
위의 문체의 방식은 "!" 외에도 "+", "-", ~ 등 다양한 방식으로 작성해도 같은 결과를 보입니다. 1진 연산자면 아무 거나 이용해도 됩니다. 결국 목적은 뒤에 있는 함수를 식으로 만드는 것이니까요.
이 부분이 잘 이해가 안가신다면, 제가 이전에 썼던 포스팅 을 참조하시면 도움이 좀 될 겁니다.
이제 콘솔에서 한번 코드를 작성해봅시다! IIFE와 재미있게 놀아봅시다.
첫번째 문자 "!" 는 작성한 함수가 statement나 정의(definition)가 되지 않게 표현식으로 만들고 그 후에 우리는 그 함수를 즉시 실행하는 것입니다.
아래에 보이는 다른 문체로 작성된 코드를 봅시다.
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번째 문체의 차이를 알아채는 것은 매우 어렵습니다. 그래서 제가 설명해드리겠습니다.
첫번째 문체 4번째 줄에서, 함수 식을 호출하기 위한 () 괄호는 바깥 괄호 안에 포함됩니다. 또, 다시 바깥 괄호가 바깥 함수를 함수식으로 만들기 위해서 필요합니다.
두번째 문체 9번째 줄에서, 함수 식을 호출하기 위한 () 괄호는 함수 표현식을 위한 감싸는 괄호 밖에 있습니다.
두가지 문체가 널리 사용됩니다. 저자는 첫번째 문체를 선호합니다. (역자는 두번째 문체를 선호합니다.) 핵심을 보자면, 두가지 문체가 작동하는 방식에서 약간 다릅니다. 하지만 실용적인 목적에서 그리고 이미 길어진 이 튜토리얼을 짧게 유지하기 위해, 좋아하는 것 아무거나 쓸 수 있다고 말하겠습니다. (미래에, 작성될 뉘앙스를 분명히 하기 위한 () 연산자와 컴마 연산자에 대한 아티클을 링크하겠습니다.)
이쯤에서 작동하는 다음 예제와 작동하지 않는 2가지 예제를 보는 것으로 마무리 지읍시다. 우리는 지금부터 우리 IIFE의 이름을 지을 것입니다. 익명 함수를 사용하는 것은 그다지 좋은 아이디어가 아니기 때문입니다.
// 유효한 IIFE
(function initGameIIFE() {
// All your magical code to initialize the game!
}());
// 유효하지 않은 IIFE
function nonWorkingIIFE() {
// 이제 왜 앞뒤로 괄호가 필요한지 알게 될 것입니다.
// 괄호 없이는 그냥 함수 정의입니다. 표현식이 아닙니다.
// 문법 에러가 날 것입니다.
}();
function () {
// 여기서도 문법 에러가 날 것입니다.
}();
이제 왜 IIFE를 만들기 위해서 괄호로 감싸진 이상한 문법이 필요한지 알게 되었을 것입니다.
꼭 기억하세요! 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 내부에 두개의 변수를 선언했습니다. 그리고 2개의 변수는 IIFE에 private합니다. IIFE 밖의 어느 누구도 그 변수들에 접근할 수 없습니다. 비슷하게, 우리는 init 함수가 있고 그 안에 있는 변수들은 IIFE 밖에서 누구도 접근할 수 없습니다. 하지만 init 함수에서는 바깥 변수에 접근 가능합니다.
코드 바깥에서는 사용하지 않는 많은 변수와 함수를 전역에 만들 때마다, 변수와 함수들 모두를 IIFE로 감싸고 그럼으로써 좋은 자바스크립트 카르마를 얻으세요. 코드는 계속 동작할 것이지만 전역 스코프는 오염하지 않습니다. 그리고 그럼으로써 또 코드에서 전역 스코프를 실수로 혹은 의도적으로 수정하는 다른 누군가로부터 코드를 보호할 수 있습니다.
우리가 모듈 패턴을 볼 때, 어떻게 이 private 변수들에 IIFE밖으로 나가는 특수한 권한을 가진, 제어된 접근권한을 주는지에 대해 설명할 것입니다. 그러니 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));
이건 정말 강력한 패턴입니다. 그리고 jQuery 코드와 여타 라이브러리에서 이러한 형식이 자주 사용됩니다.
(function($, global, document) {
// jQuery를 위해 $를 사용하고, window를 위해 global을 사용합니다.
}(jQuery, window, document));
위의 예제 3번째 줄에서, 우리는 jQuery, window, document를 IIFE에 인자로 넘겼습니다. IIFE 내부의 코드는 $, global, document를 각각 참조할 수 있습니다.
IIFE에 위의 파라미터를 넘김으로써 다음과 같은 이점을 얻을 수 있습니다.
자바스크립트는 항상 현재 함수의 스코프부터 식별자(ID)를 찾을 때까지 계속 더 높은 레벨의 스코프로 올라가며 식별자를 찾아다닙니다. 3번째 줄에서 우리가 document를 넘겼을 때가, document에 대한 로컬 스코프를 너머 스코프 탐색을 하는 유일한 때입니다. IIFE에서 document로의 어떤 참조도 IIFE의 로컬 스코프 밖에서 찾아질 필요가 없습니다. jQuery에도 동일하게 적용됩니다. IIFE 코드가 간단한지 복잡한지에 기반하여 얻어지는 성능 향상은 크지 않지만 알아두면 좋은 트릭입니다.
역자 주: 헷갈리실까봐 추가적으로 설명하자면, document를 이미 파라미터로 넘겼기 때문에 document.메소드() 또는 document.property 의 형태에서 추가적으로 전역 객체를 찾아 돌아다니지 않는다는 의미입니다. 기본적으로 IIFE에 저렇게 파라미터를 넘기지 않고 document.메소드()나 document.프로퍼티 형태의 코드를 작성하면 IIFE는 내부엔 존재하지 않는 document라는 전역 객체를 찾아 돌아다닐 것입니다.
또한 자바스크립트 축소기(minifiers)는 안전하게 함수 안에 선언된 파라미터의 이름을 축소할 수 있습니다. 우리가 만일 이러한 파라미터를 넘기지 않는다면, 축소기는 document나 jQuery에 대한 직접적인 참조를 축소할 수 없습니다. 왜냐하면 그들이 이 함수 스코프 밖에 있으니까요.
역자 주: 축소기는 억지로 번역하다보니 minifier로 번역했습니다. 영어 그 자체 minifier로 생각하심이 더욱 수월할 겁니다.
이제 자바스크립트 IIFE를 마스터했습니다. 이제 IIFE와 클로져가 들어간 끝내주는 모듈 패턴을 봅시다.
우리는 클래식한 Sequence 싱글톤 객체를 구현할 것입니다. 이 객체는 실수로 현재의 값이 오염되거나 하는 것 없이 제대로 작동합니다.
우리는 어떤 일이 일어나는지 순차적으로 이해하기 위해, 2단계에 걸쳐 이 코드를 작성할 것입니다.
var Sequence = (function sequenceIIFE() {
// 현재 counter 값을 저장하기 위한 Private 변수입니다.
var current = 0;
// IIFE에서 반환되는 객체입니다.
return {
};
}());
alert(typeof Sequence); // alert("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 변수가 IIFE에서 private하기 때문에, 클로져를 통해 여기에 접근할 수 있는 함수 말고는 누구도 current 변수의 값에 접근하거나 그 값을 수정할 수 없습니다.
클로져를 마스터하고 싶으시다면, 저자의 클로져 튜토리얼인 Learning JavaScript Closures through the Laws of Karma.를 읽어보세요.
함수 표현식에서 앞뒤에 괄호를 해주는 이유는 기본적으로 함수가 statement의 형태가 아닌 식(expression)의 형태가 되도록 만들어주기 위함입니다.
하지만 자바스크립트 엔진이 판단하기에 해당 코드가 명확히 함수 표현식이라면, 우리는 기술적으로 감싸는 괄호를 하지 않아도 될 것입니다. 아래의 예제처럼요.
var result = funciton() {
return "From IIFE!";
}();
위의 예제에서, function 키워드는 statement의 첫번째 단어가 아닙니다. 그래서 자바스크립트 엔진은 이걸 statement 또는 정의(definition)로 판단하지 않습니다. 비슷하게, 식이라는게 명확하다면, 다른 곳에도 괄호를 생략할 수 있는 곳들이 있습니다.
하지만 저자는 언제나 괄호를 적는 것을 선호합니다. 위의 경우처럼 괄호가 필요 없는 경우까지요. 괄호를 사용하는 것은 가독성을 높여줍니다. 읽는 사람이 첫 줄만 보고도 그 함수가 IIFE가 될 것이라는 것을 알 수 있게 문체적으로 넌지시 힌트를 줍니다. 그들은 그들이 읽는 것이 IIFE라는 것을 알아내기 위해 끝까지 스크롤을 넘길 필요가 없습니다.
이제 IIFE를 코드에 적용시키기 위해 알아야 할 모든 것을 살펴봤습니다. IIFE는 우리의 코드를 더 정렬되고 더 아름답게 만드는 것을 돕진 않습니다. IIFE는 쓸데 없는 전역 변수를 만드는 것을 피함으로써 버그를 줄이는데 도움을 줍니다. 이제 당신은 인증된 IIFE 닌자입니다.
저자는 이러한 자바스크립트 튜토리얼을 쓰는 것을 즐깁니다. 이러한 글을 쓰는 것은 매우 재밌습니다. 하지만 퇴고하고 퍼블리시하는데는 많은 시간이 걸립니다. 만일 이 튜토리얼이 유용하다고 느꼈다면 하트 버튼을 클릭해주세요.
저자의 원문은 여기 있으니 가서 하트버튼을 한번 클릭해줍시다.
굉장히 신기하고 유익하고 재밌네요.
함수 표현식도 제대로 이해하지 못했었는데, 감사합니다.