JavaScript

[모던 자바스크립트] 함수

jjin502 2022. 7. 5. 16:10

함수란

  • 수학에서의 함수 : 입력을 받아 출력을 내보내는 일련의 과정을 정의한 것.
  • 프로그래밍에서의 함수 : 일련의 과정을 문으로 구현하고 코드 블록으로 감싸서 하나의 실행단위로 정의한 것.
// 12-1
    // 함수 : 일련의 과정을 문으로 구현하고 코드블록으로 감싸서 하나의 실행단뒤로 정의한 것.
    // f(x, y) = x+y
    function add(x, y) {
      return x + y;
    }

    console.log(add(2, 5)); // 7

관련 용어

  • 매개 변수 : 함수 내부로 전달 받는 변수
  • 인수 : 넘겨주는 값
  • 반환값 : 출력하는 값

특징

  • 함수 = 값
  • 따라서 여러개 존재 가능 → 구별하기 위해 식별자인 함수 이름을 사용

함수 생성 방법

  • 함수 정의를 통해 생성함

어떻게 정의해요 .. ?

  • 다양한 방법으로 !
// 12-2
    // 함수 정의
    function add(x, y) {
      return x + y;
    }

    // 주의 : 정의만으론 실행 안됨.

주의

  • 인수를 매개변수를 통해 함수에 전달 하면서 함수의 실행을 명시적으로 지시해야한다.
  • 이를 함수 호출이라고 한다 !!
// 12-3
    // 함수 정의
    function add(x, y) {
      return x + y;
    }

    // 정의만으론 실행 안됨.

    // 호출이 필요함 !!!
    var res = add(2, 5);
    console.log(res);

함수를 사용하는 이유

  • 필요할 때마다 호출 가능 !! → 코드의 재사용이라는 측면에서 매우 유용하다.
  • 유지보수의 편의성을 높임
  • 실수를 줄임으로써 코드의 신뢰성을 높임
  • 코드의 가독성을 향상시킴

함수 리터럴

  • 객체를 리터럴로 생성하는 것 같이 함수도 가능함
  • 구성 : function 키워드, 함수 이름, 매개 변수 목록, 함수 몸체로 구성됨
// 12-4
    // 변수에 함수 리터럴을 할당
    var f = function add(x, y) {
      return x + y;
    };
  • 함수 = 객체

그럼 함수를 객체 처럼, 객체를 함수 처럼 쓸 수 있나요 .. ?

  • 노놉. 일반 객체와 함수는 다른 점이 있다
  • 바로 호!출!의 차이
  • 함수 객체만의 고유의 프로퍼티를 가짐.

함수 정의

  • 함수를 호출하기 이전에 인수를 전달받을 매개변수와 실행할 문들, 그리고 반환할 값을 지정하는 것.

함수 선언문

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

    // 함수 참조
    // console.dir은 console.log와 달리 함수 객체의 프로퍼티까지 출력한다.
    // 단, node.js환경에서는 console.log와 같은 결과가 출력된다.
    console.dir(add); // f add(x,y)
    // 함수 호출
    console.log(add(2, 5)); // 7
// 12-6

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

    // 함수 이름 생략 불가능 함.
    // Uncaught SyntaxError: Function statements require a function name
// 12-7

    // 함수 선언문은 표현식이 아닌 문이므로 변수에 할당할 수 없음.
    // 하지만 함수 선언문이 변수에 할당되는 것처럼 보임.
    var add = function add(x, y) {
      return x + y;
    };

    // 함수 호출
    console.log(add(2, 5));

이렇게 동작하는 이유?

 

  • 자바스크립트 엔진이 코드의 문맥에 따라 동일한 함수 리터럴을 표현식이 아닌 문인 함수 선언문으로 해석하는 경우와 표현식인 문인 함수 리터럴 표현식으로 해석하는 경우가 있기 때문 ...
// 12-8

    // 기명 함수 리터럴을 단독으로 사용하면 함수 선언문으로 해석된다.
    // 함수 선언문에서는 함수 이름을 생략할 수 없다.
    function foo() {
      console.log("foo");
    }

    foo();

    // 함수 리터럴을 피연산자로 사용 -> 함수 선언문 뿐만 아니라 함수 리터럴 표현식으로 해석된다.
    // 함수 리터럴에서는 함수 이름을 생략할 수 있다.
    (function bar() {
      console.log("bar");
    });
    bar();
    // Uncaught ReferenceError: bar is not defined
  • 자바 스크립트엔진은생성된 함수를 호출하기 위해 함수 리흠과 동일한 이름의 식별자를 암묵적으로 생성하고, 거기에 함수 객체를 할당한다.
// 12-9
    var f = function add(a, b) {
      return a + b;
    };
    // var f(->식별자) = function add(->함수이름)(a, b) {
    //   return a + b;
    // };

    console.log(f(2, 3));
    // console.log(f(->식별자)(2, 3));
  • 함수는 함수 이름으로 호출하는 것이 아니라 함수 객체를 가리키는 식별자로 호출한다.

함수 표현식

  • 일급 객체 : 값의 성질을 갖는 객체
  • 자바스크립트 함수 = 일급 객체.
// 12-10
    // 따라서, 함수 리터럴로 생성한 함수 객체를 변수에 할당할 수 있음 = 함수 표현식
    var add = function (a, b) {
      return a + b;
    };
    // 함수리터럴의 함수 이름 => 생략 ㄱㄴ == 익명함수

    console.log(add(2, 3));
  • 함수 호출 시 함수 객체를 가리키는 식별자를 사용해야한다.
// 12-11
    // 기명 함수 표현식
    var add = function foo(a, b) {
      return a + b;
    };

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

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

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

// 12-12
    // 함수 생성 시점과 함수 호이스팅
    // 함수 선언문으로 정의한 함수와 함수 표현식으로 정의한 함수의 생성시점이 다름.
    // 함수 참조
    console.dir(add);
    console.dir(sub);

    // 함수 호출
    console.log(add(2, 5));
    console.log(sub(2, 5));

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

    // 함수 표현식
    // 변수 호이스팅이 발생함
    var sub = function (x, y) {
      return x - y;
    };
    // 함수 호이스팅
    // 함수 선언문이 코드의 선두로 끌어 올려진 것처럼 동작하는 자바스크립트 고유의 특징

Function 생성자 함수

// 12-13
    // Function 생성자 함수
    // 생성자 함수 : 객체를 생성하는 함수

    var add = new Function("x", "y", "return x+y");

    console.log(add(2, 5));
    // 일반적이지 않으며, 바람직하지도 않음.
    // 클로저를 생성하지 앟는 등 함수 선언문이나 함수 표현식으로 생성한 함수와 다르게 동작함.
// 12-14
    // Function 생성자 함수
    // 생성자 함수 : 객체를 생성하는 함수

    var add1 = (function () {
      var a = 10;
      return function (x, y) {
        return x + y + a;
      };
    })();

    console.log(add1(2, 5));

    var add2 = (function () {
      var a = 10;
      return new Function("x", "y", "return x+y+a");
    })();

    console.log(add2(2, 5));
    // Uncaught ReferenceError: a is not defined

화살표 함수

// 12-15
    // 화살표 함수
    // function 키워드 대신 사용
    // 생성자 함수로 사용할 수 없다.

    const add = (x, y) => x + y;
    console.log(add(2, 5));

함수 호출

매개변수와 인수

  • 매개변수와 인수에는 개수와 타입의 제한이 없다.
// 12-16
    // 함수 호출
    // 함수 선언문
    function add(x, y) {
      return x + y;
    }

    // 함수 호출
    // 인수 1과 2가 매개변수 x와 y에 순서대로 할당, 함수 몸체의 문들이 실행
    var res = add(1, 2);
// 12-17
    // 함수 호출
    // 함수 선언문
    function add(x, y) {
      console.log(x, y);

      return x + y;
    }

    add(1, 2);

    // add함수의 매개변수 x,y는 함수 몸체 내부에서만 참조할 수 있다.
    console.log(x, y);
    // Uncaught ReferenceError: x is not defined
// 12-18
    // 함수 호출
    function add(x, y) {
      return x + y;
    }

    console.log(add(2));
    // NaN
    // 인수가 부족하기 때문에
 // 12-19
    // 함수 호출
    function add(x, y) {
      return x + y;
    }

    console.log(add(2, 5, 10));
    // 인수가 초과된 경우 -> 무시
// 12-20
    // argument 객체 - 함수 정의 시 매개변수 개수를 확정할 수 없는 가변 인자 함수 구현시 유요하게 사용.
    function add(x, y) {
      console.log(arguments);
      // Arguments(3) [2, 5, 10, callee: ƒ, Symbol(Symbol.iterator): ƒ]
      return x + y;
    }

    console.log(add(2, 5, 10));

인수 확인

// 12-21
    // 인수 확인
    function add(x, y) {
      return x + y;
    }
    // 어떤 타입의 인수 전달, 반환하는지 명확하지 않음.
    // 따라서 이렇게 될수도 있음

// 12-22
    console.log(add(2));
    // 매개변수 인수 개수 일치 확인
    console.log(add("a", "b"));
    // 동적타입 언어이기 때문에 매개변수 타입을 사전에 지정할 수 없음.
  • 문법상 이상 없기 때문에 아무런 이의제기 없이 코드를 실행 할 것.

이러한 현상이 발생한 이유?

  1. 자바스크립트 함수는 매개변수와 인수가 일치하는지 확인하지 않는다.
  2. 자바스크립트는 동적 타입 언어다. 따라서 자바스크립트 함수는 매개변수의 타입을 사전에 지정할 수 없다.
  • 따라서 자바스크립트의 경우 함수를 정의할 때 적절한 인수가 전달됐는지 확인할 필요가 있음
// 12-23
    // 인수 확인
    function add(x, y) {
      if (typeof x !== "number" || typeof y !== "number") {
        throw new TypeError("인수는 모두 숫자 값이어야합니다.");
      }
      return x + y;
    }
    console.log(add(2));
    console.log(add("a", "b"));

    // TypeError: 인수는 모두 숫자 값이어야합니다.
  • 인수가 전달되지 않은 경우 단축 평가를 사용해 매개변수에 기본값을 할당하는 방법도 있다.
// 12-24
    function add(x, y, z) {
      x = x || 0;
      y = y || 0;
      z = z || 0;
      return x + y + z;
    }
    console.log(add());
    console.log(add(1));
    console.log(add(1, 2));
    console.log(add(1, 2, 3));
  • 매개 변수 기본값은 매개변수에 인수를 전달하지 않았을 경우와 undefined 를 전달한 경우에만 유효함
// 12-25
    // 간소화 가능
    function add(x = 0, y = 0, z = 0) {
      return x + y + z;
    }
    console.log(add());
    console.log(add(1));
    console.log(add(1, 2));
    console.log(add(1, 2, 3));

매개변수의 최대 개수

  • 매개 변수의 개수가 많다 → 함수가 여러 가지 일을 한다는 증거
  • 이상적 함수 → 한 가지 일만해야하며, 가급적 작게 만들어야함!
// 12-26
    // 매개변수의 최대 개수
    // 이상적 함수 : 한 가지 일만 해야함, 가급적 작게 만들어야함.
    // jQuery의 ajax메서드에 객체를 인수로 전달하는 예
    $.ajax({
      method: "POST",
      url: "./user",
      data: { id: 1, name: "Sim" },
      cache: false
    });

반환문

// 12-27
    // 반환문
    function mul(x, y) {
      return x * y;
      // 반환문
    }

    // 함수 호출은 반환 값으로 평가됨
    var res = mul(3, 5);
    console.log(res);

    // 함수 호출 = 표현식

    // 반환문의 역할
    // 1. 함수의 실행을 중단하고 함수 몸체를 빠져나감.
    // 2. (다음장 ㄱㄱ)
// 12-28
    // 반환문
    function mul(x, y) {
      return x * y;
      // 반환문
      console.log("실행X");
      // 반환문 이후로는 무시
    }

    var res = mul(3, 5);
    console.log(res);

    // 반환문의 역할
    // 1. 함수의 실행을 중단하고 함수 몸체를 빠져나감.
    // 2. return 키워드 뒤에 오는 표현식을 평가해 반환함.
// 12-29
    // 반환문 -> 생략 가능
    function mul(x, y) {
      return;
    }

    console.log(mul());
    // undefined
// 12-30
    // 반환문 -> 생략 가능
    function mul(x, y) {}

    console.log(mul());
    // undefined
    // 주의 : 의도치 않은 결과 발생할 수도 있음
// 12-31
    function mul(x, y) {
      // return 키워드와 반환값 사이에 줄바꿈이 있으면
      return;
      // ASI에 의해 자동으로 ; 추가
      x * y;
      // ignore
    }

    console.log(mul(3, 5));
    // undefined
// 12-32
    return;
    // Uncaught SyntaxError: Illegal return statement

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

  • 매개변수도 함수 몸체 내부에서 변수와 동일하게 취급되므로 타입에 따라 값, 참조에 의한 방식을 따른다.
// 12-33
    // primitive - 원시값, obj - 객체 전달 받음
    function changeVal(primitive, obj) {
      primitive += 100;
      obj.name = "Sim";
    }

    // 외부 상태
    var n = 100;
    var person = { name: "Lee" };

    console.log(n);
    console.log(person);

    // 원시값 : 값 자체 복사되어 전달
    // 객체 : 참조 값이 복사되어 전달
    changeVal(n, person);

    // 원시 값은 원본이 훼손되지 않는다.
    console.log(n);

    // 객체는 원본이 훼손된다.
    console.log(person);

다양한 함수의 형태

즉시 실행 함수

  • 함수의 정의와 동시에 즉시 호출되는 함수
  • 단 한 번 호출
  • 다시 호출 할 수 없음
// 12-34
    // 즉시 실행 함수
    // 익명 즉시 실행함수
    (function () {
      var a = 3;
      var b = 5;
      return a * b;
    })();
// 12-35
    // 즉시 실행 함수
    // 기명 즉시 실행함수
    (function foo() {
      var a = 3;
      var b = 5;
      return a * b;
    })();

    foo();
    // Uncaught ReferenceError: foo is not defined
    // 그룹 연산자 내의 기명함수는 함수 선언문이 아니라 함수 리터럴로 평가 되며
    // 함수 이름은 함수 몸체에서만 참조할 수 있는 식별자이므호 즉시 실행함수를 다시 호출할 수는 없다.
// 12-36
    // 즉시 실행 함수
    // 반드시 그룸 연산자 (...)로 감싸야함.
    // 안그러면 에러 발생
    function () {
      // ...
    }();
    // Uncaught SyntaxError: Function statements require a function name

    // 이유 ?
    // 함수 정의가 함수 선언문의 형식에 맞지 않기 때문.
 // 12-37
    // 기명함수에서도 마찬가지
    function foo() {
      // ...
    }();
    // Uncaught SyntaxError: Function statements require a function name

    // 이유 ?
    // 자바스크립트 엔진이 암묵적으로 수행하는 세미콜론 자동 삽입기능에 의해 함수 선언문이 끝나는 위치에 세미콜론이 삽입되기 때문.
// 12-38
    // 즉시 실행 함수
    // function foo() {}();
    // Uncaught SyntaxError: Unexpected token ')'
    // 그룹 연산자에 피연산자가 없어서 에러 발생

    // 12-39
    // ();
    // Uncaught SyntaxError: Unexpected token ')'

    // 12-40
    console.log(typeof function f() {});
    // function
    console.log(typeof function () {});
    // function
  • 즉, 그룹 연산자로 함수를 묶은 이유는 먼저 함수 리터럴을 평가해 함수 객체를 생성하기 위함이다.
  • 따라서, 먼저 함수 리터럴을 평가해서 함수 객체를 생성할 수 있다면 다음과 같이 그룹연산자 이외의 연산자를 사용해도 좋음
// 12-41

    (function foo() {
      // ...
    })();
    (function foo() {
      // ...
    })();
    !(function foo() {
      // ...
    })();
    +(function foo() {
      // ...
    })();
  • 즉시 실행함수도 일반 함수처럼 값을 반환할 수 있고 인수를 전달할 수도 있음
// 12-42
    // 즉시 실행함수도 일반 함수 처럼 값을 반환할 수 있음

    var res = (function () {
      var a = 3;
      var b = 4;
      return a * b;
    })();

    console.log(res);

    // 즉시 실행함수도 일반 함수 처럼 값을 전달할 수 있음

    res = (function (a, b) {
      return a * b;
    })(3, 4);

    console.log(res);

재귀함수

  • 재귀호출 : 함수가 자기 자신을 호출 하는 것.
  • 재귀함수 : 재귀호출을 수행하는 함수
// 12-43
    // 재귀함수
    // 반복되는 처리를 위해 사용
    function cntdwn(n) {
      for (var i = n; i >= 0; i--) console.log(i);
    }

    console.log(cntdwn(10));
// 12-44
    // 재귀함수
    // 반복되는 처리를 위해 사용
    function cntdwn(n) {
      if (n < 0) return;
      console.log(n);
      cntdwn(n - 1);
    }

    console.log(cntdwn(10));
// 12-45
    // 재귀함수
    // 반복되는 처리를 위해 사용
    // 팩토리얼(계승)은 1부터 자신까지의 모든 양의 정수의 곱
    // n! = 1 * 2 * .. * (n-1) * n
    function ftl(n) {
      // 탈출 조건 : n이 1이하일 때 호출을 멈춤
      if (n <= 1) return 1;
      // 재귀호출
      return n * ftl(n - 1);
    }

    console.log(ftl(1));
    console.log(ftl(2));
    console.log(ftl(3));
    console.log(ftl(4));
    console.log(ftl(5));
// 12-47
    // 재귀함수
    // 반복되는 처리를 위해 사용
    // 팩토리얼(계승)은 1부터 자신까지의 모든 양의 정수의 곱
    // n! = 1 * 2 * .. * (n-1) * n
    function ftl(n) {
      // 탈출 조건 : n이 1이하일 때 호출을 멈춤
      if (n <= 1) return 1;
      // 재귀호출
      var res = n;
      while (--n) res *= n;
      return res;
    }

    console.log(ftl(1));
    console.log(ftl(2));
    console.log(ftl(3));
    console.log(ftl(4));
    console.log(ftl(5));

    // 반복문으로 구현할 수 있음
    // 그러나, 무한 반복 위험 -> 스택 오버플로 에러 발생 위험 있음.

중첩함수(내부함수)

  • 함수 내부에 정의된 함수
// 12-48
    // 중첩함수
    // 외부함수 : 중첩함수를 포함하는 함수
    // 중첩함수는 외부함수 내에서만 호출 가능
    // 역할 : 자신을 포함하는 헬퍼함수 역할을 함.
    function outer() {
      var x = 1;

      // 중첩함수
      function inner() {
        var y = 2;
        // 외부 함수의 변수를 참조할 수 있음.
        console.log(x + y);
      }
      inner();
    }

    outer();

콜백함수   

  • 함수의 매개변수를 통해 다른 함수의 내부로 전달되는 함수
// 12-49
    // 콜백함수

    // n만큼 어떤 일을 반복한다.
    function repeat(n) {
      // i를 출력
      for (let i = 0; i < n; i++) console.log(i);
    }

    repeat(5);
// 12-50
    // 콜백함수

    // n만큼 어떤 일을 반복한다.
    function repeat1(n) {
      // i를 출력
      for (let i = 0; i < n; i++) console.log(i);
    }

    repeat1(5);

    function repeat2(n) {
      for (let i = 0; i < n; i++) {
        if (i % 2) {
          console.log(i);
        }
      }
    }

    repeat2(5);
// 12-50
    // 콜백함수

    // n만큼 어떤 일을 반복한다.
    function repeat1(n) {
      // i를 출력
      for (let i = 0; i < n; i++) console.log(i);
    }

    repeat1(5);

    function repeat2(n) {
      for (let i = 0; i < n; i++) {
        if (i % 2) {
          console.log(i);
        }
      }
    }

    repeat2(5);
// 12-52
    // 콜백함수

    // 익명 함수 리터럴을 콜백 함수로 고차 함수에 전달한다.
    // 익명 함수 리터럴은 repeat 함수를 호출할 때마다 평가되어 함수 객체를 생성함.
    repeat(5, function (i) {
      if (i % 2) {
        console.log(i);
      }
    });
// 12-53
    // 콜백함수

	// 함수는 단 한 번만 생성됨
    var logOdds = function (i) {
      if (i % 2) {
        console.log(i);
      }
    };

    // 고차함수에 함수 참조를 전달한다.
    repeat(5, logOdds);
// 12-54
    // 콜백함수
    // 비동기 처리에 활용되는 중요한 패턴

    // 콜백함수를 사용한 이벤트 처리
    // myButton 클릭 시 콜백함수 실행
    document.getElementById("myButton").addEventListener("click", function () {
      console.log("button clicked");
    });

    // 콜백함수를 사용한  비동기 처리
    // 1초 후에 메세지 출력
    setTimeout(function () {
      console.log("1초 경과");
    }, 1000);
// 12-55
    // 콜백함수
    // 비동기 처리 뿐만 아니라 배열 고차 함수에서도 사용.

    // 콜백 함수를 사용하는 고차함수 map
    var res = [1, 2, 3].map(function (item) {
      return item * 2;
    });

    console.log(res);

    // 콜백 함수를 사용하는 고차함수 filter
    res = [1, 2, 3].filter(function (item) {
      return item % 2;
    });

    console.log(res);

    // 콜백 함수를 사용하는 고차함수 reduce
    res = [1, 2, 3].reduce(function (acc, cur) {
      return acc + cur;
    }, 0);

    console.log(res);

순수 함수와 비순수 함수

 // 12-56
    // 순수 함수 : 어떤 외부 상태에 의존하지 않고 변경하짇도 않는,부수효과가 없는 함수
    // 비순수 함수 : 어떤 외부 상태에 의존하거나 외부 상태를 변경하는, 부수효과가 있는 함수
    // 외부 상태 : 전역 변수, 서버 데이터, 파일, Console, DOM

    var cnt = 0;
    // 순수함수 increase는 동일한 인수가 전달되면 언제나 동일한 값을 반환함.
    function increase(n) {
      return ++n;
    }

    // 순수 함수가 반환한 결과값을 변수에 재할당해서 상태를 변경
    cnt = increase(cnt);
    console.log(cnt);

    cnt = increase(cnt);
    console.log(cnt);
// 12-57

	var cnt = 0;
    // 비순수함수 increase는 외부 상태에 의존하며 외부 상태를 변경.
    function increase() {
      return ++cnt;
    }

    // 비순수 함수는 외부 상태(cnt)를 변경하므로 상태 추적이 어려워짐.
    increase();
    console.log(cnt);

    increase();
    console.log(cnt);

마치며 ...

  • 함수는 프로그래밍이든 .. 수학이든 .. 많은곳에 쓰인다는걸 다시 깨달았고, 내가 알던 것 보다 훨씬 더 많은 종류의 함수가 존재한다는 것을 알았다 ..