TIL] Deep Dive-함수

🌼 12.2 함수를 사용하는 이유

프로그래밍 언어의 함수는 수학의 함수와 같은 개념이다. 프로그래밍 언어의 함수는 일련의 과정을 구현한 것을 코드 블록으로 감싸서 하나의 실행 단위로 정의한 것이다. 프로그래밍에서 함수를 사용하는 이유는 다음과 같다.

  • 함수는 실행 시점을 개발자가 결정할 수 있고 몇 번이든 재사용이 가능하다.

  • 코드의 중복을 억제하고 재사용성을 높혀 유지보수의 편의성을 높힌다.

  • 적절한 함수 이름 설정을 통해 함수 내부의 코드를 이해하지 않고도 함수의 역할을 파악할 수 있어 코드의 가독성을 향상시킨다.

🌼 12.4 함수 정의

  1. 함수 선언문: 함수 선언문은 함수 리터럴과 형태가 동일하다. 다만, 함수 리터럴은 함수 이름을 생략할 수 있어나 함수 선언문은 함수 이름을 생략할 수 없다. 함수는 함수 이름으로 호출하는 것이 아니라 함수 객체를 가리키는 식별자로 호출한다. 즉, 아래의 코드에서 함수 선언문으로 생성한 함수 foo를 호출한 것은 함수 이름이 아니라 자바 스크립트엔진이 암묵적으로 생성한 식별자 foo이다.
//기명 함수 리터럴을 단독으로 사용하면 함수 선언문으로 해석된다.
function foo() {
  console.log('foo');
}

//원래라면 foo는 함수 리터럴의 특성으로 인해 함수 몸체 내부에서만 유효한 식별자이나
//자바 스크립트 엔진이 암묵적으로 foo라는 식별자를 생성하기 때문에 foo라는 이름으로 함수 호출이 가능해진다.
foo();

//함수 리터럴을 피연산자로 사용하면 함수 선언문이 아니라 함수 리터럴 표현식으로 해석된다.
//()는 그룹연산자이고 그 안의 기명 함수 리터럴은 피연산자로 해석된다.
(function bar() { console.log('bar') });

//reference error
bar();
  1. 함수 표현식: 자바스크립트의 함수는 값처럼 변수에 할당할 수도 있고 프로퍼티 값이 될 수도 있으며 배열의 요소가 될 수도 있다. 이처럼 값의 성질을 갖는 객체는 일급 객체라 한다. 함수는 이런 일급 객체의 성질로 인해 변수에 할당할 수 있다. 함수 표현식의 함수 리터럴은 함수 이름을 생략하는 것이 일반적이다.
let add = function plus (a, b) {
  return a + b;
}

console.log(add(2, 5)); //7
console.log(plus(2, 5); //reference error
  • 함수 선언문으로 정의한 함수는 함수 선언문 이전에 호출할 수 있다. 그러나 함수 표현식으로 정의한 함수는 함수 표현식 이전에 호출할 수 없다. 이는 함수 두 함수의 생성 시점이 다르기 때문이다. 함수 선언문으로 정의한 함수는 함수 호이스팅이라는 자바 스크립트의 고유의 특징으로 선언문 이전에 참조할 수 있다.

    • 변수 호이스팅은 변수 선언시 선언된 변수가 undefined로 초기화되고 함수 선언문의 함수 호이스팅은 선언된 함수가 함수 객체로 초기화된다는 점에서 차이가 있다.

    • 함수 표현식의 경우, 함수 호이스팅이 발생하는 것이 아니라 변수 호이스팅이 발생하고 함수 표현식 이전에 함수를 참조하면 undefined로 평가된다.

    • 함수 호이스팅은 함수를 호출하기 전에 반드시 함수를 선언해야 한다는 당연한 규칙을 무시한다. 따라서 함수 선언문 대신에 함수 표현식을 사용할 것을 권장한다.

  1. Function 생성자 함수: Function 생성자 함수로 함수를 생성하는 방식은 일반적이지 않고 바람적이지도 않다.

  2. 화살표 함수(ES6): 화살표 함수는 항상 익명 함수로 정의한다.

🌼 12.5 함수 호출

  • 자바스크립트 함수는 매개변수와 인수의 개수가 일치하는지 확인하지 않는다.

  • 자바스크립트는 동적 타입 언어다. 따라서 자바스크립트 함수는 매개변수의 타입을 사전에 지정할 수 없다.

    • 이러한 문제를 개선하기 위한 방법으로 타입스크립트와 같은 정적 타입을 선언할 수 있는 자바스크립트의 상위 확장을 선택할 수도 있다.

    • 인수가 전달되지 않은 경우 아래와 같은 단축평가를 사용해 매개변수에 기본값을 할당하는 방법도 있다.

function add(a, b) {
  a = a || 0;
  b = b || 0;
  return a + b;
}

console.log(add(1)); //1

🌼 12.5 매개변수의 최대 개수

함수의 매개변수는 코드를 이해하는 데 방해되는 요소이므로 이상적인 매개변수 개수는 0개이며 적을수록 좋다. 매개변수의 개수가 많다는 것은 함수가 여러 가지 일을 한다는 증거이므로 바람직하지 않다. 이상적인 함수는 한 가지 일만 해야 하며 가급적 작게 만들어야 한다.

  • 객체를 인수로 사용하는 방법도 있지만 함수 외부에서 함수 내부로 전달한 객체를 함수 내부에서 변경하면 함수 외부의 객체가 변경되는 부수 효과(side effect)가 발생하므로 주의해야 한다.

🌼 12.6 참조에 의한 전달과 외부 상태의 변경

함수를 호출하면서 매개변수에 값을 전달하는 방식을 값에 의한 호출(call by value), 참조에 의한 호출(call by reference)로 구별해 부르는 경우도 있으나 동작 방식은 값에 의한 전달, 참조에 의한 전달과 동일하다.

function changeVal(primitive, obj) {
  primitive += 100;
  obj.name = 'Kim';
}

let num = 100;
let person = { name: 'Lee'};

//원시 값은 값 자체가 복사되어 전달되고 객체는 참조 값이 복사되어 전달된다.
changeVal(num, person);

//원시 값은 원본이 훼손되지 않는다.
console.log(num); //100
//객체는 원본이 훼손된다.
console.log(person); //{name: 'Kim'};

함수가 외부 상태를 변경하면 상태 변화를 추적하기 어려워진다. 이는 코드의 복잡성을 증가시키고 가독성을 해치는 원인이 된다. 이러한 문제를 해결하지 위한 방법 중 하나는 객체를 불변 객체로 만들어 사용하는 것이다.

🌼 12.7 다양한 함수의 형태

  • 즉시 실행 함수: 함수 정의와 동시에 즉시 호출되는 함수(Immediately Invoked Function Expression)를 말한다. 즉시 실행 함수는 단 한 번만 호출되며 다시 호출할 수 없다.
(function foo() {
  let a = 3;
  let b = 3;
  return a * b;
})();
  • 재귀 함수: 함수가 자기 자신을 호출하는 것을 재귀 호출이라고 하며 자기 자신을 호출하는 행위를 수행하는 함수를 재귀 함수라고 한다. 대부분의 재귀 함수는 for문이나 while문으로 구현 가능하다. 재귀 함수는 반복되는 처리는 반복문 없이 구현할 수 있다는 장점이 있지만 무한 반복에 빠질 위험이 있으므로 재귀 함수를 사용하는 편이 더 직관적으로 이해하기 쉬울 때만 한정적으로 사용하는 것이 바람직하다.
function factorial(n) {
  //탈출 조건
  if (n <= 1) return 1;
  //재귀 호출
  //함수 이름은 함수 몸체 내부에서만 유효하므로 아래 코드로 자기 자신을 호출할 수 있다.
  return n * factorial(n - 1);
}

function factorial(n) {
  if (n <= 1) return 1;
  let res = n;
  //n === 0이면 falsy한 값으로 인식하여 while문을 종료한다.
  while (--n) res *= n;
  return res;
}

factorial(5) //5!;
  • 중첩 함수: 함수 내부에 정의된 함수를 중첩 함수(nested function) 또는 내부 함수(inner function)라 한다. 일반적으로 중첩 함수는 자신을 포함하는 외부 함수를 돕는 헬퍼 함수의 역할을 한다.
function outer() {
  let x = 2;
  
  function inner() {
    let y = 3;
    console.log(x + y); 
  }
  inner();
}

outer(); //3
  • 콜백 함수: 함수의 매개변수를 통해 다른 함수의 내부로 전달되는 함수를 콜백 함수(callback function)라고 하며, 매개변수를 통해 함수의 외부에서 콜백 함수를 전달받은 함수를 고차 함수(higher-order function: HOF)라고 한다. 만약 콜백 함수가 고차 함수 내부에만 호출된다면 콜백 함수를 익명 함수 리터럴로 정의하면서 곧바로 고차 함수에 전달하는 것이 일반적이다. 다만 콜백 함수로 전달된 함수 리터럴은 고차 함수가 호출될 때마다 평가되어 함수 객체를 생성하므로 콜백 함수를 다른 곳에서도 호출할 필요가 있거나, 콜백 함수를 전달받는 함수가 자주 호출된다면 함수 외부에서 콜백 함수를 정의한 후 함수 참조를 고차 함수에 전달하는 편이 효율적이다.

  • 순수함수와 비순수함수: 함수형 프로그래밍에서는 어떤 외부 상태에 의존하지도 않고 변경하지도 않는 부수 효과가 없는 함수를 순수 함수(pure function)라 하고, 외부 상태에 의존하거나 외부 상태를 변경해 부수 효과가 있는 함수를 비순수 함수(impure function)라고 한다.

    • 함수가 외부 상태를 변경하면 상태 변화를 추적하기 어려워진다. 함수형 프로그래밍은 순수 함수와 보조 함수의 조합을 통해 외부 상태를 변경하는 부수 효과를 최소화해서 불변성(immutability)을 지향하는 프로그래밍 패러다임이다. 로직 내에 존재하는 조건문과 반복문을 제거해서 복잡성을 해결하며, 변수 사용을 억제하거나 생명주기를 최소화해서 상태 변경을 피해 오류를 최소하하는 것을 목표로 한다.

출처: 모던 자바스크립트 Deep Dive-이웅모

좋은 웹페이지 즐겨찾기