6. 함수 심화학습(1)

protect-me·2021년 6월 8일
0
post-thumbnail

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
  1. n == 1일 때:
    모든 절차가 간단해집니다. 명확한 결괏값을 즉시 도출하므로 이를 재귀의 베이스(base) 라고 합니다. pow(x, 1)는 x 입니다.
  2. 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)를 계산하려면 아래와 같은 재귀 단계가 차례대로 이어집니다.
  1. pow(2, 4) = 2 * pow(2, 3)
  2. pow(2, 3) = 2 * pow(2, 2)
  3. pow(2, 2) = 2 * pow(2, 1)
  4. 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) 이라는 특별한 자료 구조에 저장됩니다.
    - 중첩 호출이 실행됩니다.
    - 중첩 호출 실행이 끝난 이후 실행 컨텍스트 스택에서 일시 중단한 함수의 실행 컨텍스트를 꺼내오고, 중단한 함수의 실행을 다시 이어갑니다.
  • 재귀를 이용해 작성한 코드는 반복문을 사용한 코드로 다시 작성할 수 있습니다. 반복문을 사용하면 대개 함수 호출의 비용(메모리 사용)이 절약됩니다.

재귀적 순회

  1. 임직원 배열 을 가진 ‘단순한’ 부서 – 간단한 반복문으로 급여 합계를 구할 수 있습니다.
  2. 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로 선언한 변수와 다른 두 가지 주요한 특성을 보입니다.

  1. var로 선언한 변수는 블록 스코프가 아닌 함수 수준 스코프를 갖습니다.
  2. var 선언은 함수가 시작되는 시점(전역 공간에선 스크립트가 시작되는 시점)에서 처리됩니다.
    이 외에도 전역 객체와 관련된 특성 하나가 더 있는데, 이에 대해선 다음 챕터에서 다루도록 하겠습니다.

var만의 특성은 대부분의 상황에서 좋지 않은 부작용을 만들어냅니다. let이 표준에 도입된 이유가 바로 이런 부작용을 없애기 위해서입니다. 변수는 블록 레벨 스코프를 갖는 게 좋으므로 이제는 letconst를 이용해 변수를 선언하는 게 대세가 되었습니다.

'var’는 블록 스코프가 없습니다.

  • var로 선언한 변수의 스코프는 함수 스코프이거나 전역 스코프입니다. 블록 기준으로 스코프가 생기지 않기 때문에 블록 밖에서 접근 가능합니다.
  • var는 코드 블록을 무시하기 때문에 if혹은 for 내부의 변수는 전역 변수가 됩니다. 전역 스코프에서 이 변수에 접근할 수 있죠.
  • 코드 블록이 함수 안에 있다면, var는 함수 레벨 변수가 됩니다.
  • 위에서 살펴본 바와 같이, varif, 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

profile
protect me from what i want

0개의 댓글