[모딥다] 12장.함수

vanLan·1일 전

모딥다

목록 보기
8/8
post-thumbnail

📁 함수란?

  • 일련의 과정을 문으로 구현하고 코드 블록을 감싸서 하나의 실행 단위로 정의한 것.

  • 함수 정의를 통해 생성됨.

    // 함수 정의
    function add(x, y) {
      return x + y;
    }
  • 함수 호출을 해야 실행됨.

    // 함수 호출
    const result = add(2, 5);
    
    // 함수 add에 인수 2, 5를 전달하면서 호출하면 반환값 7을 반환
    console.log(result);

📁 함수를 사용하는 이유

  • 코드의 재사용 용이, 유지보수의 편의성, 코드의 가독성 향상.

📁 함수 리터럴

  • JS에서의 함수는 객체 타입의 값.
  • 함수 리터럴은 function 키워드, 함수 이름, 매개 변수 목록, 함수 몸체로 구성
    const f = function add(x, y) {
      return x + y;
    }
  • 구성 요소:
    • 함수 이름
      • 함수 이름은 식별자 이므로, 식별자 네이밍 규칙을 준수.
      • 함수 이름은 함수 몸체 내에서만 참조할 수 있는 식별자.
      • 함수 이름 생략 가능 (기명 함수, 무명/익명 함수)
    • 매개변수 목록
      • 0개 이상의 매개변수를 소괄호로 감싸고 쉼표로 구분.
      • 각 매개변수에는 함수를 호출할 때 지정한 인수가 순서대로 할당됨. (순서에 의미가 있음)
      • 매개변수는 함수 몸체 내에서 변수와 동일하게 취급됨. (매개변수도 식별자 네이밍 규칙을 준수)
    • 함수 몸체
      • 함수가 호출됬을 때 일괄적으로 실행될 문들을 하나의 실행 단위로 정의한 코드 블록.
      • 함수 몸체는 함수 호출에 의해 실행됨.

📁 함수 정의

🗒️ 함수 선언문

// 함수 선언문
function add(x, y) {
  return x + y;
}

// 함수 참조
console.dir(add);  // ⨍ add(x, y)

// 함수 호출
console.log(add(2, 5));  // 7
  • 함수 선언문은 함수 이름을 생략 불가.
  • 함수 선언문은 표현식이 아닌 문임.
  • 함수는 함수 이름으로 호출되는 것이 아닌 함수 객체를 가리키는 식별자로 호출함.
  • JS엔진은 생성된 함수를 호출하기 위해 함수 이름과 동일한 이름의 식별자를 암묵적으로 생성하고, 함수를 할당.

🗒️ 함수 표현식

  • JS의 함수는 일급 객체(값의 성질을 갖는 객체).

    // 함수 표현식
    const add = function (x, y) {
      return x + y;
    }
    
    console.log(add(2, 5));  // 7
  • 함수를 호출할 때는 함수 이름이 아닌 함수 객체를 가리키는 식별자를 사용.

    // 기명 함수 표현식
    const add = function foo(x, y) {
      return x + y;
    }
    
    // 함수 객체를 가리키는 식별자로 호출
    console.log(add(2, 5));  // 7
    
    // 함수 이름으로 호출시 ReferenceError가 발생
    console.log(foo(2, 5));  // ReferenceError: foo is not defined

🗒️ 함수 생성 시점과 함수 호이스팅

  • 함수 선언문으로 함수 정의시 런타임 이전에 함수 객체가 먼저 생성됨.

  • 그리고 JS엔진은 함수 이름과 동일한 이름의 식별자를 암묵적으로 생성하고 생성된 함수 객체를 할당.

  • 이처럼 함수 선언문이 코드의 선두로 끌어 올려진 것 처럼 동작하는 JS 고유의 특징을 함수 호이스팅이라 함.

    // 함수 참조
    console.dir(add);  // ⨍ add(x, y)
    
    // 함수 호출
    console.log(add(2, 5));  // 7
    
    // 함수 선언문
    function add(x, y) {
      return x + y;
    }
  • 함수 표현식은 변수에 할당되는 값이 함수 리터럴인 문임.

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

    // 함수 참조
    console.log(sub);  // undefined
    
    // 함수 호출
    console.log(sub(2, 5));  // TypeError: sub is not a function
    
    // 함수 표현식
    var sub = function(x, y) {
      return x - y;
    };
  • 위 처럼 함수 표현식으로 함수를 정의하면, 함수 호이스팅이 발생하는 것이 아닌 변수 호이스팅이 발생.

🗒️ 화살표 함수

  • function 키워드 대신 화살표(=>)를 사용해 좀 더 간략한 방법으로 함수 선언 가능.
    // 화살표 함수
    const add = (x, y) => x + y;
    console.log(add(2, 5));  // 7
  • 기존 함수와 this 바인딩 방식이 다르고, prototype 프로퍼티가 없으며 arguments 객체를 생성하지 않음.

📁 함수 호출

  • 함수 호출시 현재의 실행 흐름을 중단하고 호출된 함수로 실행 흐름을 옮김.
  • 매개변수에 인수가 순서대로 할당되고 함수 몸체의 문들을 실행.

🗒️ 매개변수와 인수

  • 매개 변수는 함수를 정의할 때 선언하며, 함수 몸체 내부에서 변수와 동일하게 취급됨.

  • 함수 몸체 내에서 암묵적으로 매개변수가 생성되고 일반 변수와 마찬가지로 undefined로 초기화된 후 인수가 순서대로 할당됨.

  • 매개변수는 함수 몸체 내부에서만 참조할 수 있고 외부에서는 참조 불가.

  • 함수는 매개변수의 개수와 인수의 개수가 일치하는지 체크하지 않음.

    function add(x, y) {
      return x + y;
    }
    
    // add 함수의 매개변수 x, y는 함수 몸체 내부에서만 참조 가능
    console.log(x, y);  // ReferenceError: x is not defined
    
    // 인수가 부족해 할당 되지 않는 매개변수의 값은 undefined 임
    console.log(add(2));  // NaN
    
    // 매개변수 보다 인수가 더 많은 경우 초과된 인수는 무시됨
    console.log(add(2, 5, 10));  // 7
  • 모든 인수는 암묵적으로 arguments 객체의 프로퍼티에 보관됨.

    function add(x, y) {
      console.log(arguments);
      // Arguments(3) [2, 5, 10, callee: ⨍, Symbol(Symbol.iterator): ⨍]
      
      return x + y;
    }
    
    add(2, 5, 10);

🗒️ 인수 확인

  • JS의 경우 함수를 정의할 때 적절한 인수가 전달되었는지 확인할 필요가 있음.

    function add(x, y) {
      if(typeof x !== 'number' || typeof y !== 'number') {
        // 매개변수를 통해 전달된 인수의 타입이 부적절한 경우 에러를 발생 시킴.
        throw new TypeError('인수는 모두 숫자 값이어야 합니다.');
      }
      
      return x + y;
    }
    
    console.log(add(2));  // TypeError: 인수는 모두 숫자 값이어야 합니다.
    console.log(add('a', 'b'));  // TypeError: 인수는 모두 숫자 값이어야 합니다.
  • 매개변수에 기본값을 사용할 수 있음.

    function add(a = 0, b = 0, c = 0) {
      return a + b + c;
    }
    
    console.log(add(1, 2, 3));  // 6
    console.log(add(1, 2));  // 3
    console.log(add());  // 0

🗒️ 매개변수의 최대 개수

  • 이상적 함수는 한 가지 일만 해야 하며 가급적 작게 작성해야 함.
  • 매개변수는 최대 3개 이상 넘지 않는 것을 권장하며, 그 이상의 매개변수가 필요할 시 하나의 매개변수를 선언하고 객체를 인수로 전달하는 것이 유리.

🗒️ 반환문

  • return 키워드를 사용해 실행 결과를 함수 외부로 반환 시킬 수 있음.
  • 함수 호출은 반환한 표현식의 평가 결과, 즉 반환값으로 평가됨.
  • return 키워드 뒤의 표현식을 비워두거나, 생략할 경우 undefined를 반환.

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

// 매개변수 primitive는 원시 값을 전달받고 매개변수 obj는 객체를 전달 받음
function changeVal(primitive, obj) {
  primitive += 100;
  obj.name = 'Kim';
}

// 외부 상태
var num = 100;
var person = { name: 'Lee' };

console.log(num);  // 100
console.log(person);  // {name: "Lee"}

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

// 원시 값은 원본이 훼손되지 않음
console.log(num);  // 100

// 객체는 원본이 훼손 됨
console.log(person);  // {name: "Kim"}

📁 다양한 함수의 형태

🗒️ 즉시 실행 함수

  • 함수 정의와 동시에 즉시 호출되는 함수를 즉시 실행 함수라고 함.

  • 단 한번만 호출되며 다시 호출 불가.

    // 익명 즉시 실행 함수
    (function () {
      var a = 3;
      var b = 5;
      return a * b;
    }());
    
    // 기명 즉시 실행 함수
    (function foo() {
      var a = 3;
      var b = 5;
      return a * b;
    }());
    
    foo();  // ReferenceError: foo is not defined
  • 일반 함수처럼 값을 반환할 수 있고 인수를 전달 할 수도 있음.

    // 즉시 실행 함수도 일반 함수처럼 값을 반환할 수 있음
    var res = (function () {
      var a = 3;
      var b = 5;
      return a * b;
    }());
    
    // 즉시 실행 함수에도 일반 함수처럼 인수를 전달 가능
    res = (function (a, b) {
      return a * b;
    }(3, 5));
    
    console.log(res);  // 15
  • 즉시 실행 함수 내에 코드를 모아 두면 혹시 있을 수도 있는 변수나 함수 이름의 충돌을 방지할 수 있음.

🗒️ 재귀 함수

  • 함수가 자기 자신을 호출하는 것을 재귀 호출이라 함.

  • 반복 처리를 위해 주로 사용.

    // 팩토리얼은 1부터 자신까지의 모든 양의 정수의 곱
    // n! = 1 * 2 * ... * (n-1) * n
    function factorial(n) {
      if(n <= 1) return 1;
      
      return n * factorial(n - 1);
    }
    
    console.log(factorial(3));  // 3! = 3 * 2 * 1 = 6
  • 반드시 탈출 조건을 만들어야 함. 없을 시 무한 호출되어 스택 오버플로에러가 발생.

  • 대부분의 재귀 함수는 반복문을 통해 구현이 가능함. 재귀 함수를 사용하는 편이 더 직관적으로 이해하기 쉬울 때만 한정적으로 사용해야 함. (무한 반복의 위험성 때문)

🗒️ 중첩 함수

  • 함수 내부에 정의된 함수를 중첩 함수 또는 내부 함수라 부름.

  • 중첩 함수는 외부 함수 내부에서만 호출 가능. 일반적으로 중첩 함수는 자신을 포함한 외부 함수의 헬퍼 함수의 역할.

    function outer() {
      var x = 1;
      
      // 중첩 함수
      function inner() {
        var y = 2;
        // 외부 함수의 변수를 참조할 수 있음
        console.log(x + y);  // 3
      }
      
      inner()
    }
    
    outer()
  • 호이스팅으로 인해 혼란이 발생할 수 있으므로 if문이나 for문 등의 코드 블록에서 함수 선언문을 통해 함수를 정의하는 것은 바람직 하지 않음.

  • 중첩 함수는 스코프와 클로저에 깊은 관련이 있음.

🗒️ 콜백 함수

  • 함수의 매개변수를 통해 다른 함수의 내부로 전달되는 함수를 콜백 함수라고 부름.

  • 매개변수를 통해 함수의 외부에서 콜백 함수를 전달받은 함수를 고차 함수라고 부름.

  • 고차 함수는 콜백 함수를 자신의 일부분으로 합성.

    // 외부에서 전달받은 ⨍을 n만큼 반복 호출
    function repeat(n, f) {
      for(var i = 0; i < n; i++) {
        logOdds(i);
      }
    }
    
    // logOdds 함수는 단 한번만 생성
    var logOdds = function (i) {
      if (i % 2) console.log(i);
    }
    
    // 고차 함수에 함수 참조를 전달
    repeat(5, logOdds);  // 1 3

🗒️ 순수 함수와 비순수 함수

  • 어떤 외부 상태에 의존하지도 않고 변경하지도 않는, 부수 효과가 없는 함수를 순수 함수라 부름.

    var count = 0;  // 현재 카운트를 나타내는 상태
    
    // 순수 함수 increase는 동일한 인수가 전달되면 언제나 동일한 값을 반환
    function increase(n) {
      return ++n;
    }
    
    // 순수 함수가 반환한 결과값을 변수에 재할당해서 상태를 변경
    count = increase(count);
    console.log(count);  // 1
    
    count = increase(count);
    console.log(count);  // 2
  • 외부 상태를 변경하는, 부수 효과가 있는 함수를 비순수 함수라고 부름.

    var count = 0;  // 현재 카운트를 나타내는 상태: increase 함수에 의해 변화
    
    // 비순수 함수
    function increase() {
      return ++count;
    }
    
    // 비순수 함수는 외부 상태(count)를 변경하므로 상태 변화를 추적하기 어려워짐
    increase();
    console.log(count);  // 1
    
    increase();
    console.log(count);  // 2
  • 함수가 외부 상태를 변경하면 상태 변화를 추적하기 어려워짐. 따라서 함수 외부 상태의 변경을 지양하는 순수 함수 사용을 하는 것이 좋음.

  • 비순수 함수를 최대한 줄이는 것은 부수 효과를 최대한 억제하는 것과 같음.

  • 함수형 프로그래밍은 순수 함수와 보조 함수의 조합을 통해 외부 상태를 변경하는 부수 효과를 최소화해 불변성을 지향하는 프로그래밍 패러다임임.

  • 로직 내에 존재하는 조건문과 반복문을 제거해서 복잡성을 해결하며, 변수 사용을 억제하거나 생명주기를 최소화해서 상태 변경을 피해 오률 최소하는 것을 목표로 함.

profile
프론트엔드 개발자를 꿈꾸는 이

0개의 댓글