본문 바로가기
웹 프로그래밍/JavaScript | TypeScript

[모던 자바스크립트 Deep Dive] 12장. 함수1 (12.1. 함수란? ~ 12.4. 함수 정의)

by 청량리 물냉면 2024. 3. 29.
반응형

12.1. 함수란?

함수

  • 일련의 과정을 문(statement)으로 구현학 코드 블록으로 감싸서 하나의 실행 단위로 정의한 것
  • 매개변수: 함수 내부로 입력을 전달
  • 인수: 입력. 함수 호출 시 사용됨
  • 반환값: 출력
  • 함수는 값이며, 여러 개 존재할 수 있다.

 

12.2. 함수를 사용하는 이유

  • 함수의 재사용성
  • 유지보수성
  • 편의성
  • 코드의 신뢰성 높임
  • 코드 가독성 향상

 

12.3. 함수 리터럴

리터럴: 사람이 이해할 수 있는 문자 또는 약속된 기호를 사용해 값을 생성하는 표기 방식
5.2절 <리터럴> 파트

  • 자바스크립트의 함수는 객체타입의 값이다. 따라서 숫자 값을 숫자 리터럴로 생성하고 객체를 객체 리터럴로 생성하는 것처럼 함수도 함수 리터럴로 생성할 수 있다.
  • 함수는 일반 객체와 다르다. 일반 객체는 호출할 수 없지만 함수는 호출 가능하다. 또한 일반 객체에는 없는 함수 객체만의 고유한 프로퍼티를 갖는다.
  • 함수 리터럴은 function 키워드, 함수 이름, 매개변수 목록, 함수 몸체로 구성
//변수에 함수 리터럴 할당
var t = function add(x, y) {
  return x + y;
};

 

함수 이름

  • 함수 이름은 식별자 👉 식별자 네이밍 규칙 준수 필수
  • 함수 이름은 함수 몸체 내에서만 참조 가능
  • 함수 이름 생략 가능 👉 이름이 있는 경우: 기명 함수, 이름이 없는 경우: 무명/익명 함수

 

매개변수 목록

  • 0개 이상의 매개변수를 소괄호로 감싸고 쉼표로 구분
  • 함수 호출 시 지정한 인수가 순서대로 할당됨.(순서에 의미 o)
  • 매개변수는 함수 내에서 변수와 동일 취급을 받는다. 👉 식별자 네이밍 규칙 준수 필수

 

함수 몸체

  • 함수 호출 시 실행될 문들을 하나의 실행 단위로 정의한 코드블록

 

12.4. 함수 정의

12.4.1. 함수 선언문

function add(x, y) {
  return x + y;
}

// 함수 이름 생략 불가!
// function (x, y) {
//   return x + y;
// }
  • 함수 리터럴과 동일한 형태를 가진다.
  • 단, 함수 리터럴과 달리 함수 선언문은 함수 이름을 생략할 수 없다.

 

함수선언문은 표현식이 아닌 문이다. (관련글 참고)

표현식이 아닌 문은 변수에 할당할 수 없으며, 따라서 함수 선언문도 변수에 할당할 수 없다.

그런데 아래 코드를 보면, 함수 선언문이 변수에 할당되는 것처럼 보인다.

var add = function (x, y) {
  return x + y;
};

console.log(add(1, 3)); //4

💁‍♀️ 이유? 

그 이유는 자바스크립트 엔진이 코드의 문맥에 따라 동일한 함수 리터럴을

표현식이 아닌 문인 함수 선언문으로 해석하는 경우와

표현식인 문인 함수 리터럴 표현식으로 해석하는 경우가 있기 때문이다.

함수 선언문은 함수 이름을 생략할 수 없다는 점을 제외하면 함수 리터럴과 형태가 동일하다. 이는 함수 이름이 있는 기명함수 리터럴은 함수 선언문 및 함수 리터럴 표현식으로 해석될 가능성이 있다는 의미다.

 

👉 정리

즉, 자바스크립트 엔진은

함수 이름이 있는 함수 리터럴을 단독으로 사용하면 함수 선언문으로 해석하고,

함수 리터럴이 값으로 평가되어야 하는 문맥(함수 리터럴을 변수에 할당하거나 피연산자로 사용)에서는 함수 리터럴 표현식으로 해석한다. 

//기명 함수 리터럴을 단독으로 사용하면 함수 선언문으로 해석된다.
function foo() {
  console.log("foo");
}

foo();	//foo

//함수 리터럴에서는 피연산자로 사용하면
//함수 선언문이 아니라 함수 리터럴 표현식으로 해석한다
(function bar() {
  console.log("bar");
});

bar();	//참조에러(bar is not defined)

위 예제의 함수 리터럴 foo는 함수 선언문으로 해석되지만 그룹 연산자() 내에 있는 함수 리터럴 bar는 함수 리터럴 표현식으로 해석된다.

그룹 연산자의 피연산자는 값으로 평가될 수 있는 표현식이어야 하므로, 표현식이 아닌 문인 함수 선언문은 피연산자로 사용할 수 없다.

 

✅ 함수 호출

또한 함수 선언문과 함수 리터럴 표현식은 함수 호출에 있어서도 차이를 보인다.

위 예제에서 함수 선언문인 foo는 호출 가능하지만 함수 리터럴 표현식 bar는 호출 불가하다.

그 이유는 함수 리터럴의 특성 때문이다. 

함수 이름은 함수 몸체 내에서만 참조 가능

위와 같은 객체 리터럴의 특성 때문에, 함수 몸체 외부에서는 함수 이름으로 함수를 참조할 수 없다.

즉, 함수 몸체 외부에서는 함수 이름으로 함수를 호출할 수 없다는 뜻이다(= 함수를 가리키는 식별자가 없다).

따라서 위 예제의 bar함수는 호출이 불가하다.

 

그러나 위 예제에서 함수 선언문으로 정의된 함수는 foo라는 이름으로 호출이 가능했다.

foo는 함수 몸체 내부에서만 유효한 식별자인 함수 이름이므로 foo로 함수를 호출할 수 없어야 정상이다. 위 예제에서는 식별자 foo를 선언한 적도 할당한 적도 없다.

 

😲 foo는 무엇일까?

foo는 자바스크립트가 암묵적으로 생성한 식별자이다.

자바스크립트 엔진은 함수 선언문을 해석해 함수 객체를 생성한다. 이때 함수 이름은 함수 몸체 내부에서만 유효한 식별자이므로 함수 이름과는 별도로, 생성된 함수 객체를 가리키는 식별자가 필요하다. 함수 객체를 가리키는 식별자가 없으면 생성된 함수 객체를 참조할 수 없으므로 호출 역시 불가능한다.

따라서 JS 엔진은 생성된 함수를 호출하기 위해 함수 이름과 동일한 이름의 식별자를 암묵적으로 생성하고, 거기에 함수 객체를 할당한다.

 

함수는 함수 이름으로 호출되는 것이 아니라 함수 객체를 가리키는 식별자로 호출된다.

즉, 함수 선언문으로 생성한 함수를 호출하는 것은 함수 이름 add가 아니라 자바스크립트 엔진이 암묵적으로 생성한 식별자 add인 것이다.

var add = function (x, y) {
  return x + y;
};

console.log(add(1, 3)); //4

결론적으로 자바스크립트 엔진은 함수 선언문을 함수 표현식으로 변환해 함수 객체를 생성한다. (함수 선언문, 함수 표현식이 정확히 동일한 동작을 하지는 않는다)

 

 

12.4.2. 함수 표현식

js의 함수: 객체 타입의 값. 값처럼 변수에 할당할 수도 있고 프로퍼티 값이 될 수도 있으며 배열의 요소가 될 수도 있다.

이처럼 값의 성질을 갖는 객체를 일급객체라 한다. 👉 함수가 일급 객체라는 것은 함수를 값처럼 자유롭게 사용할 수 있다는 의미

함수는 일급 객체이므로 함수 리터럴로 생성한 함수 객체를 변수에 할당할 수 있으며, 이러한 함수 정의 방식을 함수 표현식이라 한다. 

var add = function (x, y) {
  return x + y;
};

console.log(add(1, 3)); //4

함수 리터럴의 이름은 생략할 수 있다(익명함수).

또한 함수 표현식의 함수 리터럴은 함수 이름을 생략하는 것이 일반적이다.

함수 선언문에서와 같이 함수를 호출할 때는 함수 이름이 아니라 함수 객체를 가리키는 식별자를 사용해야 한다. 함수 이름은 함수 몸체 내부에서만 유효한 식별자이므로 함수 이름으로 함수를 호출할 수 없다.

//기명 함수 표현식
var add = function foo(x, y) {
  return x + y;
};

//함수 객체를 가리키는 식별자로 호출
console.log(add(1, 3)); //4

//함수 이름은 함수 몸체 내부에서만 유효한 식별자이다.
//참조 에러: foo is not defined
console.log(foo(2, 5));

 

 

12.4.3. 함수 생성 시점과 함수 호이스팅

console.log(dir(add)); //ƒ add(x, y)
console.log(dir(sub)); //undefined

console.log(add(4, 5)); //9
console.log(sub(4, 5)); //TypeError: sub is not a function

function add(x, y) {
  return x + y;
}

var sub = function (x, y) {
  return x - y;
};
  • 함수 선언문으로 정의한 함수는 함수 선언문 이전에 호출 가능
  • but 함수 표현식으로 정의한 함수는 함수 표현식 이전에 호출 불가

👉 이러한 차이는 함수 선언문으로 정의한 함수와 함수 표현식으로 정의한 함수의 생성시점이 다르기 때문에 발생

 

함수 선언문으로 함수 정의 시, 런타임 이전에 함수 객체가 먼저 생성된다. 그리고 js 엔진은 함수 이름과 동일한 이름의 식별자를 암묵적으로 생성하고 생성된 함수 객체를 할당한다.

즉, 코드가 한줄씩 순차적으로 실행되기 시작하는 런타임에는 이미 함수 객체가 생성되어 있고 함수 이름과 동일한 식별자에 할당까지 완료된 상태다. 따라서 함수 선언문 이전에 함수를 참조할 수 있으며 호출할 수도 있다. 이러한 특징을 함수 호이스팅이라 한다.

 

함수 표현식은 변수에 할당되는 값이 함수 리터럴인 문이다.

따라서 함수 표현식은 변수 선언문과 변수 할당문을 한 번에 작성한 축약 표현과 동일하게 동작한다.

변수 선언은 런타임 이전에 실행되어 undefined로 초기화되지만 변수 할당문의 값은 할당문이 실행되는 시점(런타임)에 평가되므로 함수 표현식의 함수 리터럴도 할당문이 실행되는 시점에 평가되어 함수 객체가 된다.

따라서 함수 표현식으로 함수를 정의하면 함수 호이스팅이 발생하는 것이 아니라 변수 호이스팅이 발생한다. 

함수 표현식 이전에 함수를 참조하면 undefined로 평가된다. 따라서 이때 함수를 호출하면 undefined를 호출하는 것과 마찬가지이므로 타입 에러가 발생한다.

따라서 함수 표현식으로 정의한 함수는 반드시 함수 표현식 이후에 참조 또는 호출해야 한다.

➕ 함수 호이스팅 vs 변수 호이스팅

공통점
1. var 키워드를 사용
2. 런타임 이전에 js 엔진에 의해 먼저 실행되어 식별자를 생성한다는 점

차이점
변수
선언된 변수는 undefined로 초기화 👉 변수 선언문 이전에 변수 참조시, 변수 호이스팅에 의해 undefined로 평가됨

함수
함수 객체로 초기화 👉 함수 선언문을 통해 암묵적으로 생성된 식별자는 함수 객체로 초기화됨. 따라서 호이스팅으로 호출 가능

함수 호이스팅은 함수를 호출하기 전에 반드시 함수를 선언해야 한다는 규칙을 무시한다.

따라서 함수 선언문 대신 함수 표현식 사용이 권장된다.

 

 

12.4.4. Function생성자 함수

var add = new Function("x", "y", "return x+y");
  • js 기본 제공 빌트인 함수 Function *생성자 함수에 매개변수 목록과 함수 몸체를 문자열로 전달 + new 연산자와 함께 호출하면 함수 객체를 생성해서 반환한다. (new 연산자는 생략 가능)
  • 일반적이지 않고 바람직하지도 않은 함수 생성 방식
  • 클로저 생산x, 함수 선언문/함수 표현식으로 생성한 함수와 다르게 동작... 등 이슈가 발생하는 방식이다.

*생성자 함수: 객체를 생성하는 함수

 

 

12.4.5. 화살표 함수(ES6)

var add = (x, y) => x + y;
  • 기존의 함수보다 표현 및 내부 동작이 간략화되어 있다.
  • 화살표 함수는 생성자 함수로 사용 불가하다
  • 기존 함수와 this 바인딩 방식이 다르다.
  • prototype 프로퍼티가 존재하지 않으며 arguments 객체를 생성하지 않는다.

 

 

 


출처: 모던 자바스크립트 Deep Dive (이웅모 저, 위키북스)

반응형