티스토리 뷰

프로그래밍/JavaScript

함수

푸르른강물을먹이를찾아어슬렁거리는연어처럼 2020. 2. 11. 23:11

가. 함수란?

1. 정의 방식

  • function 명령으로 정의하기

    function 함수명(인수, ...){
        ...함수 안에서 실행되는 임의의 처리....
        return 반환값;
    }
  • Function 생성자로 정의하기

    • 함수를 직접 정의하는 특징(리터럴 표현과 비교했을 때)

      var 변수명 = new Function(인수, ... , 함수의 본체);
      //삼각형의 면적 얻는 함수 예제
      var getTriangle = new Function('base', 'height', 'return base * height /2;');
      var getTriangle = new Function('base, height', 'return base * height /2;');
  • 함수 리터럴 표현으로 정의하기

    • function(base, height){...} 로 이름없는 함수를 정의한 다음, 변수 triangle에 대입하고 있다.

      var getTriangle = function(base, height){
          return base * height /2;
      };
    • 익명함수, 또는 무명함수로도 불린다

  • 애로우 함수로 정의하기(ES2015)

    //{인수, ...) => {... 함수의 본체 ...}
    
    let getTriangle = (base, height) => {
        return base * height /2;
    };
    • 인수가 한개라면 괄호 생략 가능

      let getCircle = radius => radius * radius * Math.PI;
    • 단, 인수가 없다면 괄호 생략은 불가능하다.

    • 주의) 객체 리터럴을 건내는 경우, 리터럴 전체를 괄호로 감싸야 한다.

      let func = () => ({ hoge : '호게'});//올바른 예시
      let func = () =>{ hoge : '호게' };//결과적으로 undefined를 return

나. 함수를 정의할 때 주의할 네가지 사항

  • return 명령은 도중에 개행하지 않는다.

  • 함수는 데이터형의 하나다.

  • function 명령은 정적인 구조를 선언한다.

    console.log('삼각형의 면적 : ' + getTriangle(5, 2));
    function getTriangle(base, height){
        return base * height / 2;
    }
    • function 명령은 코드를 해석 / 컴파일하는 타이밍에 함수를 등록한다.
  • 함수 리터럴 / Function 생성자는 실행할 때 판단된다.

    console.log('삼각형의 면적 : ' + getTriangle(5, 2));//error: getTriangle is not a function
    var getTriangle = function(base, height){
        return base * height / 2;
    };
    • function 명령과는 달리, 함수 리터럴 / Function 생성자는 실행시(대입 시) 판단된다.
    • 따라서, 함수 리터럴 / Function 생성자로 함수를 정의하는 경우에는 '호출원의 코드보다 먼저 기술해야 할' 필요가 있다.

다. 변수는 어떤 곳에서 참조할 수 있을까? - 스코프

  1. 글로벌 변수와 로컬 변수의 차이점

  2. 변수 선언에 var 명령이 필수인 이유

    • var 명령으로 정의된 변수는 정의한 장소에 따라 변수의 스코프가 정해진다
    • 로컬변수를 정의하려면 반드시 var 명령을 사용해야 한다!
  3. 로컬 변수의 유효범위는 어디까지?

    var scope = 'Global Variable';
    
    function getValue(){
        console.log(scope); // (가) 결과 : undefined
        var scope = 'Local Variable';
        return scope;
    }
    console.log(getValue()); // 결과 : Local Variable
    console.log(scope); // 결과 : Global Variable
    • ((가) 시점에서) 로컬 변수는 함수 전체에서 유효하지만, 아직 정의되지 않았다(=undefined)
  4. 가인수의 scope - 기본형과 참조형의 차이 주의하기

    • 호출원으로부터 함수로 건네진 값을 받는 변수

      function getTriangle(base, height) { ... };// 가인수 : base, height
    • 기본형의 경우 예시

      var value = 10;
      function decrementValue(value){
          value--;
          return value;
      }
      console.log(decrementValue(100));//결과 : 99
      console.log(value);//결과 : 10
    • 참조형의 경우 예시

      var value=[1, 2, 4, 8, 16];
      function deleteElement(value){
          value.pop();
          return value;
      }
      console.log(deleteElement(value));// 결과 : [1,2,4,8]
      console.log(value);// 결과 : [1,2,4,8]
  5. 블록 레벨의 스코프는 존재하지 않는다(ES2015 이전)

  • Java의 경우(블록 스코프 예시)

    if(true){
        int i = 5;
    }
    System.out.println(i);// error
  • JavaScript의 경우

    if(true){
        var i = 5;
    }
    console.log(i); //결과 : 5
  • 보충설명 : 즉시 함수에서 변수명의 경합을 방지하기

    (function() {
        var i = 5;
        console.log(i);
    }).call(this); // 그 자리에서 바로 실행
    console.log(i);// scope 밖이므로 error
  1. 블록 스코프에 대응한 let 명령(ES 2015)

    if(true){
        let i = 5;
    }
    console.log(i);//error
    • let 명령으로 선언된 변수는 블록의 밖에서 무효가 된다.

    • 즉시 함수는 사용하지 않는다.

      {
          let i = 5;
          console.log(i);//결과 : 5
      }
      console.log(i);//변수 i는 scope 밖이므로 error
    • switch 블록에서의 let 선언은 주의가 필요하다.

      • switch 명령은 조건 분기의 전체로서 하나의 블록이다

        switch(x) {
            case 0:
                let value = 'x:0';
            case 1:
                let value = 'x:0';
        }//error
  2. 함수 리터럴 / Function 생성자에서의 스코프 차이

    var scope = 'Global Variable';
    function checkScope(){
        var scope = 'Local Variable';
        var f_lit = function(){ return scope; };
        console.log(f_lit()); // 결과 : Local Variable
    
        var f_con = new Function(){ return scope; };
        console.log(f_con()); // 결과 : Global Variable
    }
    checkScope();
    • 리터럴 생성자의 경우 Local Variable을 참조

    • Function 생성자 안의 변수는 그 선언 장소에 상관없이 항상 글로벌 scope로 간주

      ( -> 함수의 기법은 다양한 차이를 가지고 있다.)

라. 인수의 다양한 표기법

  1. JavaScript는 인수의 수를 체크하지 않는다.

    function showMessage(value){
        console.log(value);
    }
    showMessage(); // (1) 결과 : undefined
    showMessage('철수'); // (2) 결과 : 철수
    showMessage('철수','영희'); // (3) 결과 : 철수
    • 경우 (3)과 같이 인수가 넘치는 경우, 버려지는 것이 아니라 arguments 객체가 관리함. 따라서, 실제로 주어진 인수와 필요한 인수의 갯수가 다른 경우 에러를 반환하는 식의 처리도 가능(arguments.length를 이용하여)

    • 인수의 갯수가 부족한 경우를 위해, default 값을 정의할 수 있음.

      function getTriangle(base, height){
          if (base === undefined){ base = 1; }
          if (height === undefined){ height = 1;}
          return base * height / 2;
      }
      console.log(getTriangle(5));// 결과 : 2.5 (height = 1)
  2. 가변길이 인수의 함수 정의하기

    • arguments 객체의 역할은 인수의 타당성 체크만이 아니라, 가변길이 인수의 함수라는 기능도 있다.

    • 가변길이 인수의 함수란 '인수의 갯수가 미리 정해져 잇지 않은 함수'

      var showMessage = new Function('msg', 'console.log(msg);')
      // 주어진 인수가 2개(가인수와 처리 내용이 한 개씩)
      var getTriangle = new Function('base', 'height', 'return base * height /2;');
      // 주어진 인수가 3개(가인수 2개와 처리 내용이 한 개)
      //인수에 주어진 숫자의 합계를 구하는 sum 함수 예시
      function sum(){
          var result = 0;
          // 주어진 인수를 순서대로 취득하여 차례로 더하는 처리
          for (var i = 0, len = arguments.length; i < len; i++){
              var tmp = arguments[i];
              if (typeof tmp !== 'number'){
                  throw new Error('인수값이 숫자가 아닙니다. : '+ tmp);
              }
              result += tmp;
          }
          return result;
      }
      
      try {
          console.log(sum(1, 3, 5, 7, 9));//결과 : 25
      }catch(e) {
          window.alert(e.message);
      }
  3. 명시적으로 선언된 인수와 가변길이 인수 혼재시키기

    • 앞에서 언급한 두가지 경우 혼재 가능.

      function printf(format){
          // 인수의 2번째 이후를 순서대로 처리
          for (var i = 0, len = arguments.length; i < len; i++){
              //RegExp는 정규표현식을 만드는 함수.
              var pattern = new RegExp('\\{' + (i - 1) + '\\}', 'g');
              format = format.replace(pattern, arguments[i]);
          }
          console.log(format);
      }
      printf('안녕하세요, {0}씨. 나는 {1}입니다.', '시온', '피카츄');
      //결과 : 안녕하세요, 시온씨. 나는 피카츄입니다.
      // 제 1인수(서식 문자열)의 플레이스 홀더를 제2인수 이후의 값으로 치환하여 출력
    • 무명 인수는 필요 최소한으로

  4. 명명된 인수로 코드를 읽기 쉽게 하기

    • 명명된 인수의 장점

      • 인수가 많아져도 코드의 의미를 알기 쉽다.

      • 생략 가능한 인수를 스마트하게 표현할 수 있다.

      • 인수의 순서를 자유롭게 변경할 수 있다.

      • 함수를 호출할 때, 앞의 인수만 생략하거나 인수의 순서를 변경하는 것이 가능하다.

        getTriangle({ height : 4}) // 앞의 인수만 생략
        getTriangle({ height : 4, base : 5}) // 인수의 순서를 변경
    • 명명된 인수의 구체적인 구현 예시

      function getTriangle(args) {
          if (args.base === undefined) { args.base = 1;}
          if (args.height === undefined) {args.height = 1;}
          return args.base * args.height / 2;
      }
      console.log(getTriangle({ base:5, height:4 })); //결과 : 10

마. ES2015의 인수 표기법

  1. 인수의 디폴트값(ES2015)

    function getTriangle(base = 1, height = 1) {
        return base * height /2;
    }
    console.log(getTriangle(5)); //결과 : 2.5
    • 디폴트 값에는 리터럴 뿐만 아니라 다른 인수, 함수(식)의 결과등을 지정하는 것도 가능하다.

    • 단, 다른 인수를 디폴트 값으로 할 경우 참조할 수 있는 것은 자기 자신보다 앞에서 정의된 것 뿐이다.

      function multi(a = b, b = 5) {
          return a * b;
      }
      console.log(multi());
    • 디폴트 값을 이용시 주의점

      • 디폴트 값이 적용될 경우와 적용되지 않을 경우

        • 디폴트 값이 적용되는 것은 인수가 명시적으로 건네지지 않았을 경우 뿐

        • null / false / 0 / empty string 등 이라도 명시적으로 건네진 경우 디폴트 값 적용 X

          function getTriangle(base = 1, height = 1) { .... }
          console.log(getTriangle(5, null));//결과 : 0
        • 디폴트 값을 갖는 가인수는 인수 리스트의 끝으로

          function getTriangle(base = 1, height){ ... }
          console.log(getTriangle(10));//base = 10, height = undefined
        • 주의) 값이 건네지지 않았다고 해도 인수가 부족하다고 일일히 알려주지 않는다.

          function required(){
              throw new Error('인수가 부족합니다.');
          }
          function hoge(value = required()) {
              return value;
          }
          hoge(); // 결과 : Error : 인수가 부족합니다.

          위와 같은 방식으로 예외만 발생시키는 required() 함수를 준비하여 필수 인수의 디폴트 값으로 지정하면 인수가 지정되지 않을 떄 required 함수가 발생하게 된다.

  2. 가변길이 인수의 함수 정의하기(ES2015)

    function sum(...nums) {
        let result = 0;
        for (let num of nums) {
            if (typeof num !== 'number') {
                throw new Error('지정값이 숫자가 아닙니다: ' + num);
            }
            result + = num;
        }
        return result;
    }
    
    try {
        console.log(sum(1, 3, 5, 7, 9));
    } catch(e) {
        window.alert(e.message);
    }
    • 장점
      • 함수가 가변길이 인수를 취하는 것을 쉽게 알 수 있다.
      • 모든 배열 조작 가능
  3. '...' 연산자에 의한 인수의 전개(ES2015)

    console.log(Math.max(15, -3, 78, 1)); // (1) 결과 : 78
    console.log(Math.max([15, -3, 78, 1]);// (2) 결과 : NaN
    • 위의 예시에서 (1)의 경우,Math.max메소드가 가변길이 인수를 취하므로, 인수의 최댓값을 구할 수 있었으나, (2)와 같이 배열을 건넨 경우에는 인식할 수 없어 NaN의 결과가 나왔다.

    • (ES2015) 이전의 처리법

      console.log(Math.max.apply(null, [15, -3, 78, 1])); // 결과 : 78
    • (ES2015) 처리법 - '...' 연산자 사용하기

      console.log(Math.max(...[15, -3, 78, 1])); //결과 : 78

      '...' 를 부여함으로서, 배열 안의 요소가 max 메소드로 전달 되었다.

  4. 명명된 인수로 코드를 이해하기 쉽게 하기(ES2015)

    • (ES2015) 이전 문법
    function getTriangle(base, height){
        if (base === undefined){ base = 1; }
        if (height === undefined){ height = 1;}
        return base * height / 2;
    }
    console.log(getTriangle(5));// 결과 : 2.5 (height = 1)
  • (ES2015) 이후 문법 - 분할 대입

    function getTriangle({base = 1, height = 1}) {
      return base * height / 2;
    }
    console.log(getTriangle({ base : 5, height : 4 })); // 결과 : 10
  • (ES2015) 이후 문법 - 객체로부터 특정 프로퍼티만 추출하기

    function show({name}) {
        console.log(name);
    };
    
    let member = {
        mid: 'Y0001',
        name: '정시온',
        address: 'shion_jung@example.com'
    };
    show(member); // 결과 : 정시온

바. 함수 호출과 반환값

  1. 복수의 반환값을 개별 변수에 대입하기(ES2015)

    function getMaxMin(...nums) {
        return [Math.max(...nums), Math.min(...nums)];
    }
    
    let result = getMaxMin(10, 35, -5, 78, 0);
    console.log(result); // 결과 : [78, -5]
    
    let [max, min] = getMaxMin(10, 35, -5, 78, 0);
    console.log(max); // 결과 : 78
    console.log(min); // 결과 : -5
  2. 함수 자신을 재귀적으로 호출하기 - 재귀함수

    function factorial(n){
        if (n != 0){ return n * factorial( n - 1 ); }
        return 1;
    }
    console.log(factorial(5));
  3. 함수의 인수도 함수 - 고차 함수

    // 고차 함수 arrayWalk를 정의
    function arrayWalk(data, f) {
        for (var key in data) {
            f(data[key], key);
        }
    }
    
    //배열을 처리하기 위한 사용자 정의 함수
    function showElement(value, key) {
        console.log(key + ' : ' + value);
    }
    
    var ary = [1, 2, 4, 8, 16];
    arrayWalk(ary, showElement);
    /*
    결과
    0 : 1
    1 : 2
    2 : 4
    3 : 8
    4 : 16
    */
    //고차 함수 arrayWalk를 정의
    function arrayWalk(data, f) {
        for (var key in data) {
            f(data[key], key);
        }
    }
    
    //결과값을 대입하기 위한 글로벌 변수
    var result = 0;
    function sumElement(value, key) {
        // 주어진 배열 요소로 변수 result를 가산
        result += value;
    }
    
    var ary = [1, 2, 4, 8, 16];
    arrayWalk(ary, sumElement);
    console.log('합계 : ' + result); //결과 : 합계 : 31
    • 고차 함수를 이용함으로써 '큰 범위의 기능만을 정의해두고 상세한 기능은 함수의 이용자가 자유롭게 결정한다'
  4. '일회용 함수'는 익명 함수로

    // 익명 함수를 이용하여 위의 코드를 수정함.
    // 고차 함수 arrayWalk를 정의
    function arrayWalk(data, f) {
        for (val key in data) {
            f(data[key], key);
        }
    }
    
    var ary = [1, 2, 4, 8, 16];
    arrayWalk(
        ary,
        function (value, key) {
            console.log(key + ' : ' + value);
        }
    );
    • 좀 더 관계에 대해 이해하기 수월해지고, 의도하지 않은 이름의 중복을 회피할 수 있다.

사. 높은 수준의 함수 테마

  1. 템플릿 문자열을 애플리케이션 사양으로 커스터마이즈하기 - 태그 부여 템플릿 문자열(ES2015)

    • 템플릿 문자열(Tagged template strings)

      //주어진 문자열을 이스케이프 처리
      function escapeHtml(str) {
          if (!str) { return ''; }
          str = str.replace(/&/g, '&amp;');
          str = str.replace(/</g, '&lt;');
          str = str.replace(/>/g, '&gt;');
          str = str.replace(/"/g, '&quot;');
          str = str.replace(/'/g, '&#39;');
          return str;
      }
      
      // 분해된 templates와 values를 순서대로 연결(values는 escapeHtml 함수로 이스케이프)
      function e(templates, ...values) {
          let result = '';
          for (let i = 0, len = templates.length; i < len; i++){
              result += templates[i] + escapeHtml(values[i]);
          }
          return result;
      }
      
      // 템플릿 문자열을 이스케이프 처리
      let name = '<"Mario" & \'Luigi\'>';
      console.log(e`안녕하세요, ${name}씨!`);
      //결과: 안녕하세요, &lt;&quot;Mario&quot; &amp; &#39;Luigi&#39;&gt;씨!
    • 태그 부여 템플릿 문자열로 이용하기 위해서는 함수는 다음의 조건을 만족해야 한다.(function e)

      • 인수로서 '템플릿 문자열(분해한 것)', '삽입할 변수(가변길이 인수)'를 수령할 것
      • 반환값으로서 가공이 끝난 문자열을 반환할 것
  2. 변수는 어떤 순서로 해결될까? - 스코프 체인

    • 스코프 체인이란 글로벌 객체, Call 객체를 생성 순서대로 연결한 리스트를 말한다.

      var y = 'Global';
      function outerFunc() {
          var y = 'Local Outer';
      
          function innerFunc() {
              var z = 'Local Inner';
              console.log(z); // 결과 : Local Inner
              console.log(y); // 결과 : Local Outer
              console.log(x); // 결과 : 에러 (변수 x는 미정의)
          }
          innerFunc(); //InnerFunc 함수의 호출
      }
      outerFunc(); // outerFunc 함수의 호출
  3. 같은 행동을 하는 객체 - 클로저

    function closure(init) {
        var counter = init;
    
        return function() {
            return ++counter;
        }
    }
    
    var myClosure = closure(1);
    console.log(myClosure());// 결과 : 2
    console.log(myClosure());// 결과 : 3
    console.log(myClosure());// 결과 : 4
    • 보통은 함수 안에서 사용된 로컬 변수(여기에서는 변수 counter)는 함수의 처리가 종료한 시점에 파기된다. 하지만 위의 예시에서는 closure함수로부터 되돌려진 '익명 함수가 로컬 함수 counter를 계속 참조하고 있다'는 이유로 closure함수의 종료 후에도 로컬 변수 counter는 계속해서 보관 유지된다.

    • 익명 함수가 유효한 사이에는 보관 / 유지되는 예시

      • 익명 함수를 나타내는 Call 객체
      • Closure함수의 Call 객체
      • 글로벌 객체
    • 클로저는 일종의 '기역 영역을 제공하는 구조'

      function closure(init) {
          var counter = init;
      
          return function() {
              return ++counter;
          }
      }
      
      var myClosure1 = closure(1);
      var myClosure2 = closure(100);
      
      console.log(myClosure1()); //결과 : 2
      console.log(myClosure2()); //결과 : 101
      console.log(myClosure1()); //결과 : 3
      console.log(myClosure2()); //결과 : 102
    • myClosure1, myClosure2 각각의 독립된 기억 공간이 생성

'프로그래밍 > JavaScript' 카테고리의 다른 글

객체 지향 구문  (0) 2020.02.11
기본 데이터 조작하기  (0) 2020.02.11
기본적인 작성법 익히기  (0) 2020.02.06
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/04   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
글 보관함