6. 함수 심화학습(1)
6.1 재귀와 스택
요약
재귀(recursion) – 함수 내부에서 자기 자신을 호출하는 것을 나타내는 프로그래밍 용어입니다. 재귀 함수는 우아하게 원하는 문제를 해결할 때 자주 쓰이곤 합니다.
함수가 자신을 호출하는 단계를 재귀 단계(recursion step) 라고 부릅니다. basis라고도 불리는 재귀의 베이스(base) 는 작업을 아주 간단하게 만들어서 함수가 더 이상은 서브 호출을 만들지 않게 해주는 인수입니다.재귀적으로 정의된 자료 구조는 자기 자신을 이용해 자료 구조를 정의합니다.
재귀적으로 정의된 자료구조에 속하는 연결 리스트는 리스트 혹은 null을 참조하는 객체로 이루어진 데이터 구조를 사용해 정의됩니다.
list = {value, next -> list}
HTML 문서의 HTML 요소 트리나 위에서 다룬 부서를 나타내는 트리 역시 재귀적인 자료 구조로 만들었습니다. 이렇게 재귀적인 자료 구조를 사용하면 가지가 여러 개인데 각 가지가 여러 가지로 뻗쳐 나가는 형태로 자료 구조를 만들 수 있습니다.
예시에서 구현한sumSalary
같은 재귀 함수를 사용하면 각 분기(가지)를 순회할 수 있습니다.모든 재귀 함수는 반복문을 사용한 함수로 다시 작성할 수 있습니다. 최적화를 위해 반복문으로 다시 작성해야 할 수도 있죠. 그러나 상당수 작업은 재귀를 사용해도 만족할 만큼 빠르게 동작합니다. 재귀를 사용하면 구현과 유지보수가 쉽다는 장점도 있습니다.
- 문제 해결을 하다 보면 함수에서 다른 함수를 호출해야 할 때가 있습니다. 이때 함수가 자기 자신을 호출할 수도 있는데, 이를 재귀 라고 부릅니다.
두 가지 사고방식
- 간단한 예시를 시작으로 재귀에 대해 알아보겠습니다. x를 n 제곱해 주는 함수 pow(x, n)를 만들어봅시다. pow(x, n)는 x를 n번 곱해주기 때문에 아래 결과를 만족해야 합니다.
1. 반복적인 사고를 통한 방법: for 루프
function pow(x, n) {
let result = 1;
// 반복문을 돌면서 x를 n번 곱함
for (let i = 0; i < n; i++) {
result *= x;
}
return result;
}
alert( pow(2, 3) ); // 8
2. 재귀적인 사고를 통한 방법: 작업을 단순화하고 자기 자신을 호출함
function pow(x, n) {
if (n == 1) {
return x;
} else {
return x * pow(x, n - 1);
}
}
alert( pow(2, 3) ); // 8
- n == 1일 때:
모든 절차가 간단해집니다. 명확한 결괏값을 즉시 도출하므로 이를 재귀의 베이스(base) 라고 합니다. pow(x, 1)는 x 입니다. - n == 1이 아닐 때:
pow(x, n)은 x pow(x, n - 1)으로 표현할 수 있습니다. 수학식으론 xn = x xn-1로 표현할 수 있겠죠. 이를 재귀 단계(recursive step) 라고 부릅니다. 여기선 목표 작업 pow(x, n)을 간단한 동작(x를 곱하기)과 목표 작업을 변형한 작업(pow(x, n - 1))으로 분할하였습니다. 재귀 단계는 n이 1이 될 때까지 계속 이어집니다.
- pow (2, 4)를 계산하려면 아래와 같은 재귀 단계가 차례대로 이어집니다.
- pow(2, 4) = 2 * pow(2, 3)
- pow(2, 3) = 2 * pow(2, 2)
- pow(2, 2) = 2 * pow(2, 1)
- pow(2, 1) = 2
- if 대신 조건부 연산자 ?를 사용하면 pow (x, n)를 더 간결하고 읽기 쉽게 만들 수도 있습니다
function pow(x, n) {
return (n == 1) ? x : (x * pow(x, n - 1));
}
- 재귀 깊이 제한 때문에 재귀를 실제 적용하는데 제약이 있긴 하지만, 재귀는 여전히 광범위하게 사용되고 있습니다. 재귀를 사용하면, 간결하고 유지보수가 쉬운 코드를 만들 수 있기 때문입니다.
실행 컨텍스트와 스택
- 실행 중인 함수의 실행 절차에 대한 정보는 해당 함수의 실행 컨텍스트(execution context) 에 저장됩니다.
- 실행 컨텍스트는 함수 실행에 대한 세부 정보를 담고 있는 내부 데이터 구조입니다. 제어 흐름의 현재 위치, 변수의 현재 값, this의 값(여기선 다루지 않음) 등 상세 내부 정보가 실행 컨텍스트에 저장됩니다.
- 함수 호출 일 회당 정확히 하나의 실행 컨텍스트가 생성됩니다.
- 함수 내부에 중첩 호출이 있을 때는 아래와 같은 절차가 수행됩니다.
- 현재 함수의 실행이 일시 중지됩니다.
- 중지된 함수와 연관된 실행 컨텍스트는 실행 컨텍스트 스택(execution context stack) 이라는 특별한 자료 구조에 저장됩니다.
- 중첩 호출이 실행됩니다.
- 중첩 호출 실행이 끝난 이후 실행 컨텍스트 스택에서 일시 중단한 함수의 실행 컨텍스트를 꺼내오고, 중단한 함수의 실행을 다시 이어갑니다.
- 재귀를 이용해 작성한 코드는 반복문을 사용한 코드로 다시 작성할 수 있습니다. 반복문을 사용하면 대개 함수 호출의 비용(메모리 사용)이 절약됩니다.
재귀적 순회
- 임직원 배열 을 가진 ‘단순한’ 부서 – 간단한 반복문으로 급여 합계를 구할 수 있습니다.
- N개의 하위 부서가 있는 객체 – 각 하위 부서에 속한 임직원의 급여 합계를 얻기 위해 N번의 재귀 호출을 하고, 최종적으로 모든 하위부서 임직원의 급여를 더합니다.
let company = { // 동일한 객체(간결성을 위해 약간 압축함)
sales: [{name: 'John', salary: 1000}, {name: 'Alice', salary: 1600 }],
development: {
sites: [{name: 'Peter', salary: 2000}, {name: 'Alex', salary: 1800 }],
internals: [{name: 'Jack', salary: 1300}]
}
};
// 급여 합계를 구해주는 함수
function sumSalaries(department) {
if (Array.isArray(department)) { // 첫 번째 경우
return department.reduce((prev, current) => prev + current.salary, 0); // 배열의 요소를 합함
} else { // 두 번째 경우
let sum = 0;
for (let subdep of Object.values(department)) {
sum += sumSalaries(subdep); // 재귀 호출로 각 하위 부서 임직원의 급여 총합을 구함
}
return sum;
}
}
alert(sumSalaries(company)); // 7700
- 짧고 이해하기 쉬운 코드로 원하는 기능을 구현하였습니다. 재귀의 강력함은 여기에 있습니다. 하위 부서의 깊이와 상관없이 원하는 값을 구할 수 있게 되었네요.
재귀적 구조
연결 리스트
value
next
: 다음 연결 리스트 요소를 참조하는 프로퍼티. 다음 요소가 없을 땐 null이 됩니다.
let list = {
value: 1,
next: {
value: 2,
next: {
value: 3,
next: {
value: 4,
next: null
}
}
}
};
let list = { value: 1 };
list.next = { value: 2 };
list.next.next = { value: 3 };
list.next.next.next = { value: 4 };
list.next.next.next.next = null;
- 나누기
let secondList = list.next.next;
list.next.next = null;
-
합치기
list.next.next = secondList;
-
맨 앞에 추가하기
let list = { value: 1 };
list.next = { value: 2 };
list.next.next = { value: 3 };
list.next.next.next = { value: 4 };
// list에 새로운 value를 추가합니다.
list = { value: "new item", next: list };
- 중간 요소 제거
list.next = list.next.next;
6.2 나머지 매개변수와 전개 문법
요약
"..."은 나머지 매개변수나 전개 문법으로 사용됩니다.
나머지 매개변수와 전개 문법은 아래의 방법으로 구분할 수 있습니다.
...
이 함수 매개변수의 끝에 있으면 인수 목록의 나머지를 배열로 모아주는 '나머지 매개변수’입니다....
이 함수 호출 시 사용되면 배열을 목록으로 확장해주는 '전개 문법’입니다.사용 패턴:
- 인수 개수에 제한이 없는 함수를 만들 때 나머지 매개변수를 사용합니다.
- 다수의 인수를 받는 함수에 배열을 전달할 때 전개 문법을 사용합니다.
둘을 함께 사용하면 매개변수 목록과 배열 간 전환을 쉽게 할 수 있습니다.
조금 오래된 방법이긴 하지만 arguments라는 반복 가능한 유사 배열 객체를 사용해도 인수 모두를 사용할 수 있습니다.
나머지 매개변수 ...
- 함수 정의 방법과 상관없이 함수에 넘겨주는 인수의 개수엔 제약이 없습니다.
- 이렇게 여분의 매개변수는 그 값들을 담을 배열 이름을 마침표 세 개
...
뒤에 붙여주면 함수 선언부에 포함시킬 수 있습니다. 이때 마침표 세 개...
는 "나머지 매개변수들을 한데 모아 배열에 집어넣어라."는 것을 의미합니다.
function sumAll(...args) { // args는 배열의 이름입니다.
let sum = 0;
for (let arg of args) sum += arg;
return sum;
}
alert( sumAll(1) ); // 1
alert( sumAll(1, 2) ); // 3
alert( sumAll(1, 2, 3) ); // 6
function showName(firstName, lastName, ...titles) {
alert( firstName + ' ' + lastName ); // Julius Caesar
// 나머지 인수들은 배열 titles의 요소가 됩니다.
// titles = ["Consul", "Imperator"]
alert( titles[0] ); // Consul
alert( titles[1] ); // Imperator
alert( titles.length ); // 2
}
showName("Julius", "Caesar", "Consul", "Imperator");
나머지 매개변수
...rest
는 항상 마지막에 있어야 합니다.
‘arguments’ 변수
- arguemnts라는 특별한 유사 배열 객체(array-like object)를 이용하면 인덱스를 사용해 모든 인수에 접근할 수 있습니다.
function showName() {
alert( arguments.length );
alert( arguments[0] );
alert( arguments[1] );
// arguments는 이터러블 객체이기 때문에
// for(let arg of arguments) alert(arg); 를 사용해 인수를 나열할 수 있습니다.
}
// 2, Julius, Caesar가 출력됨
showName("Julius", "Caesar");
// 1, Bora, undefined가 출력됨(두 번째 인수는 없음)
showName("Bora");
화살표 함수에는 \'arguments\'가 없습니다.
화살표 함수에서 arguments 객체에 접근하면, 외부에 있는 ‘일반’ 함수의 arguments 객체를 가져옵니다.
화살표 함수는 자체 this를 가지지 않기 때문입니다.
spread 문법
- 함수를 호출할 때
... arr
를 사용하면, 이터러블 객체arr
이 인수 목록으로 '확장’됩니다.
let arr = [3, 5, 1];
alert( Math.max(...arr) ); // 5 (전개 문법이 배열을 인수 목록으로 바꿔주었습니다.)
let arr1 = [1, -2, 3, 4];
let arr2 = [8, 3, -8, 1];
alert( Math.max(1, ...arr1, 2, ...arr2, 25) ); // 25
let arr = [3, 5, 1];
let arr2 = [8, 9, 15];
let merged = [0, ...arr, 2, ...arr2];
alert(merged); // 0,3,5,1,2,8,9,15 (0, arr, 2, arr2 순서로 합쳐집니다.)
let str = "Hello";
alert( [...str] ); // H,e,l,l,o
let str = "Hello";
// Array.from은 이터러블을 배열로 바꿔줍니다.
alert( Array.from(str) ); // H,e,l,l,o
array/object 새로운 복사본 만들기
spread
연산자로 복사본을 만들 수 있음- 컨텐츠는 같지만 참조값이 다른 복사본임
let arr = [1, 2, 3];
let arrCopy = [...arr];
let obj = { a: 1, b: 2, c: 3 };
let objCopy = { ...obj };
6.3 변수의 유효범위와 클로저
코드 블록
- 코드 블록
{...}
안에서 선언한 변수는 블록 안에서만 사용할 수 있습니다. - 이미 선언된 변수와 동일한 이름을 가진 변수를 별도의 블록 없이 let으로 선언하면 에러가 발생합니다.
- for문에서 for 옆 괄호 안에서 선언한 변수(예시에서 let i)는 {...} 밖에 있긴 하지만 블록 {...}에 속하는 코드로 취급됩니다.
중첩 함수
- 함수 내부에서 선언한 함수는 ‘중첩(nested)’ 함수라고 부릅니다.
- 중첩 함수는 새로운 객체의 프로퍼티 형태나 중첩 함수 그 자체로 반환될 수 있다는 점에서 흥미롭습니다. 이렇게 반환된 중첩 함수는 어디서든 호출해 사용할 수 있습니다. 물론 이때도 외부 변수에 접근할 수 있다는 사실은 변함없습니다.
function makeCounter() {
let count = 0;
return function() {
return count++;
};
}
let counter = makeCounter();
alert( counter() ); // 0
alert( counter() ); // 1
alert( counter() ); // 2
렉시컬 환경
- 자바스크립트에선 실행 중인 함수, 코드 블록 {...}, 스크립트 전체는 렉시컬 환경(Lexical Environment) 이라 불리는 내부 숨김 연관 객체(internal hidden associated object)를 갖습니다.
- 렉시컬 환경 객체는 두 부분으로 구성됩니다.
- 환경 레코드(Environment Record) – 모든 지역 변수를 프로퍼티로 저장하고 있는 객체입니다. this 값과 같은 기타 정보도 여기에 저장됩니다.
- 외부 렉시컬 환경(Outer Lexical Environment) 에 대한 참조 – 외부 코드와 연관됨
단계 1. 변수
- 변수는 특수 내부 객체인 환경 레코드의 프로퍼티입니다. 환경 레코드는 현재 실행 중인 함수와 코드 블록, 스크립트와 연관되어 있습니다.
변수를 변경하면 환경 레코드의 프로퍼티가 변경됩니다.
단계 2. 함수 선언문
- 다만 함수 선언문(function declaration)으로 선언한 함수는 일반 변수와는 달리 바로 초기화된다는 점에서 차이가 있습니다.
- 선언되기 전에도 함수를 사용할 수 있는 것은 바로 이 때문입니다.
단계 3. 내부와 외부 렉시컬 환경
- 코드에서 변수에 접근할 땐, 먼저 내부 렉시컬 환경을 검색 범위로 잡습니다. 내부 렉시컬 환경에서 원하는 변수를 찾지 못하면 검색 범위를 내부 렉시컬 환경이 참조하는 외부 렉시컬 환경으로 확장합니다. 이 과정은 검색 범위가 전역 렉시컬 환경으로 확장될 때까지 반복됩니다.
- 함수
say
내부의alert
에서 변수name
에 접근할 땐, 먼저 내부 렉시컬 환경을 살펴봅니다. 내부 렉시컬 환경에서 변수name
을 찾았습니다. alert
에서 변수phrase
에 접근하려는데,phrase
에 상응하는 프로퍼티가 내부 렉시컬 환경엔 없습니다. 따라서 검색 범위는 외부 렉시컬 환경으로 확장됩니다. 외부 렉시컬 환경에서phrase
를 찾았습니다.
단계 4. 반환 함수
function makeCounter() {
let count = 0;
return function() {
return count++;
};
}
let counter = makeCounter();
- 위쪽에서 살펴본 say("John") 예시와 마찬가지로 makeCounter()를 호출할 때도 두 개의 렉시컬 환경이 만들어집니다.
- 그런데 say("John")와 makeCounter() 예시에는 차이점이 하나 있습니다. makeCounter()가 실행되는 도중에 한 줄짜리 본문(return count++)을 가진 중첩 함수가 만들어진다는 점입니다. 현재는 중첩함수가 생성되기만 하고 실행은 되지 않은 상태입니다.
- 여기서 중요한 사실이 하나 있습니다. 모든 함수는 함수가 생성된 곳의 렉시컬 환경을 기억한다는 점입니다. 함수는 [[Environment]]라 불리는 숨김 프로퍼티를 갖는데, 여기에 함수가 만들어진 곳의 렉시컬 환경에 대한 참조가 저장됩니다.
- 따라서 counter.[[Environment]]엔 {count: 0}이 있는 렉시컬 환경에 대한 참조가 저장됩니다. 호출 장소와 상관없이 함수가 자신이 태어난 곳을 기억할 수 있는 건 바로 [[Environment]] 프로퍼티 덕분입니다. [[Environment]]는 함수가 생성될 때 딱 한 번 그 값이 세팅됩니다. 그리고 이 값은 영원히 변하지 않습니다.
- counter()를 호출하면 각 호출마다 새로운 렉시컬 환경이 만들어집니다. 그리고 이 렉시컬 환경은 counter.[[Environment]]에 저장된 렉시컬 환경을 외부 렉시컬 환경으로서 참조하게 됩니다.
- 실행 흐름이 중첩 함수의 본문으로 넘어오면 count 변수가 필요한데, 먼저 자체 렉시컬 환경에서 변수를 찾습니다. 익명 중첩 함수엔 지역변수가 없기 때문에 이 렉시컬 환경은 비어있는 상황입니다(
<empty>
). 이제 counter()의 렉시컬 환경이 참조하는 외부 렉시컬 환경에서 count를 찾아봅시다. count를 찾았습니다! - 변숫값 갱신은 변수가 저장된 렉시컬 환경에서 이뤄집니다.
- 실행이 종료된 후의 상태는 다음과 같습니다.
- counter()를 여러 번 호출하면 count 변수가 2, 3으로 증가하는 이유가 바로 여기에 있습니다.
클로저
'클로저(closure)'는 개발자라면 알고 있어야 할 프로그래밍 용어입니다.
클로저는 외부 변수를 기억하고 이 외부 변수에 접근할 수 있는 함수를 의미합니다. 몇몇 언어에선 클로저를 구현하는 게 불가능하거나 특수한 방식으로 함수를 작성해야 클로저를 만들 수 있습니다. 하지만 자바스크립트에선 모든 함수가 자연스럽게 클로저가 됩니다. 예외가 하나 있긴 한데 자세한 내용은 new Function 문법에서 다루도록 하겠습니다.
요점을 정리해 봅시다. 자바스크립트의 함수는 숨김 프로퍼티인 [[Environment]]를 이용해 자신이 어디서 만들어졌는지를 기억합니다. 함수 내부의 코드는 [[Environment]]를 사용해 외부 변수에 접근합니다.
프런트엔드 개발자 채용 인터뷰에서 "클로저가 무엇입니까?"라는 질문을 받으면, 클로저의 정의를 말하고 자바스크립트에서 왜 모든 함수가 클로저인지에 관해 설명하면 될 것 같습니다. 이때 [[Environment]] 프로퍼티와 렉시컬 환경이 어떤 방식으로 동작하는지에 대한 설명을 덧붙이면 좋습니다.
가비지 컬렉션
- 함수 호출이 끝나면 함수에 대응하는 렉시컬 환경이 메모리에서 제거됩니다. 함수와 관련된 변수들은 이때 모두 사라지죠. 함수 호출이 끝나면 관련 변수를 참조할 수 없는 이유가 바로 여기에 있습니다. 자바스크립트에서 모든 객체는 도달 가능한 상태일 때만 메모리에 유지됩니다.
- 그런데 호출이 끝난 후에도 여전히 도달 가능한 중첩 함수가 있을 수 있습니다. 이때는 이 중첩함수의
[[Environment]]
프로퍼티에 외부 함수 렉시컬 환경에 대한 정보가 저장됩니다. 도달 가능한 상태가 되는 것이죠. - 함수 호출은 끝났지만 렉시컬 환경이 메모리에 유지되는 이유는 바로 이 때문입니다.
- 렉시컬 환경 객체는 다른 객체와 마찬가지로 도달할 수 없을 때 메모리에서 삭제됩니다. 해당 렉시컬 환경 객체를 참조하는 중첩 함수가 하나라도 있으면 사라지지 않죠.
- 중첩 함수가 메모리에서 삭제되고 난 후에야, 이를 감싸는 렉시컬 환경(그리고 그 안의 변수인 value)도 메모리에서 제거됩니다.
최적화 프로세스
- 앞에서 보았듯이, 함수가 살아있는 동안엔 이론상으론 모든 외부 변수 역시 메모리에 유지됩니다.
- 그러나 실제로는 자바스크립트 엔진이 이를 지속해서 최적화합니다. 자바스크립트 엔진은 변수 사용을 분석하고 외부 변수가 사용되지 않는다고 판단되면 이를 메모리에서 제거합니다.
- 디버깅 시, 최적화 과정에서 제거된 변수를 사용할 수 없다는 점은 V8 엔진(Chrome, Opera에서 쓰임)의 주요 부작용입니다.
6.4 오래된 'var'
요약
var
로 선언한 변수는let
이나const
로 선언한 변수와 다른 두 가지 주요한 특성을 보입니다.
var
로 선언한 변수는 블록 스코프가 아닌 함수 수준 스코프를 갖습니다.var
선언은 함수가 시작되는 시점(전역 공간에선 스크립트가 시작되는 시점)에서 처리됩니다.
이 외에도 전역 객체와 관련된 특성 하나가 더 있는데, 이에 대해선 다음 챕터에서 다루도록 하겠습니다.
var
만의 특성은 대부분의 상황에서 좋지 않은 부작용을 만들어냅니다.let
이 표준에 도입된 이유가 바로 이런 부작용을 없애기 위해서입니다. 변수는 블록 레벨 스코프를 갖는 게 좋으므로 이제는let
과const
를 이용해 변수를 선언하는 게 대세가 되었습니다.
'var’는 블록 스코프가 없습니다.
var
로 선언한 변수의 스코프는 함수 스코프이거나 전역 스코프입니다. 블록 기준으로 스코프가 생기지 않기 때문에 블록 밖에서 접근 가능합니다.var
는 코드 블록을 무시하기 때문에if
혹은for
내부의 변수는 전역 변수가 됩니다. 전역 스코프에서 이 변수에 접근할 수 있죠.- 코드 블록이 함수 안에 있다면,
var
는 함수 레벨 변수가 됩니다. - 위에서 살펴본 바와 같이,
var
는if
,for
등의 코드 블록을 관통합니다. 아주 오래전의 자바스크립트에선 블록 수준 렉시컬 환경이 만들어 지지 않았기 때문입니다. var는 구식 자바스크립트의 잔재이죠.
'var'는 재선언을 허용합니다.
- 동일한 범위에 동일한 변수를 두 번 선언하면 오류가 발생합니다.
- var를 사용하면 변수를 언제든지 다시 선언할 수 있습니다. 이미 선언된 변수와 함께 var를 사용하면 무시됩니다.
선언하기 전 사용할 수 있는 ‘var’
- var 선언은 함수가 시작될 때 처리됩니다. 전역에서 선언한 변수라면 스크립트가 시작될 때 처리되죠.
- 함수 본문 내에서 var로 선언한 변수는 선언 위치와 상관없이 함수 본문이 시작되는 지점에서 정의됩니다(단, 변수가 중첩 함수 내에서 정의되지 않아야 이 규칙이 적용됩니다).
- 이렇게 변수가 끌어올려 지는 현상을 '호이스팅(hoisting)'이라고 부릅니다. var로 선언한 모든 변수는 함수의 최상위로 ‘끌어 올려지기(hoisted)’ 때문입니다(hoist는 끌어올리다 라는 뜻이 있습니다. – 옮긴이).
- 선언은 호이스팅 되지만 할당은 호이스팅 되지 않습니다. 따라서, 할당이 되지 않은 변수를 출력하면 undefined가 됩니다.
즉시 실행 함수 표현식
- 과거엔 var만 사용할 수 있었습니다. 그런데 var의 스코프는 블록 레벨 수준이 아니죠. 개발자들은 var도 블록 레벨 스코프를 가질 수 있게 여러가지 방안을 고려하게 됩니다. 이때 만들어진 것이 '즉시 실행 함수 표현식(immediately-invoked function expressions)'입니다. 즉시 실행 함수 표현식은 IIFE라고 부르기도 합니다.
- 즉시 실행 함수 표현식을 요즘에는 자주 쓰지 않습니다. 하지만 오래된 스크립트에서 만날 수 있기 때문에 즉시 실행 함수 표현식이 무엇인지 알아 둘 필요가 있습니다.
(function() {
let message = "Hello";
alert(message); // Hello
})();
- 즉시 실행 함수를 만들 땐, 함수 표현식을 괄호로 둘러쌓아 (function {…})와 같은 형태로 만듭니다. 이렇게 괄호로 둘러싸지 않으면 에러가 발생합니다. 자바스크립트는 'function’이라는 키워드를 만나면 함수 선언문이 시작될 것이라 예상합니다. 그런데 함수 선언문으로 함수를 만들 땐 반드시 함수의 이름이 있어야 합니다. 따라서 아래와 예시를 실행하면 에러가 발생합니다.
// IIFE를 만드는 방법
(function() {
alert("함수를 괄호로 둘러싸기");
})();
(function() {
alert("전체를 괄호로 둘러싸기");
}());
!function() {
alert("표현식 앞에 비트 NOT 연산자 붙이기");
}();
+function() {
alert("표현식 앞에 단항 덧셈 연산자 붙이기");
}();
📚 참고 : javascript.info
Author And Source
이 문제에 관하여(6. 함수 심화학습(1)), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@protect-me/함수-심화학습1저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)