ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • ES6 기초와 함수형 자바스크립트 코드 예제
    Javascript 2017. 3. 19. 20:17



      ES 6 의 새로운 문법과 함수형 자바스크립트에 대한 코드 예제 

      함수형 자바스크립트를 작성하는 법에 대해서 일반적으로 쓰는 OOP 개념의 코드와 다른 점을 

      예제 코드로 적어보겠습니다. 

      *  본 글은 유튜브 https://youtu.be/HvMemAgOw6I 를 바탕으로 작성되었습니다. 






      > ES6 의 배열인자 

    
    
    const array = (...elements) => {
        return elements;
    };
    
    array(1,2,3); //  [1, 2, 3]
    
    const log = (...args) => {
        console.log(...args);
    }
    
    log('hello', 'Seoul');  // hello Seoul
    
    

     

     * const  :  es6 의 새로운 문법으로 const 로 선언된 값은 외부에서 직접적으로 변경할 수 없습니다.

     * arrow function :  () => {}  로 실행하며 () 의 값은 인자로 받고 , {} 은 함수의 실행구문을 넣습니다.

                              ex)  function( value )  {  return value }    ===   ( value ) => { return value }


     코드 설명

     1)  const 로 선언된 array 는 (...elements) 인자를 받아서 elements 를 리턴합니다.

      * ...elements 에서 앞에 붙은 ... 은 인자를 강제적으로 배열로 받는 다는 뜻 입니다. 

     2)  array(1, 2, 3)  으로  array 함수를 실행 했다면  결과값은 배열로 변경되어 [1, 2, 3] 이 됩니다. 

     3) 마찬가지로 const 로 선언된 log 는 ...args 를 인자로 받게됩니다. 

     4) log 함수를 실행하면 'hello' , 'Seoul' 이 배열로 변경되어 출력되는 것을 확인 할 수 있습니다. 





     > ES6 의 배열식으로 변수선언 

    
    const langs = ['JavaScript', 'Ruby', 'Haskell'];
    const [js, ...rest] = langs;
    
    js === 'JavaScript'
    rest[0] === 'Ruby';
    rest[1] === 'Haskell';
    
    
    
    const head = ([x]) => x;
    
    head([1,2,3]) === 1;
    


     이번엔 변수 선언을 배열처럼 하는 문법입니다. 

     1) const langs 는 일반적인 배열을 선언하였고 내부에는 'JavaScript', 'Ruby', 'Haskell' 을 배열로 선언하였습니다.

     2) const [js, ...rest] = langs;  에서 변수 역시 배열처럼 선언하여 js 변수에는 langs 의 첫번째 값인

        'JavaScript' 가 들어가고  ...rest 에는 앞서 배운듯이 나머지 값들이 배열형식으로 들어갑니다. 

     3) const head 에서는 함수의 인자로 배열내부의 x 값을 받아 리턴하게 되었습니다.

     4) head([1,2,3]) 을 실행하면 인자로 배열의 값을 하나만 받기 때문에 1 이 결과로 나오게 됩니다. 






     > ES6 함수의 인자로 기본값 선언 

    
    const greet = (name, greeting = 'Hi') => {
        console.log(greeting, name);
    }
    
    greet('Seoul', 'Hello');
    // Hello Seoul
    
    greet('Alex');
    // H1 Alex 
    


     이번엔 함수의 인자값에 기본값을 설정할 수 있는 문법입니다. 


     1) const greate 는 name, 과 greeting 를 인자로 받는 함수로서 , 결과값으로 

        greeting , name 순으로 log 를 출력합니다.

        여기서 greeting = 'HI' 를 사용해 두번 째 인자값이 존재하지 않으면 

        greeting 은 자동으로 'HI' 가 들어가게 됩니다. 


     2) greet('Seoul', 'Hello')  실행 결과와 greet('Alex') 실형결과를 보면 인자의 기본값이 적용된 것을 

        확인 할 수 있습니다. 




     > ES6 Object Merge 

    
    Object.assign(
        {},
        { hello: 'Alex'},
        { hi: 'Scenic City Summit'}
    );
    
    // {
    //   hello: 'Alex
    //   hi: 'Scenic City Summit
    // }
    


     이번엔 굉장히 유용한 Object Assign 문법입니다. 

     1) Object.assign 명령은 다수의 Object 를 하나로 묶어주는 역활을 합니다. 

     2) Object.assign (  대상오브젝트 , ... 추가,병합할 오브젝트)  형식으로 사용됩니다.

     3) 위 코드에서는  {} 빈 객체에 두개의 객체 를 병합하여  하나의 객체로 결과가 나오게 되었습니다. 

     * 주의 할 점은 Object.assign 은 대상오브젝트에 오브젝트를 병합할때 병합되는 오브젝트의 참조를 복사하게 됩니다. 

       병합되어진 객체의 값을 변경하면 병합당한 객체의 값도 같이 변하게 됩니다. 






    > ES6 Class 

    
    class Point  {           
        constructor(x, y) {
            this.x = x;
            this.y = y;
        }
    
        moveBy(dx,dy) {
            this.x += dx;
            this.y += dy;
        }
    }
    
    //
    
    function Point(x, y) {
        this.x = x;
        this.y = y;
    }
    
    Point.prototype.moveBy = 
       function(dx,dy) {
           this.x += dx;
           this.y += dy;
    }
    


     기존 언어에서 볼 수 있었던 class 가 도입되었습니다. 

     1)  class Point 는 생성자를 가지고 있고 moveBy 라는 함수 역시 포함하고 있습니다.

     2) 이를 기존 자바스크립토 변경해보면  Point 라는 함수와 Point 의 프로토타입으로 moveBy 를 설정 해 둔 것과 

        같은 것을 볼 수 있습니다. 







     >> 함수형 자바스크립트    

     

      PURE 함수를 작성합니다.

      같은 값을 넣으면 언제나 같은 결과가 나와야 합니다.


      간단한 예 

    
    const add = (x,y) => x + y;
    
    add(2, 3) === 5;
    


     여기서 add(2, 3) 을 실행하면 나오는 값은 5 라는 것을 누구나 예측할 수 있고 변하지 않습니다. 

     이것이 순수함수 입니다. 

     해당 함수의 결과를 예측 할 수 있고, 테스트 하기 쉽습니다. 


     그럼 잘못된 예를 보겠습니다. 

    
    let name = 'Jeremy';
    const getName = () => name;  
    const setName = (newName) => {
        name = newName;   
    }
    const printUpperName = () => {
        console.log(name.toUpperCase()); 
    }
    

     * let : let 으로 선언된 함수는 scope 가 해당 지역으로 한정됩니다. 

             const 와 다르게 값을 외부에서 변경할 수 있습니다. 


     위 코드의 잘못된 점이 보이나요?  

     얼핏보면 잘못된 점 없이 실행도 잘되고 결과도 잘 나옵니다. 맞습니다. 위 코드의 문제점은 일반적으로는 없습니다. 

     다만 함수형으로 생각한다면 변경점이 생기게 됩니다. 


     1) let 으로 name 변수를 선언하고 값은 'Jeremy' 를 주었습니다.

     2) const getName 은 인자없이 name 을 리턴합니다. 

     3) const setName 은 newName 을 인자로 받아서 name 변수의 값을 newName 으로 변경합니다. 

     4) const printUpperName 은 인자없이 현재 name 값을 대문자로 변경하여 출력합니다. 


     자 여기서 잘못된 점은 everything 입니다. 

     먼저 let name 의 선언은 해당값이 변할 수가 있다는 점이 생깁니다. 첫번째로 문제가 생겼구요. 

     getName 은 name 값을 리턴합니다. 엥? 이게 뭐가 잘못된거죠?  

     함수형으로 생각해봅시다. 

     getName 의 결과를 예상할 수 있나요?  응 'Jeremy' 야  라고 한다면 할 말이 없습니다. 

     만약 나중에 누군가가 나도 모르게 name 값을 변경한다면 ?  응 'Jeremy' 야 라고 할 수 없습니다. 

     어디서 변경되었는지, 뭘로 변경되었는지를 알아야 해당함수의 결과를 예측할 수 있습니다. 

      

     그 다음 setName 역시 당연히 잘못 되었다는 것이 보이나요? 

     해당 함수는 newName 을 인자로 받아서 name 값을 바꿔 버립니다. 

     setName 이 실행되고, getName 이 실행되면 나도 모르게 jeremy 는 사라지고 다른 결과값이 나오게 됩니다.

     setName 의 잘못은 외부의 값을 직접적으로 건드린 것 입니다. 


     마지막으로 printUpperName 의 결과값이 예상되나요? 

     실행하면 어디 있는지도 모르는 name 을 대문자로 변경하여 실행합니다. 

     


    
    const upperName = (name) => name.toUpperCase(); 

     

     printUpperName 을 재정의해서 upperName 으로 바꾸고 name 인자를 받아서 대문자로 변경합니다.

     이렇게 하면 사용자는 무엇이 대문자로 변경되서 나오는지 결과를 예측할 수 있고, 결과 값이 변하지 않습니다. 

     getName 과 setName 이요 ? 바꿀려면 억지로 바꿀수는 있지만 햔재로선 의미가 없습니다. 

     함수형의 중요한 점은 기존의 값을 건드리며 변경하며 계속 사용하는 것이 아닌 새로운 값이 필요하면 

     새로운 값을 만들어서 할당하게 됩니다. 


     

     

    >> 함수형자바스크립트  -  함수형 배열함수를 사용하자 

     

     이번엔 ES6 를 쓰는 분들이라면 자주 애용하는 Map 함수를 씁니다. 

     알다시피 Map 은 연산후에 리턴되는 값은 기존값의 변경이 아닌 새로운 배열을 리턴합니다. 

     

     그럼 예제를 봅시다. 

    
    function doubleNumbers(numbers) {
        const doubled = [];
        const l = numbers.lenth;
    
        for (let i = 0; i < l; i++) {
            doubled.push(numbers[i] * 2);
        }
    
        return doubled;
    }
    
    doubleNumbers([1,2,3]);
    // [2, 4, 6]
    


     doubleNumbers 함수의 역활이 보이시나요? 

     numbers 를 인자로 받아서 (아마 배열이겠지요) doubled 라는 새로운 배열을 만든 뒤 numbers 의 

     각 값들의 제곱을 구해서 doubled 에 삽입한 후 리턴합니다. 

     함수형 으로 작성되었습니다. 기존의 numbers 를 변경하지 않고 새로운 배열을 만들어 

     새로운 값을 담은 뒤 리턴 하니깐요. 

     다만 map 함수를 활용한다면 위 코드는 확 줄어들겠죠? 

    
    function doubleNumbersMap(numbers) {
        return numbers.map(n => n * 2);
    }
    
    doubleNumbersMap([1,2,3]);
    // [2, 4, 6]
    


    numbers 를 인자로 받아 map 함수를 활용하여 제곱을 한뒤 리턴 해 줍니다. 

    여기서는 doubled 라는 새로운 배열을 만들 필요도 없이 map 이 알아서 새로운 배열을 리턴하기 때문에 

    코드가 굉장히 간결해 졌습니다. 




    >> 함수형 자바스크립트  -  변수의 값을 변경시키지 말자 


    
    const hobbies = [
        'programming',
        'reading',
        'music'
    ];
    
    const firstTwo = hobbies.splice(0,2); 
    
    console.log(firstTwo);
    // ['programming' , 'reading']
    
    console.log(hobbies);
    // ['music']
    
    


    위 코드를 보면 어디가 잘못 되었을까요? 

    바로 firstTwo 부분이죠. 

    firstTwo 는 기존 hobbies 배열을 잘라서 삽입시켰습니다. 

    문제는 이때문에  hobbies 값이 변경되었습니다. 

    hobbies 는 순수성을 잃고 music 만 가지게 되었습니다. 이는 함수형에서 원하는 형식이 아닙니다. 

    기존의 값은 불변해야 합니다. 

    여기서 강제적으로 기존 값을 지켜주는 함수가 있습니다. 바로 Object.freeze 입니다. 


    
    const hobbies = Object.freeze([
        'programming',
        'reading',
        'music'
    ]);
    
    const firstTwo = hobbies.splice(0,2);
    // TypeError 
    


    hobbies 를 Object.freeze 를 사용하여 만들었습니다. 

    이제 hobbies 를 변경하려고 하면 TypeError 를 발생시키며 변경시킬 수 없도록 합니다.



    이와 관련된 또다른 예를 봅시다. 


    
    class PointTwo {
        constructor(x, y) {
            this.x = x; 
            this.y = y;
        }
    
        moveBy(dx, dy) {
            this.x += dx;
            this.y += dy;
        }
    }
    
    const PointTwo = new Point(0, 0);
    
    PointTwo.moveBy(5 ,5);
    PointTwo.moveBy(-2, 2);
    
    console.log([PointTwo.x, PointTwo.y]);
    // [3, 7]
    


    아까 보았던 Point 클래스 입니다. 

    PointTwo 클래스는 생성자와 moveBy 라는 함수를 가지고 있고 moveBy 는 dx, dy 를 인자로 받아서  

    x 와 y 값을 변형시킵니다. 

    지금까지 글을 봐왔다면 무엇이 잘못되었는지 바로 알 수 있습니다. 

    moveBy 함수가 직접적으로 x 값과 y 를 변경하기 때문에 const PointTwo 의 값은 moveBy 를 만날때 마다 

    값이 변하게 됩니다.


    그럼 함수형으로 바꿔서 봅시다. 

    
    const createPoint = (x, y) => Object.freeze([x, y]);
    
    const movePointBy = ([x,y], dx, dy) => {
        return Object.freeze([x + dx, y + dy]);
    };
    
    let point = createPoint(0, 0);
    
    point = movePointBy(point, 5, 5);
    point = movePointBy(point, -2, 2);
    
    console.log(point);
    // [3, 7]
    


    무엇이 바뀌었는지 보이나요? 

    1) 먼저 PointTwo 클래스를 없애고  const createPoint 변수가 PointTwo 의 생성자를 대신하게 되었습니다.

    2) Object.freeze 를 사용하여 생성하기 때문에 이제부터 createPoint 로 생성된 값은 변경되지 않습니다. 

    3) movePointBy 함수는 x,y 가 포함된 배열과 dx ,dy 를 인자로 받습니다. 여기서 이 함수의 목적은

       [x,y] 배열은 대상 배열 , dx ,dy 변수는 새롭게 할당 할 값이라는 것을 알 수 있습니다. 

    4) let point 변수는 createPoint 함수를 사용하여 0 , 0 배열을 만들었습니다. 

    5) 이제 point 변수에 movePointBy 함수를 두번 적용합니다.  대상은 자기자신인 point 와 변경할 값을 

       인자로 주었습니다. 

    6) 두번의 함수 적용 후 최종 값은 3, 7 이 나옵니다. 


    여기서 기존 코드랑 뭐가 다른거죠?  

    Object.freeze 를 썼는데 왜 point 는 변경이 되나요 ? 


    이는 movePointBy 함수를 살펴보면 알 수 있습니다. 

    movePointBy 함수는 대상으로 받은 Point 를 변경해서 돌려주는 것이 아닌 point 의 값과 dx, dy 의 값을 가지고 

    연산을 한 값을 새롭계 freeze 시켜서 돌려주게 됩니다. 

    한마디로 movePointBy 를 거치면 기존의 point 는 사라지고 새로운 값과

    freeze 가 적용된 새로운 point 객체를 받는 겁니다. 

    기존 값의 재활용이 아니라는 거죠. 

    사용자는 기존값과 변경될 값을 같이 보내서 결과를 예측할 수 있고, movePointBy 는 순수함수가 되는 것 입니다.






    >> 함수형 자바스크립트 - Closure 를 이용한 고차함수


    Closure 를 이용하여 고차함수를 활용할 수 있습니다. 

    예제를 봅시다. 

    
    const createAdder = (x) => {
        return (y) => x + y;
    }
    
    const add3 = createAdder(3);
    
    add3(2) === 5;
    add3(3) === 6;
    


    createAdder 함수는 x를 인자로 받아서  다시 (y)를 인자로 받아 x + y 를 리턴하는 함수를 리턴합니다. 

    add3 는 createAdder 에 3을 인자로 줬습니다. 이 상태에서 add3 은  (y) => 3 + y 형식을 가지는 함수입니다. 

    이제 add3(2) 를 호출하면 (2) => 3 + 2   즉 5가 나오게 됩니다. 

    child 함수가 parent 함수의 값을 참조할 수 있는 closure 의 특성때문에 나올 수 있는 함수입니다. 

    이제 createAdder 는 순수함수가 되었고 , 중복되는 인자는 미리 정해두고 값을 예측할 수 있게 되었습니다. 


    이를 간단하게 REST API 함수에 활용 해 보겠습니다.

    먼저 기본 코드를 봅시다.


    
    const request = (options) => {
        return fetch(options.url, options)
              .then(resp => resp.json());
    };
    
    const userPromise = request({
        url: '/users',
        headers: { 'X-Custom' : 'mykey'}
    });
    
    const tasksPromise = request({
        url: '/tasks',
        headers: { 'X-Custom' : 'mykey'}
    });
    


    여기서 request 함수는 options 를 받아서 다시 options.url 과 options 를 인자로 받아 

    fetch 시키는 함수를 리턴하고 있습니다. 

    userPromiose 와 tasksPromise 는 request 함수에 url 과 headers 과 포함된 배열을 인자로 보내 

    API 의 결과 값을 받고 있습니다. 

    여기서 headers 의 값이 중복됩니다. 

    이를 해결해 봅시다. 


    
    const request = (options) => {
        return fetch(options.url, options)
              .then(resp => resp.json());
    };
    
    const createRequest = (options) => {
        return (otherOptions) => {
            return request(Object.assign(
                {}, options, otherOptions
            ));
        };
    };
    
    const customRequest = createRequest({
        headers: { 'X-Custonm' : 'mykey' }
    });
    
    const userPromise = customRequest({ url: '/users' });
    const tasksPromise = customRequest({ url: '/tasks' });
    
    


    createRequest 라는 고차 함수를 만들었습니다.

    두개의 옵션을 받아서 합친 뒤에 request 함수를 호출합니다. 

    customRequest 는 아까 중복되었던 headers 를 createRequest 에 인자로 줍니다. 

    이로써 customRequest 는

     (otherOptions) =>  {  request ( Object.assign ( {} , { headers } , otherOptions  ) ) }; 

    의 형태가 되었습니다. 

    이제 userPromise 와 tasksPromise 는 customRequest 에게 url 객체를 넘겨주면 자동적으로 request 객체에 

    headers 와 url 을 넘겨주고 request 가 실행됩니다. 

    이제 같은 headers 를 사용하면 url 만으로 request 함수를 호출 하게 됩니다. 


    마지막으로 request 함수에 createRequest 함수를 합쳐봅시다. 

    
    const request = defaults => options => {
        options = Object.assign(
            {}, defaults, options
        );
    
        return fetch(options.url, options)
              .then(resp => resp.json());
    };
    




    >> 함수형 자바스크립트 -  함수를 연결하자 


    바로 예제를 봅시다.

    먼저 

    [
         { price : '5' } ,
         { price : '10'} , 
         { price : '3'}
    ];


    이런 형식의 데이터 가 있습니다. 


    
    const request = defaults => options => {
        options = Object.assign(
            {}, defaults, options
        );
    
        return fetch(options.url, options)
              .then(resp => resp.json());
    };
    
    
    const map = fn => array => array.map(fn);
    const multiply = x => y => x * y;
    const pluck = key => object => object[key];
    
    const discount = multiply(0.98);
    const tax = multiply(1.0925);
    
    const customRequest = request({
        headers: { 'X-Custom' : 'mykey'}
    });
    
    customRequest({ url: '/cart/items' })
       .then(map(pluck('price')))
       .then(map(discount))
       .then(map(tax));
    


    위 코드를 봅시다. 

    먼저 map 함수를 지정하였습니다. 

    map 은 함수를 인자로 받고 다시 배열을 인자로 받아 해당 배열에 인자로 받은 함수를 적용하여 리턴합니다. 

    multiply 는 x 와 y 를 인자로 받는 고차함수 입니다.

    pluck 는 key 와 객체를 받아 해당 객체의 key의 값을 표현합니다

    discount 는 0.98 을 곱하고  , tax 는 1.0925 를 곱합니다. 


    이제 customRequest 에는 request 를 부르고 

    customRequest 를 통해 url 을 전달하여 fetch 를 실행합니다. 

    이제 코드를 순서대로 실행 해 봅시다. 


    1) customRequest( { url : '/cart/items' }) 를 실행합니다. 해당 api 주소에서 리턴 받는 값은 

       위에서 설정 해 둔 값이라고 생각합시다.

    2) 해당 응답이 완료되면 먼저 map ( pluck('price')) 를 실행합니다. map 함수는 위에 적었듯이 함수와 배열을 

       인자로 받아서 해당 배열을 인자 함수로 실행한 뒤 리턴합니다. 

       pluck('price') 를 통해 해당 배열에서 price 값을 뽑아서 표현합니다.

       표현하면 

    [
        5,
        10,
        3
    ]

      

      이런 값이 됩니다. 


    3) 이제 map(discount) 를 통해 해당 값에 0.98 을 곱하고 

    [
        4.9,
        9.8,
        2.94
    ]

      

    4) 마지막으로 map(tax) 를 통해 1.0925를 곱합니다. 

    [
        5.35,
        10.71,
        3.21
    ]


    별 거 없죠? 

    이런 방식은 아마 기존에 많이 사용하고 있으리라 생각합니다. 




    >> 함수형자바스크립트  -  재귀함수를 이용하자 


    함수형에서는 기존값을 지속적으로 변화시키는 for , while 등을 사용하지 않습니다. 

    대신 새로운 값을 가지고 다시 연산하게 됩니다. 

    예를 보면 이해가 쉽습니다. 

    일단 팩토리얼 함수를 사용한다고 생각해봅시다. 

    4의 팩토리얼 즉  4! = 4 x 3 x 2 x 1  = 24 를 함수로 만들어 봅시다. 

    먼저 재귀함수 없이 기본함수로 만들면 

    
    const factorial = (n) => {
        let result = 1;
    
        while (n > 1) {
            result *= n;
            n--;
        }
    
        return result;
    }


    이런 식으로 n 을 받아서 while 문을 돌려서 결과를 생성한 뒤 리턴합니다. 

    이제 이놈을 재귀함수로 변경 해 봅시다. 

    
    const factorial = (n) => {
        if (n < 2) {
            return 1;
        }
    
        return n * factorial(n - 1); 
    };
    


    코드가 짧아지고 인자로 받은 n 에 재귀함수를 사용해서 계속 값을 더하고 있습니다. 

    자 여기서 문제가 무엇일까요? 

    먼저 함수를 실행했을 때 Javascript 내부에서는 콜 스택이 어떻게 쌓이는지 봅시다. 


    factorial(4);
    4 * factorial(3);
    4 * 3 * factorial(2);
    4 * 3 * 2 * factorial(1);
    4 * 3 * 2 * 1;
    4 * 3 * 2;
    4 * 6;
    24;


    이런식으로 factorial 함수는 n * factorial 이 계속 쌓이게 됩니다. 

    여기서 먼저 재귀함수에서 중요한 2가지를 알아봅시다. 

    1 . 재귀함수를 활용하는 부분을 찾습니다. 

      여기서는 (n * n - 1 , n * n - 2, n * n -3 ....) 이 됩니다. 

    2. 해당 함수를 중지시키는 부분을 찾습니다. 

      여기서는 (n < 2) return 1 이 되겠네요. 


    이제 이 함수의 문제점을 알아봅시다. 

    현재 return n * factorial(n-1) 에서 가장 먼저 실행되는 부분은 어디입니까? 

    바로 ( n - 1 ) 부분 입니다.  함수를 실행하려면 인자값을 먼저 알아야 하니깐요. 

    그뒤에 실행되는 부분은 어디일까요? 당연히 해당 인자를 감싸고 있는 factorial 이 됩니다. 

    그리고 마지막으로 n * factorial (n-1) 이 실행됩니다. 

    여기서 Javascript 내부에서는 factorial ( n -1 ) 을 콜 스택에 쌓아서 다 실행하고 난 뒤에 

    factorial 함수가 더 이상 쌓이지 않으면 마지막으로 n * factorial( n - 1) 을 차차 실행하게 됩니다. 

    왜냐면 factorial 이 완료되지 않으면 factorial 에 의존성을 가지는 n * factorial ( n - 1 ) 공식을 실행 할 수가 없기 때문이죠.

    생각해보세요. factorial ( n - 1) 의 값을 모르는데 어떻게 n * factorial ( n - 1 ) 결과를 냅니까?  

    그러므로 위 함수는 잘못되었습니다. 

    결과가 잘 나오는데요 ?  하시는 분은 그럼 factorial(100,000) 을 실행 해보십시오. 

    call stack 을 초과했다고 나오게 됩니다. 

    당연합니다 . Javascript 의 Maximum call stack size 는 Chrome 이 약 1MB  입니다.


    그럼 call stack 하나의 용량이 약 48B 이면 

    100,000 x 48 / 1024 / 1024 = 4.58 MB > 1MB   값이 나오니 당연히 

    maximum call stack size error 가 나오게 됩니다. 

    그럼 어떡하냐 어떤식으로 바꾸느냐 

    이는 꼬리물기 재귀함수로 변경하면 간단히 해결됩니다. 


    
    const factorial = (n, accum = 1) => {
        if (n < 2) {
            return accum;
        }
    
        return factorial(n - 1, n * accum);
    };
    


    변경된 함수입니다. 

    뭐가 변경되었는지 보이나요 ? 

    먼저 인자가 하나 늘어나게 되었습니다. accm 은 해당 재귀함수를 제어하는 값 입니다. 

    default 값으로 1이 들어가 있고 n 의 값에 따라 점점 줄어들게 됩니다. 

    그리고 최종적인 결과값이 들어가는 곳이기도 합니다. 


    위 함수에서 factorial (4) 를 실행하면? 

    처음 실행시 factorial (4 , 1)  이 되고  if 문을 갔지만 n 은 4이기에 패스. 

    그리고 return 하게 되는데 여기서 함수자체를 리턴하도록 변경되었습니다. 

    이렇게 되면 factorial 에 의존성을 가지는 공식이 없고 새로운 값을 가지고 다시 함수를 실행하기 때문에 

    깔끔하게 call stack 이 리셋되고 해당함수를 실행하게 됩니다. 

    결국 함수의 실행은 call stack 에 쌓아두지 않고 

    factorial ( 4 , 1) 

    factorial ( 3 , 4) 

    factorial ( 2 , 12) 

    factorial ( 1 , 24)

    이 됩니다. 

    이제 해당 함수로 factorial (1,000,000) 을 실행하게 되면 maximum call stack error 가 아닌 

    infinity 가 결과로 나옵니다. 

    값이 표현하기 힘들정도로 높다고 나오는거죠.  연산이 제대로 실행 된 겁니다. 

    참 쉽죠? 


    이로써 간단하게 함수형자바스크립트 예제와 ES6 의 새로운 문법을 살펴 보았습니다. 

    OOP 에서 함수형으로 변하는 건 쉽지 않다고들 하는데 , 기본적으로 순수함수의 법칙과 값의 변경이 아닌 

    새로운 값을 만들어 대입시킨 다는 생각으로 차근차근 하다보면 익숙해 지리라 생각합니다. 

    고차함수와 for, while 을 대신하는 재귀함수의 활용도 잊지 말구요. 



    잘못된 점이나 잘못된 지식은 언제든지 말해 주세요 

    추가로 오랜만에 글 쓰다 알아낸 기능이 있습니다. 

    vscode 를 사용중에 코드를 그대로 복사 붙여넣기 하면  

    코드가 그대로 붙는군요 . 우오  


    const value = factorial(100000);
    console.log(value);


    점점 글쓰기 편해져서 좋군요 ㅠ 




    댓글