본문 바로가기

Javascript/Javascript

[Javascript] 즉시 실행 함수(IIFE)

728x90
반응형

 

 

 

IIFE란?

Immediately Invoked Function Expression의 줄임말로 정의되지마자 득시 실행되는 함수 표현식을 말한다.

// 아래 두 함수는 동일한 동작을 수행한다.
var app = function() {
    console.log('함수 호출');
};
app();  // '함수 호출'


// 즉시실행함수 IIFE
(function() {
    console.log('함수 호출');
}());  // '함수 호출'

 

위의 두 함수는 동일한 로직을 가지고 있다. IIFE는 전체 익명함수를 괄호로 감싸 줌으로써 내부 코드가 선언문이 아니라 표현식인 것처럼 Parser를 속인다. 단, 익명함수이기 때문에 재사용은 적합하지 않다. 물론 기명으로도 가능은 하다. 하지만 보통 함수의 이름을 짓는 것은 호출이 목적인 경우가 대부분인데, 한 번 자동으로 실행된 이후 생명을 다하는 IIFE의 이름일 지어주는 것은 의미가 없다. 어찌되었든 IIFE를 익명으로 써야 하는지, 기명으로 써야 하는지에 대해서는 개발자들 사이에서도 의견이 갈리는 듯 하다.

 

 


 

 

즉시 실행 함수 표현식 문법 종류

아래는 Javascript에서 사용되는 즉시 실행 함수 표현식의 3가지 문법을 보여준다.

// 아래 함수선언문은 아무것도 반환하지 않는다(혹은 undefined)
function() {
}

 

 

 

1) 논리 부정 연산자(!)

 

논리 부정 연산자(!)를 사용해서 즉시 실행 함수를 구현할 수 있다. 이렇게 하면 함수는 undefined를 반환하며 논리 부정 연산자에 의해 true 값을 가지기 때문에 즉시 실행이 되는 원리이다.

// 이런 표현식을 쓰면 !undefined는 true를 반환함. 그리고 바로 실행하기도 함. 단, 리턴값이 없어야함
!function() {
	console.log("This code runs immediately.");
}();

 

 

2) 괄호 안에서 괄호 문법

 

함수 선언문을 괄호 기호로 둘러싸고 함수를 호출하는 괄호를 붙이면 즉시 실행함수로 변환된다. (함수)();

(function() {
  console.log('IIFE syntax 1');
})();

 

 

3) Douglas 표기법

 

이는 Douglas Crockford가 권장하는 즉시 실행 함수 표현식의 표준 표기법이다. 위의 괄호 문법과의 차이점은 닫는 괄호 부분을 어디에 두느냐이다.

(function() {
  console.log('IIFE syntax 2');
}());

 

 

단, 일반적으로 자바스크립트 커뮤니티에서는 더글라스 표기법보다는 괄호 안에서 괄호 문법이 더 선호되며, JSLint와 같은 일부 도구에서는 더글라스 표기법을 사용할 때 경고를 발생시키기도 한다. 그러나 둘 다 괜찮은 방법이며 코드 작성 스타일에 따라 선택하면 된다.

 

 


 

 

즉시 실행 함수를 쓰는 이유

 

1. 클로저 구현 시

 

바로 클로저 변수가 적용된 클로저 함수를 얻고 싶을 때 쏠쏠히 사용된다.

let func = (function() {
  var counter = 0;
      
  return function(){ // 클로저
    return ++counter; 
  }
})();

func(); // 1
func(); // 2
func(); // 3

 

 

2. async / await 비동기 처리를 바로 사용할 때

 

await 키워드는 반드시 async function 내에서 쓸 수 있다는 제한 때문에 반드시 함수를 정의하고 함수를 호출하는 형식으로 사용해야 한다. 하지만 모든 코드를 함수로 묶고 호출하는 방식은 가독성 측면에서 좋지 않을 수 있다. 따라서 만약 이를 별도의 함수 호출이 아닌 일반 코드 실행과 같이 즉시 실행을 하고 싶은 경우 다음과 같이 async function을 정의하고 즉시 호출하기도 한다. 이는 정말 자주 사용되는 기법이기도 하다.

(async () => {
  ... 코드1
  await promise();
  ... 코드2
})();

 

cf) 다만 최근에 추가된 Top Level Await 기능 덕분에 굳이 async 즉시 실행 함수 문법을 쓰지 않고도 await 키워드를 사용할 수 있게 되었다.

 

 

3. 전역 변수 충돌을 방지하고 싶을 때

 

즉시 실행 함수의 진정한 장점은 스코프를 제한하고 전역 변수 충돌을 방지하는 데 사용되어 모듈 패턴을 구현하는 데 있다. IIFE를 사용하면 함수 내에서 변수(지역변수)를 정의하는 것과 같아 이러한 변수는 IIFE 내에서만 유효하게 된다. 따라서 IIFE를 사용하면 전역 스코프에서 변수 충돌 문제가 발생하지 않게 된다.

(function() {
  var x = 10;
  console.log(x); // Output: 10
})();

console.log(x); // Output: ReferenceError: x is not defined

 

위의 예시를 보면 var 키워드로 변수를 선언했지만 전역 변수 접근이 안 되는 걸 볼 수 있다. 이처럼 IIFE는 전역 스코프에 불필요한 변수를 추가하여 오염시키는 것을 방지할 수 있을 뿐 아니라 IIFE 내부로 다른 변수들이 접근하는 것을 막을 수도 있다. 따라서 일회성 변수가 필요하면 전역 변수로 선언하지 않고 안전하게 지역 변수로 다루어 해결이 가능해진다.

 

전역 변수뿐 아니라 전역 함수 충돌 역시 방지할 수도 있다. 예를 들어 IIFE를 사용하여 전역 스코프에서 함수를 정의하면 전역 스코프에서 함수 이름 충돌 문제를 방지할 수 있다.

// sayHello는 오로지 IIFE 블록 내에서만 사용이 가능하다
(function() {
  function sayHello(name) {
    console.log('Hello, ' + name + '!');
  }

  sayHello('John');
})();

 

IIFE를 사용하여 전역 스코프 충돌을 방지하는 것은 Javascript 개발에서 일반적인 패턴 중 하나다. 이 패턴은 모듈 패턴과 유사해서 자바스크립트 모듈의 시초이기도, 기본 뼈대이기도 하다.

 

 


 

 

IIFE와 세미콜론

일반적으로 자바스크립트 코드에서는 세미콜론(;)을 생략해도 실행하는 데 문제가 없다. 하지만 모든 경우에서 생략 가능한 것은 아니다. 특히 IIFE와 같이 사용할 때에는 세미콜론을 생략하지 말아야 한다. 그렇지 않으면 아래와 같은 오류가 발생하기 때문이다.

const a = 1 // 세미콜론 생략함

(function () {
  console.log(1)
})()

// Uncaught TypeError: 1 is not a function
//    at <anonymous>:3:1

 

위 코드를 보면 const a = 1에 세미콜론을 붙이지 않았음을 볼 수 있다. 일반적으로 const a = 1에 세미콜론을 안 붙였다고 해서 변수 a에 1이 할당되지 않는 것은 아니다. 문제는 그 다음에 이어지는 즉시 실행 함수 때문에 에러가 발생하는 것이다.

 

세미콜론이 없으면 자바스크립트 엔진은 다음 줄의 코드를 이전 줄과 연결하여 해석하려고 시도하게 된다. 그래서 이 경우에는 const a = 1 다음에 오는 (function() { console.log(1)})() 라는 즉시 실행 함수가 함수 호출을 의미하는 것이 아니라 함수 표현식을 감싼 괄호로 인식되게 된다. 따라서 위 코드는 const a = 1 곱하기 (function() { console.log(1)})() 로 해석되고, 이는 유효한 문법이 아니므로 에러가 발생하는 것이다.

 

 

 

자바스크립트 괄호 해석의 원리

 

갑자기 곱셈이 왜 나오느냐 하면, 자바스크립트에서는 괄호가 여러 의미를 가질 수 있기 때문이다. 예를 들어 괄호는 함수 호출을 의미할 수도 있고, 함수 표현식을 감싸는 역할을 할 수도 있고, 연산자 우선순위를 조절하는 역할을 할 수도 있다. 그런데 만약 괄호 앞에 세미콜론이 없다면 자바스크립트 엔진은 괄호를 함수 호출이 아니라 연산자 우선순위를 조절하는 역할로 해석해버린다.

 

예를 들어 2 * (3 + 4) 라는 코드가 있다면 자바스크립트 엔진은 괄호 안 3 + 4를 먼저 계산하고 그 결과에 2를 곱하는 방식으로 해석한다. 이때 괄호는 연산자 우선순위를 조절하는 역할을 한다. 그런데 만약 const a = 1 (function() { console.log(1)})()와 같은 세미콜론이 생략된 코드가 있다면 자바스크립트 엔진은 괄호를 함수 호출이 아니라 연산자 우선순위를 조절하는 역할로 해석하게 된다. 그래서 const a = 1 곱하기 (function() { console.log(1)})()로 해석하고 이는 유효한 문법이 아니므로 에러가 발생되는 것이다.

 

좀 더 이해하기 편하게 변수가 아닌 함수 선언식으로 비교해 보자. 변수 x는 엄연히 함수이다. 하지만 이 역시 실행하면 똑같은 에러가 발생하게 된다.

 var x = function () {
 } 
 
 (function () {
   console.log('hello')
 })()
 
// Uncaught TypeError: (intermediate value)(...) is not a function
//    at <anonymous>:6:4

 

이 역시 이전과 마찬가지로 자바스크립트 엔진은 IIFE를 앞의 코드와 연결하여 해석하기 때문에 var x = function(){} 곱하기 (function() { console.log('hello')})()로 해석하고 에러를 발생시킨 것이다. 이처럼 세미콜론의 유무에 따라 괄호의 의미가 달라질 수 있으므로 주의해야 한다.

 

 

 

세미콜론은 되도록 다 붙이자

 

정리하자면 IIFE와 일반 코드를 연달아 사용할 때는 IIFE 앞에 세미콜론을 붙여 주면 이러한 문제를 방지할 수 있다.

 

그런데 이처럼 복잡한 원리를 항상 머릿속에 인지하면서 프로그래밍을 하는 것보다, 여타 C언어와 자바 언어에서 반드시 한 라인의 코드가 끝나면 세미콜론을 붙여야 하는 것처럼 자바스크립트도 코딩할 때 세미콜론을 마지막에 붙여 주는 습관을 들이면 이러한 현상을 피해갈 수 있을 것이다. 

 

 

 

 

 

 

728x90
반응형