1. 즉시 실행 함수 표현식의 조건
IIFE는 2가지 조건을 갖는다.
- 즉시 실행해야 한다.
- 함수 표현식이어야 한다. ➡ 함수 선언문이 아닌
즉, 함수 표현식을 즉시 실행하는 것을 의미하며 해당 함수는 익명함수와 기명함수 모두 가능하다.
(function(){
console.log('IIFE');
})();
위의 코드에서 익명함수로 표현된 함수를 괄호로 감쌌는데, 괄호를 사용하지 않고 작성하게 되면 함수 표현식이 아니라 함수 선언문이 되기 때문에 괄호를 이용해 함수 표현식으로 만든 것이다.
2. 3가지 즉시 실행 함수 표현식 문법과 화살 표현식
IIFE는 3가지 형태로 표현할 수 있다.
2-1 논리 부정 연산자(!)
논리 부정 연산자를 이용해 IIFE를 구현할 수 있다.
이렇게 하면 함수는 undefined를 반환하며, 논리 부정 연산자에 의해 true값이 되기 때문에 즉시 실행이 되는 원리이다.
// 이 함수 선언문은 아무것도 반환하지 않는다.(혹은 undefined)
function() {
console.log("Runs immediately.");
}();
// 이런 표현식을 쓰면 !undefined는 true를 반환한다. 그리고 바로 실행하기도 함. 단, 리턴값이 없어야함
!function() {
console.log("Runs immediately.");
}();
2-2 괄호 안에서 괄호 문법
함수 선언문을 괄호로 감싸고 함수를 호출하는 괄호를 붙이면 즉시 실행 함수로 변환된다.
(function)(); 이런 방식
(function() {
console.log('IIFE');
})();
2-3 더글라스 표기법
Douglas Crockford가 권장하는 즉시 실행 함수 표현식의 표준 표기법이다.
위의 괄호 문법과의 차이점은 닫는 괄호 부분을 어디에 두느냐의 차이이다.
(function(){
console.log('IIFE');
}());
일반적으로는 괄호 안에서 괄호 문법이 더 선호되며, JSLint와 같은 일부 도구에서는 더글라스 표기법을 사용할 때 경고를 발생시키기도 한다.
그냥 코드 작성 스타일에 따라 선택해서 작성하면 된다.
2-4 화살표 함수(Arrow Function) 표현
ES2015(ES6)에서 등장한 화살표 함수를 사용해 IIFE를 만들 수 있다.
(() => console.log('IIFE'))();
하지만, 화살표 함수를 이용하게 된다면 아래와 같은 방식으로는 사용할 수 없다.
(() => console.log('IIFE')());
이 코드가 동작하지 않는 이유는 함수를 호출하기 위해서는 호출대상이 명세에서 말하는 MemberExpression이어야 한다.
그러나 화살표 함수는 Assignment Expression이기 때문에 불가능한 것이다.
참고 링크
https://stackoverflow.com/questions/34589488/es6-immediately-invoked-arrow-function/34589765#34589765
3. 사용 이유
IIFE를 사용하는 이유는, 보통은 전역 스코프(Global Scope)를 오염시키지 않기 위해 사용한다.
변수를 전역 스코프에 선언하는 것을 피하기 위함이다.
사용하는 상황은 아래와 같다.
- 클로저를 구현할 때
- async / await 비동기 처리를 바로 사용할 때
- 전역 변수 충돌을 방지할 때
3-1 클로저를 구현할 때
클로저 안에 선언된 클로저 변수가 적용된 클로저 함수를 얻을 때 사용한다.
private 데이터를 만들어 외부에서 접근을 제한할 때 사용.
let func = (function() {
var counter = 0;
return function(){ // 클로저
return ++counter;
}
})();
func(); // 1
func(); // 2
func(); // 3
3-2 async / await 비동기 처리를 바로 사용할 때
Top Level Await 기능을 제외한 await 키워드는 async function 내에서만 사용할 수 있기 때문에 함수를 정의하고 함수를 호출하는 형식으로 사용해야 한다.
하지만, 모든 코드를 함수로 묶고 호출하는 방법은 가독성 측면에서 좋지 않을 수 있다.
이를 별도의 함수 호출이 아닌 일반 코드 실행과 같이 즉시 실행 하고 싶은 경우 다음과 같이 async function을 정의해 즉시 호출할 수 있다. 정말 자주 사용하는 기법이기도 하다.
(async () => {
... code1
await promise();
... code2
})();
ES2022에서 Top-Level await 기능이 등장하며 async이 없이 모듈의 최상위 레벨에서 await를 사용할 수 있게 되었다.
3-3 전역 변수 충돌을 방지할 때
즉시 실행 함수의 진정한 장점은 스코프를 제한하고 전역 변수 충돌을 방지하는 데 사용되어 모듈 패턴을 구현하는 데 있다.
IIFE를 사용하면 함수 내에서 지역 변수를 정의하는 것과 같아, 이런 변수는 IIFE 내에서만 유효하게 된다.
즉, IIFE를 사용해 전역 스코프에서 변수 충돌 문제가 발생하지 않는다.
(function() {
var age = 24;
console.log(age); // Output: 24
})();
console.log(age); // Output: ReferenceError: age is not defined
위의 코드에서 var 키워드로 변수를 선언했지만 전역 변수 접근은 안된다.
이처럼 IIFE는 전역 스코프에 불필요한 변수를 추가해서 오염시키는 것을 방지할 수 있을 뿐 아니라 IIFE 내부 안으로 다른 변수들의 접근을 막을 수 있다.
여기서 블럭 스코프를 이용하는 let과 const 키워드를 사용한다 생각해 보면,
var 키워드는 함수 스코프를 이용하기 때문에 함수 스코프에 쌓여 있지 않으면
최상단 스코프로 호이스팅 되기 때문에 전역 변수 충돌이 발생하는 것 같다.
let과 const를 사용하면 전역 변수 충돌이 발생하지 않기에 IIFE를 사용하지 않아도
전역 변수 충돌을 막을 수 있다고 생각한다.
전역 변수뿐만 아니라 전역 함수 충돌 역시 방지할 수 있다.
IIFE를 사용하여 전역 스코프에서 함수를 정의하면 전역 스코프에서 함수 이름 충돌 문제를 방지할 수 있다.
// sayHello는 오로지 IIFE 블록 내에서만 사용이 가능하다
(function() {
function sayHello(name) {
console.log('Hello, ' + name + '!');
}
sayHello('John');
})();
IIFE를 사용하여 전역 스코프 충돌을 방지하는 것은 Javascript에서는 일반적인 패턴 중 하나이다.
이 패턴은 모듈 패턴과 유사해서 JS 모듈의 시초와 기본 뼈대 이기도 하다.
jQuery의 경우 $라는 전역 변수를 갖는데 이를 갖는 또 다른 라이브러리가 있다면 해결할 수 있는 방법을 제공한다.window.$ = funciton(){}; (function($){...})(jQuery);
세미콜론 주의보
일반적으로 Javascript에서 세미콜론(;)을 생략해도 실행에는 큰 문제가 없다.
하지만 모든 경우는 아니다.
IIFE와 같이 사용할 경우는 세미콜론 생략을 하지 말아야 한다.
const a = 1 // 세미콜론 생략함
(function () {
console.log(1)
})()
// Uncaught TypeError: 1 is not a function at <anonymous>:3:1
위의 코드에서 const a = 1에서 세미콜론이 없는 모습을 볼 수 있다.
일반적으로는 세미콜론을 생략해도 문제가 없다.
이 코드에서도 a에 1이 할당되지 않은 것은 아니다.
에러는 다음에 이어지는 IIFE에서 발생한다.
세미콜론이 없다면 JS 엔진은 다음 줄의 코드를 이전 줄과 연결해 해석하려 한다.
그래서 이 경우에는 const a = 1 다음에 오는 IIFE 가 함수 호출을 의미하는 것이 아니라 함수 표현식을 감싼 괄호로 인식하게 된다.
이렇게 해석된다면 const a = 1 곱하기 (function (){console.log(1)})() 로 해석되고, 이는 유효한 문법이 아니므로 에러가 발생한다.
JS에서 괄호는 함수 호출 역할, 함수 표현식을 감싸는 역활, 연산자 우선순위 조절 역할을 할 수 있다.
그런데 만약 괄호 앞에 세미콜론이 없다면,
JS엔진은 괄호를 함수 호출이 아니라 연산자 우선순위를 조절하는 역할로 해석한다.
정리
1. IIFE는 두가지 조건을 갖는다.
- 즉시 실행해야 한다.
- 함수 표현식이어야 한다. ➡ 함수 선언문이 아닌
2. 4가지 문법으로 IIFE를 표현할 수 있다.
- 논리 부정 연산자(!)
- 괄호 안에서 괄호 문법
- 더글라스 표기법
- 화살표 표기법
3. 3가지 사용 이유가 있다.
- 클로저 구현으로 클로저 변수를 private하게 사용할 때
- async / await 비동기 처리를 바로 할 때 (이는 Top-Level Await 기능이 추가되며 어느정도 완화)
- 전역 변수 충돌을 방지할 때
4. IIFE를 사용할 때는 될 수 있다면 모든 코드에 세미콜론(;)을 사용하자
참고 링크
- https://github.com/baeharam/Must-Know-About-Frontend/blob/main/Notes/javascript/iife.md#%EC%B5%9C%EC%86%8C%ED%99%94minification%EB%A5%BC-%ED%99%9C%EC%9A%A9%ED%95%9C-%EC%B5%9C%EC%A0%81%ED%99%94optimization
- https://inpa.tistory.com/entry/JS-%F0%9F%93%9A-IIFE-%EC%A6%89%EC%8B%9C%EC%8B%A4%ED%96%89-%ED%95%A8%EC%88%98-%ED%91%9C%ED%98%84%EC%8B%9D#1._%ED%81%B4%EB%A1%9C%EC%A0%80_%EA%B5%AC%ED%98%84%ED%95%A0%EB%95%8C
- https://fe-developers.kakaoent.com/2022/220728-es2022/#1-top-level-await
- https://im-developer.tistory.com/76
'JavaScript > Javascript' 카테고리의 다른 글
Javascript의 메모리누수와 가비지 컬렉션 (0) | 2023.10.06 |
---|---|
콜 스택(Call stack)과 힙(Heap) (0) | 2023.10.06 |
변수, var 그리고 블럭 스코프를 갖는 let, const (0) | 2023.10.02 |
네이티브 객체와 호스트 객체 (0) | 2023.09.26 |
클로저 Closure (1) | 2023.09.25 |