[카테고리:] IT

다양한 IT 기술에 대한 내용을 공유합니다.

  • [JavaScript] 날짜 구하기(Data 함수)

    [JavaScript] 날짜 구하기(Data 함수)

    Data 객체는 날짜와 시간을 제공하는 생성자 함수이다.
    인자 없이 객체를 선언하면 현재 날짜와 시간을 반환한다.

    var value = new Date();
    console.log(value);
    // Thu Jan 09 2020 14:44:13 GMT+0900 (한국 표준시)
    

    특정 값을 구하는 메서드

    메서드
    getFullYear()
    getMonth()
    getDate()날짜
    getDay()요일

    응용

    2015년 12월 25일의 요일을 구하는 법

    function func(a, b) {
        return ['SUN', 'MON', 'TUE', 'WED', 'THU', 'FRI', 'SAT'][new Date(2015, a - 1, b).getDay()];
    }
    console.log(func(12, 25)); // FRI
    

    References

    프로그래머스 문제 풀이 Level 1

  • [JavaScript] 프로토타입(Prototype)

    [JavaScript] 프로토타입(Prototype)

    정의

    자바스크립트는 클래스라는 개념이 없다. 클래스는 자바, 파이썬, 루바 등 객체지향 언어에서 빠질수 없는 개념이다. 하지만 자바스크립트도 객체지향언어인데, 클래스 대신 프로토타입(Prototype)을 기반으로 클래스의 상속 기능을 흉내내도록 구현하여 사용한다. 그래서 자바스크립트는 프로토타입 기반의 객체 지향 언어라고 한다.

    자바스크립트의 모든 객체는 자신의 부모역할을 담당하는 객체와 연결되어 있다. 이것은 마치 객체 지향의 상속 개념과 같이 부모 객체의 프로퍼티 또는 메서드를 상속받아 사용할 수 있게 한다. 이러한 부모 객체를 프로토타입 이라 한다.

    프로토타입은 언제 쓰는가

    function Person() {
        this.eyes = 2;
        this.nose = 1;
    }
    
    var kang = new Person();
    var park = new Person();
    
    console.log(kang.eyes); // 2
    console.log(kang.nose); // 1
    console.log(park.eyes); // 2
    console.log(park.nose); // 1
    

    kangparkeyesnose를 공통적으로 가지고 있는데, 메모리는 eyesnose가 두개씩 총 4개에 할당된다. 객체를 100개를 만들면 200개의 변수가 메모리에 할당된다. 이런 메모리 낭비 문제를 프로토타입으로 해결할 수 있다.

    function Person() {}
    
    Person.prototype.eyes = 2;
    Person.prototype.nose = 1;
    
    var kang  = new Person();
    var park = new Person();
    
    console.log(kang.eyes); // 2
    console.log(park.nose); // 1
    

    간략히 설명하면 Person.prototype라는 빈 객체가 어딘가에 존재하고 Person함수로부터 생성된 객체(kang, park)은 어딘가에 존재하는 객체의 값을 모두 갖다쓸 수 있다. 즉, eyesnose를 어딘가에 있는 빈 객체(Person.prototype)에 넣어두고, kimpark이 공유해서 사용하는 것이다.

    프로토타입 객체와 프로토타입 링크

    자바스크립트에서는 프로토타입 객체(prototype object)와 프로토타입 링크(prototype link)라는 것이 존재한다. 그리고 이 둘을 통틀어 프로토타입이라고 부른다.

    객체는 언제나 함수로 생성된다.

    function Person() {} // 함수
    var obj = new Person(); // new 키워드와 함수로 객체를 생성
    

    obj 객체는 Person이라는 함수로 생성된 객체이다. 일반적인 객체 리터럴 방식도 예외는 아니다.

    var obj = {};
    

    객체 리터럴 방식으로 객체를 생성하였는데 이 방식은 아래 방식과 같다.

    var obj = new Object();
    

    Object도 객체를 만드는 생성자 함수이다. Object와 마찬가지로 Function, Array도 모두 생성자 함수이다. 이 사실은 프로토타입과 밀접하게 관련이 있는데 함수가 정의될 때는 2가지 일이 동시에 일어나기 때문이다.

    함수가 정의될 때

    해당 함수에 constructor(생성자) 자격 부여

    constructor 자격이 부여되면 new 키워드를 통해 객체를 만들수 있다. 오직 함수만 new 키워드를 사용할 수 있다.

    var obj = {}; // 객체 선언
    var a = new obj();
    // Uncaught TypeError: obj is not a constructor
    

    obj는 생성자 자격이 없다고 나온다. 오직 함수만이 constructor 자격을 가질 수 있다.

    해당 함수의 프로토타입 객체 생성 및 연결

    함수를 정의하면 함수만 생성되는 것이 아니라 프로토타입 객체도 같이 생성이 된다. 생성된 함수는 prototype라는 속성을 통해 프로토타입 객체에 접근할 수 있다. 프로토타입 객체는 일반적인 객체와 같으며, 기본적인 속성으로 constructor__proto__를 가지고 있다.

    function Person() {}
    console.log(Person.prototype);
    // {constructor: ƒ}
    // > constructor: ƒ Person()
    // > __proto__: Object
    

    constructor는 프로토타입 객체와 같이 생성되었던 함수를 가르키고 있다. __proto__은 프로토타입 링크다. 프로토타입 링크는 아래에서 다시 알아보도록 하고 위에서 언급된 eyes, nose예제를 다시 살펴보겠다.

    function Person() {}
    
    Person.prototype.eyes = 2;
    Person.prototype.nose = 1;
    
    var kang  = new Person();
    var park = new Person();
    
    console.log(Person.prototype);
    // {eyes: 2, nose: 1, constructor: ƒ}
    // > eyes: 2
    // > nose: 1
    // > constructor: ƒ Person()
    // > __proto__: Object
    

    Person.prototype라는 빈 객체가 어딘가에 존재하고, 그 객체에 eyes, nose값을 할당한 것을 확인할 수 있다. 프로토타입 객체는 일반적인 객체이므로 속성을 마음대로 추가, 삭제할 수 있으며 kangparkPerson함수를 통해 생성되었으니 Person.prototype를 참조할 수 있게 된다.

    프로토타입 링크

    function Person() {}
    Person.prototype.eyes = 2;
    var kang  = new Person();
    
    console.log(kang);
    // Person {}
    console.log(kang.eyes);
    // 2
    

    kang객체에 따로 eyes속성을 선언하지 않았지만 kang.eyes를 실행하면 2라는 값을 참조한다. 위에서 설명했듯이 프로토타입 객체의 eyes속성을 참조한 것인데, 이것이 가능한 이유는 kang이 가지고 있는 __proto__속성이 프로토타입 객체를 가르키고 있기 때문이다.

    console.log(kang.__proto__);
    // {eyes: 2, nose: 1, constructor: ƒ}
    

    kang.__proto__ 속성을 확인해보니 프로토타입 객체를 가르키고 있다. kang객체는 직접 eyes속성을 가지고 있지 않아 eyes속성을 찾을 때 까지 상위 프로토타입을 탐색한다. 최상위인 Object의 프로토타입 객체까지 도달했는데도 못찾을 경우 undefined를 리턴한다. 이렇게 __proto__속성을 통해 상위 프로토타입과 연결되어있는 형태를 프로토타입 체인이라고 한다. 이런 프로토타입 체인 구조 때문에 모든 객체는 Object의 자식이라고 하며, Object에 있는 모드 속성을 사용할 수 있다.

    References

    [Javascript] 프로토타입 이해하기
    Javascript 기초 – Object prototype 이해하기
    JavaScript : 프로토타입(prototype) 이해

  • [Yum] Yum 명령어

    [Yum] Yum 명령어

    패키지 설치

    # install [패키지명]
    

    패키지 삭제

    # yum remove [패키지명]
    

    패키지 업데이트

    # yum update [패키지명]
    

    패키지 정보 확인

    # yum info [패키지명]
    

    패키지 검색

    # yum search [검색어]
    

    패키지 목록 보기

    # yum list
    

    설치된 패키지 목록 보기

    # yum list installed
    

    패키지 설치, 변경, 삭제로 변경된 정보 보기

    # yum history list
    

    저장소 확인하기

    # yum repolist
    
  • [JavaScript] 배수 구하기

    [JavaScript] 배수 구하기

    0부터 100까지 특정 배수를 구하는법이다. 반복문과 조건문, 나머지 연산자가 사용된다.

    for (var i = 0; i <= 100; i++) {
        console.log(i);
    }
    

    i가 증감되면서 콘솔창에 1부터 100까지 차례로 출력될 것이다. 그렇다면 아래의 코드는 어떻게 출력될까?

    for (var i = 0; i <= 100; i++) {
        console.log(i % 3);
    }
    
    0  
    1  
    2  
    0  
    1  
    2  
    ...
    

    0부터 2는 3으로 나눠지지 않기때문에 첫번째 피연산자가 출력되고 3은 나눠지기 때문에 0이 출력된다. 그 이후로는 3으로 나눈 뒤 나머지가 출력되기 때문에 위처럼 0, 1, 2, 0, 1, 2 .. 로 출력된다. i값이 3으로 나머지 없이 나눠지면 0이 출력되는걸 확인할 수 있다. 이걸 통해 조건문으로 3의 배수를 구할 수 있는 것이다.

    for (var i = 0; i <= 100; i++) {
        if (i % 3 === 0) {
            console.log(i);
        }
    }
    

    나머지 없이 3으로 나눠 질때만 i값이 출력된다. 하지만 i값이 0일 때도 같이 출력된다. 다중 조건문을 추가해 주자

    for (var i = 0; i <= 100; i++) {
        if (i % 3 == 0 && i !== 0) {
            console.log(i);
        }
    }
    

    3의 배수가 출력되는걸 확인할 수 있다.

  • [UI] 탭메뉴(tab menu)

    [UI] 탭메뉴(tab menu)

    설명

    vanillaJS로 제작된 기본 탭 메뉴

    HTML

    <div id="tab">
        <div class="btn">
            <button type="button">01</button>
            <button type="button">02</button>
            <button type="button">03</button>
            <button type="button">04</button>
        </div>
        <div class="cnt">
            <div>content01 content01 content01 content01 content01 content01 content01 content01 content01 content01</div>
            <div>content02 content02 content02 content02 content02 content02 content02 content02 content02 content02</div>
            <div>content03 content03 content03 content03 content03 content03 content03 content03 content03 content03</div>
            <div>content04 content04 content04 content04 content04 content04 content04 content04 content04 content04</div>
        </div>
    </div>
    

    CSS

    #tab {
        border: 1px solid #ccc;
        width: 300px;
    }
    
    #tab .btn:after {
        content: '';
        display: block;
        clear: both;
    }
    
    #tab .btn button {
        float: left;
        border: 0;
        width: 25%;
        height: 30px;
        cursor: pointer;
        outline: none;
        background-color: #ccc;
    }
    
    #tab .btn button:hover {
        background-color: #fff;
    }
    
    #tab .btn button.on {
        background-color: #fff;
    }
    
    #tab .cnt div {
        display: none;
    }
    
    #tab .cnt div.on {
        display: block;
    }
    

    JavaScript

    window.addEventListener('load', function(){
    
        var tab = document.getElementById('tab'),
            btn = tab.getElementsByClassName('btn')[0],
            cnt = tab.getElementsByClassName('cnt')[0],
            index = 0;
    
        btn.children[0].classList.add('on');
        cnt.children[0].classList.add('on');
    
        for(var i = 0;i < btn.children.length;i++){
            (function(target){
                btn.children[target].addEventListener('click', function(){
                    tabOn(target);
                });
            })(i);
        };
    
        function tabOn(target){
            for(var i = 0;i < btn.children.length;i++){
                btn.children[i].classList.remove('on');
                cnt.children[i].classList.remove('on');
            };
            btn.children[target].classList.add('on');
            cnt.children[target].classList.add('on');
        }
    
    });
    
  • [Linux] CentOS7 Apache 설치

    [Linux] CentOS7 Apache 설치

    yum을 이용하여 apache를 설치한다.

    # yum -y install httpd
    

    apache 버전을 확인하여 설치가 제대로 되었는지 확인해 본다.

    # httpd -v
    
    Server version: Apache/2.4.6 (CentOS)
    Server built:   Aug  8 2019 11:41:18
    

    apache 실행, CentOS7부터 기존에 사용하단 service 명령이 실행되지 않을 수 있다. systemctl 명령어를 사용해준다.

    # systemctl start httpd
    

    부팅될 때 마다 apache를 실행

    # chkconfig httpd on
    

    자신의 공인아이피로 들어가보면 apache 서버가 실행되어 있는 확인해 볼 수있다. 자신의 공인아이피를 확인하고 싶다면 curl를 확인해 볼 수 있다.

    # curl bot.whatismyipaddress.com
    # curl http://ipecho.net/plain
    # curl icanhazip.com
    # curl ipv4.icanhazip.com
    # curl ipv4.ipogre.com

    이제 구동된 서버에 간단한 html 문서를 띄워보자. 아래 경로로 들어간다.(설치된 Apache 버전마다 경로가 다를 수 있다.)

    /var/www/html/

    해당 디렉토리로 가서 index.html 파일을 만들어준다.

    # touch index.html
    

    vi 편집기를 실행하여 “hello world!”를 입력하고 ESC를 누른뒤 아래 명령어를 입력하여 저장하고 나온다.

    # wq
    

    공인아이피로 들어가 보면 “hello world!”가 제대로 출력되는 것을 확인할 수 있다.

    apache를 재시작할일이 드물기 때문에 종종 명령어를 잃어버린다. 아래 명령어를 참고하도록 한다.

    Apache 버전 확인

    # httpd -v
    

    Apache 상태 확인

    # systemctl status httpd
    # service httpd status
    

    Apache 시작

    # systemctl start httpd
    # service httpd start
    # apachectl start
    

    Apache 중지

    # systemctl stop httpd
    # service httpd stop
    # apachectl stop
    

    Apache 재시작

    # systemctl restart httpd
    # service httpd restart
    # apachectl restart
    

    References

    CentOS 아파치 설치
    CentOS에서 apache 설치
    CentOS 아파치 상태/재시작/시작/중지 명령어
    리눅스 공인 IP 확인

  • [JavaScript] 인덱스 증가 감소

    [JavaScript] 인덱스 증가 감소

    슬라이드에서 이전 슬라이드, 다음 슬라이드 인덱스 값이 필요할 경우 쓰이는 방법이다. 슬라이드 갯수와 다음, 이전 인덱스 값을 초기 설정해준다.

    var slideLength = 4,
        next = 0,
        prev = 0;
    

    알고리즘이 들어갈 함수와 이벤트를 실행시킬 이벤트 리스너가 필요할 것이다. slide 함수를 선언하고 setInterval 함수에다가 이벤트 리스너를 등록하자

    function slide() {
        console.log(0);
    }
    setInterval(slide, 1000);
    

    1초마다 콘솔창에 0이 출력된다. 이제 1초마다 다음 인덱스에 1을 더하며 그 값을 이전 인덱스에 주자

    function slide() {
        next++;
        console.log(next, prev);
        prev = next;
    }
    setInterval(slide, 1000);
    

    콘솔창이 들어가있는 곳이 추후에 인덱스 다음과 이전 인덱스 값을 받아 처리하는 기능이 들어간다. 콘솔창을 보면 아래와 같이 다음과 이전이 1씩 밀리면서 출력된다.

    1 0  
    2 1  
    3 2  
    4 3  
    5 4  
    6 5  
    7 6  
    ...
    

    하지만 슬라이드 갯수는 4개다. 다음 인덱스가 4이상이 되면 0으로 초기화되도록 조건문을 입력하면 된다.

    function slide() {
        next++;
        if (next >= slideLength) {
            next = 0;
        };
        console.log(next, prev);
        prev = next;
    }
    setInterval(slide, 1000);
    

    아래와 같이 순차적으로 1씩 밀려서 출력되며, 4이상이 되면 0으로 초기화가 된다.

    1 0  
    2 1  
    3 2  
    0 3  
    1 0  
    2 1  
    3 2  
    ...
    

    이제 인덱스가 순차적으로 감소되는 코드를 작성해 보자

    function slide() {
        next--;
        if (next < 0) {
            next = slideLength - 1;
        }
        console.log(next, prev);
        prev = next;
    }
    setInterval(slide, 1000);
    

    다음 인덱스를 1식 빼고, 다음 인덱스가 0보다 작이질 시 슬라이드 갯수의 1을 뺀 값을 대입하면 된다. 1을 빼는 이유는 프래그래밍에서 수의 시작은 0부터 시작하기 때문이다. 네번째 슬라이드의 인덱스는 3이 될 것이다.

  • [UI] 슬라이드 배너(Slide Banner)

    [UI] 슬라이드 배너(Slide Banner)

    설명

    vanillaJS로 만들어본 기본 슬라이드 배너

    조건

    • 라이브러리 없이 자바스크립트만 활용
    • 다음, 이전 버튼으로 슬라이드 조작 가능
    • 인디게이터는 슬라이드 갯수에 맞게 자동으로 생성
    • 자동재생, 일시정지 버튼으로 웹 접근성 준수

    HTML

    <div id="slide">
      <ul class="cnt">
        <li>1</li>
        <li>2</li>
        <li>3</li>
        <li>4</li>
      </ul>
      <div class="btn">
        <button type="button" class="prev">prev</button>
        <button type="button" class="next">next</button>
      </div>
      <div class="auto">
        <button type="button" class="stop">stop</button>
        <button type="button" class="play">play</button>
      </div>
    </div>
    

    CSS

    #slide {
      position: relative;
      overflow: hidden;
      width: 300px;
      height: 300px;
    }
    
    #slide .cnt > li {
      position: absolute;
      top: 0;
      left: 300px;
      width: 300px;
      height: 300px;
      text-align: center;
      font-size: 30px;
      line-height: 300px;
      color: #fff;
    }
    
    #slide .cnt > li:nth-child(1) {
      background-color: red;
    }
    
    #slide .cnt > li:nth-child(2) {
      background-color: orange;
    }
    
    #slide .cnt > li:nth-child(3) {
      background-color: green;
    }
    
    #slide .cnt > li:nth-child(4) {
      background-color: blue;
    }
    
    #slide .btn > button {
      position: absolute;
      top: 50%;
      transform: translateY(-50%);
      border: 0;
      padding: 5px;
      background-color: #fff;
    }
    
    #slide .btn .prev {
      left: 5px;
    }
    
    #slide .btn .next {
      right: 5px;
    }
    
    #slide .auto > button {
      display: none;
      position: absolute;
      bottom: 5px;
      right: 5px;
      border: 0;
      padding: 5px;
      background-color: #fff;
    }
    
    #slide .indi {
      position: absolute;
      bottom: 10px;
      left: 50%;
      transform: translateX(-50%);
    }
    
    #slide .indi:after {
      content: "";
      display: block;
      clear: both;
    }
    
    #slide .indi > li {
      float: left;
      margin-left: 5px;
      border-radius: 50%;
      width: 12px;
      height: 12px;
      cursor: pointer;
      opacity: 0.5;
      background-color: #fff;
    }
    
    #slide .indi > li.on {
      opacity: 1;
    }
    
    #slide .indi > li:first-child {
      margin-left: 0;
    }
    

    JavaScript

    window.addEventListener("load", function () {
      var MOVEING_PX = 4,
        AUTO_TIME = 2000,
        slide = document.getElementById("slide"),
        indi = document.createElement("ul"),
        slideCnt = slide.getElementsByClassName("cnt"),
        slideCntItem = slideCnt[0].getElementsByTagName("li"),
        prevBtn = slide.getElementsByClassName("prev"),
        nextBtn = slide.getElementsByClassName("next"),
        playBtn = slide.getElementsByClassName("play"),
        stopBtn = slide.getElementsByClassName("stop"),
        playSet = null,
        before = 0,
        after = 0,
        moveIng = false;
    
      // init
      slideCntItem[0].style.left = 0;
      playBtn[0].style.display = "block";
      var indi = document.createElement("ul");
      for (var i = 0; i < slideCntItem.length; i++) {
        indi.innerHTML += "<li></li>";
      }
      indi.classList.add("indi");
      indi.children[0].classList.add("on");
      slide.append(indi);
    
      for (var j = 0; j < indi.children.length; j++) {
        indiClick(j);
      }
    
      // initEvnet
      nextBtn[0].addEventListener("click", function (e) {
        if (!moveIng) {
          after++;
          if (after >= slideCntItem.length) {
            after = 0;
          }
          move(after, before, "next");
          before = after;
        }
      });
    
      prevBtn[0].addEventListener("click", function (e) {
        if (!moveIng) {
          after--;
          if (after < 0) {
            after = slideCntItem.length - 1;
          }
          move(after, before);
          before = after;
        }
      });
    
      playBtn[0].addEventListener("click", function () {
        playBtn[0].style.display = "none";
        stopBtn[0].style.display = "block";
        playSet = setInterval(function () {
          if (!moveIng) {
            after++;
            if (after >= slideCntItem.length) {
              after = 0;
            }
            move(after, before, "next");
            before = after;
          }
        }, AUTO_TIME);
      });
    
      stopBtn[0].addEventListener("click", function () {
        playBtn[0].style.display = "block";
        stopBtn[0].style.display = "none";
        clearInterval(playSet);
      });
    
      function indiClick(target) {
        indi.children[target].addEventListener("click", function () {
          if (!moveIng) {
            after = target;
            if (after > before) {
              move(after, before, "next");
            } else if (after < before) {
              move(after, before);
            }
            before = after;
          }
        });
      }
    
      function move(after, before, type) {
        var nextX = type === "next" ? slide.offsetWidth : slide.offsetWidth * -1,
          prevX = 0,
          set = null;
        set = setInterval(function () {
          moveIng = true;
          if (type === "next") {
            nextX -= MOVEING_PX;
            slideCntItem[after].style.left = nextX + "px";
            if (nextX <= 0) {
              clearInterval(set);
              nextX = slide.offsetWidth;
              moveIng = false;
            }
            prevX -= MOVEING_PX;
          } else {
            nextX += MOVEING_PX;
            slideCntItem[after].style.left = nextX + "px";
            if (nextX >= 0) {
              clearInterval(set);
              nextX = slide.offsetWidth * -1;
              moveIng = false;
            }
            prevX += MOVEING_PX;
          }
          slideCntItem[before].style.left = prevX + "px";
        });
        indi.children[before].classList.remove("on");
        indi.children[after].classList.add("on");
      }
    });
    
  • [Git] 깃 커밋 메시지 컨벤션(Git Commit Message Convention)

    [Git] 깃 커밋 메시지 컨벤션(Git Commit Message Convention)

    정의

    커밋 메시지는 타입, 제목, 본문(선택), 꼬리말(선택) 세 부분으로 작성한다.

    • [타입(Type)] 제목(Title)
    • 본문(Body)
    • 꼬리말(Footer)

    제목

    • 커밋 메세지 제목의 맨 앞에 타입(Type)을 붙여준다. 각 타입의 종류는 아래와 같다.
      • 기능(feat): 새로운 기능을 추가
      • 버그(fix): 버그 수정
      • 리팩토링(refactor): 코드 리팩토링
      • 형식(style): 코드 형식, 정렬, 주석 등의 변경(동작에 영향을 주는 코드 변경 없음)
      • 테스트(test): 테스트 추가, 테스트 리팩토링(제품 코드 수정 없음, 테스트 코드에 관련된 모든 변경에 해당)
      • 문서(docs): 문서 수정(제품 코드 수정 없음)
      • 기타(chore): 빌드 업무 수정, 패키지 매니저 설정 등 위에 해당되지 않는 모든 변경(제품 코드 수정 없음)
    • 총 글자 수는 50자 이내며 마지막에 마침표(.)를 붙이지 않는다.
    • 커밋 유형들이 복합적인 경우 최대한 분리하여 커밋한다.

    본문

    • 본문은 한 줄당 72자 이하로 작성한다.
    • 깃은 자동 줄바꿈을 지원하지 않으므로, 직접 줄바꿈을 해야 한다.
    • 내용은 어떻게 변경하였는지 보다 무엇을, 왜 변경하였는지 설명한다.

    꼬리말

    • 바닥 글은 선택 사항이며 이슈 트래커 ID를 참조하는데 사용된다.

    References

    Git 사용 규칙 – Git commit 메시지
    Udacity Git Commit Message Style Guide
    깃허브(GitHub)로 취업하기
    How to Write a Git Commit Message
    좋은 git 커밋 메시지를 작성하기 위한 7가지 약속

  • [JavaScript] 재귀 함수(Recursive Function)

    [JavaScript] 재귀 함수(Recursive Function)

    정의

    재귀(Recursive)를 정의한다면 한 함수가 자기 자신을 호출하는 순간이다. 재귀함수를 이해하기 전에는 팩토리얼 이라는 개념이 필요하다.

    팩토리얼

    팩토리얼이란 자기 자신의 수에 1 작은 수를 곱하고 또 1 작은 수를 곱하고 해서 1 작은 수가 1이 될때까지 곱하는 것이다. 팩토리얼의 기호는 !이며 아래의 예제는 5!의 팩토리얼을 나타내고 있다.

    5 * 4 * 3 * 2 * 1 = 120
    

    재귀 함수

    이제 팩토리얼의 개념을 알았으니 재귀 함수가 무엇인지 확인해 본다.

    function factorial(x) {
      if (x < 0) return;
      if (x === 0) return 1;
      return x * factorial(x - 1);
    }
    console.log(factorial(3)); // 6
    

    결과 값이 나오는 과정을 순서대로 살펴보겠다.

    1. factorial 함수에 인자값으로 3을 담아 함수를 실행한다.
    2. 파라미터로 3을 받아 처음 조건문을 거친다. 30보다 크기 때문에 다음 조건문으로 이동한다. 30이 아니기 때문에 아래 구문으로 넘어간다.
    3. 3factorial 함수에 2를 넣은 결과 값을 곱하라는 구문이다. 다시 factorial 함수에 인자로 2를 넣어 실행한다.
    4. 20보다 크고 0이 아니니 다시 아래 구문으로 내려간다. 2factorial 함수에 1를 넣은 결과 값을 곱하라는 구문이다. 다시 factorial 함수에 인자로 1를 넣어 실행한다.
    5. 1역시 0보다 크고 0이 아니니 다시 아래 구문으로 내려간다. 1factorial 함수에 0를 넣은 결과 값을 곱하라는 구문이다. 다시 factorial 함수에 인자로 0를 넣어 실행한다. 위와는 다르게 0은 두번째 조건문에서 걸려 1을 리턴한다. 결국은 3 * 2 * 1라는 값을 리턴하게 되는 것이다. 이렇게 함수가 자기 자신을 호출하는 순간을 재귀 라고 한다.

    References

    재귀 함수
    자바스크립트 개발자라면 알아야 할 33가지 개념 #23 자바스크립트 : 자바스크립트 재귀(Recursion) 이해하기