ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 유용한 JS 문법(2) - 모던 자바스크립트 | 김민준
    Javascript/ECMAScript 2015 (ES6) 2020. 6. 24. 23:43
    반응형

    [출처 : https://learnjs.vlpt.us]

    spread

    일단 spread 문법부터 알아봅시다. spread 라는 단어가 가지고 있는 의미는 펼치다, 퍼뜨리다 입니다. 이 문법을 사용하면, 객체 혹은 배열을 펼칠수있습니다.

    예를 들어서 다음과 같은 객체들이 있다고 가정해봅시다.

    const slime = {
      name: '슬라임'
    };
    
    const cuteSlime = {
      name: '슬라임',
      attribute: '귀여운'
    };
    
    const purpleCuteSlime = {
      name: '슬라임',
      attribute: '귀여운',
      color: '보라'
    };
    
    console.log(slime);           // {name: "슬라임"}
    console.log(cuteSlime);       // {name: "슬라임", attribute: "귀여운"}
    console.log(purpleCuteSlime); // {name: "슬라임", attribute: "귀여운", color: "보라"}

    이 코드에서는 먼저 slime 이라는 객체를 선언했습니다. 그리고 cuteSlime 이라는 객체를 만들었는데요, 기존에 선언한 slime 을 건들이지 않고 새로운 객체를 만들어서 slime 이 가지고 있는 값을 그대로 사용하였습니다.

     

    그 다음에는 purpleCuteSlime 이라는 객체를 만들었는데요, 이 객체는 cuteSlime 이 가지고 있는 속성을 그대로 사용하면서 추가적으로 color 가 추가되었습니다.

     

    위 코드에서의 핵심은, 기존의 것을 건들이지 않고, 새로운 객체를 만든다는 것 인데요, 이러한 상황에 사용 할 수 있는 유용한 문법이 spread 입니다.

     

    아까 코드는 spread 문법을 사용하면 다음과 같이 작성 할 수 있습니다.

    const slime = {
      name: '슬라임'
    };
    
    const cuteSlime = {
      ...slime,
      attribute: '귀여운'
    };
    
    const purpleCuteSlime = {
      ...cuteSlime,
      color: '보라'
    };
    
    console.log(slime);           // {name: "슬라임"}
    console.log(cuteSlime);       // {name: "슬라임", attribute: "귀여운"}
    console.log(purpleCuteSlime); // {name: "슬라임", attribute: "귀여운", color: "보라"}

    여기서 사용한 ... 문자가 바로 spread 연산자입니다.

     

    spread 연산자는 배열에서도 사용 할 수 있습니다.

    const animals = ['개', '고양이', '참새'];
    const anotherAnimals = [...animals, '비둘기'];
    console.log(animals);
    console.log(anotherAnimals);

    기존의 animals 는 건드리지 않으면서, 새로운 anotherAnimals 배열에 animals 가 가지고 있는 내용을 모두 집어넣고, '비둘기' 라는 항목을 추가적으로 넣었습니다.

     

    배열에서 spread 연산자를 여러번 사용 할 수도 있습니다.

    const numbers = [1, 2, 3, 4, 5];
    
    const spreadNumbers = [...numbers, 1000, ...numbers];
    console.log(spreadNumbers); // [1, 2, 3, 4, 5, 1000, 1, 2, 3, 4, 5]
    

    함수 인자와 spread

    이번에는, 다시 아까 배웠던 spread 로 돌아와서 한가지를 더 가르쳐드리겠습니다. 바로 함수의 인자와 spread 인데요, 만약 프로그래밍을 처음 배우신다면 파라미터와 인자가 좀 헷갈릴 수 있습니다.

     

    이에 대해서 간단하게 설명 드려볼게요.

    const myFunction(a) { // 여기서 a 는 파라미터
      console.log(a); // 여기서 a 는 인자
    }
    
    myFunction('hello world'); // 여기서 'hello world' 는 인자

    함수에서 값을 읽을때, 그 값들은 파라미터라고 부릅니다. 그리고 함수에서 값을 넣어줄 때, 그 값들은 인자라고 부릅니다.

     

    인자가 무엇인지 이해를 하셨다면 이제 함수인자와 spread 문법을 사용하는 것에 대하여 알아볼게요.

     

    우리가 방금 함수파라미터와 rest 를 사용한 것과 비슷한데, 반대의 역할입니다. 예를 들어서, 우리가 배열 안에 있는 원소들을 모두 파라미터로 넣어주고 싶다고 가정해보겠습니다.

    function sum(...rest) {
      return rest.reduce((acc, current) => acc + current, 0);
    }
    
    const numbers = [1, 2, 3, 4, 5, 6];
    const result = sum(...numbers);
    console.log(result);

    rest

    rest는 생김새는 spread 랑 비슷한데, 역할이 매우 다릅니다.

    rest는 객체, 배열, 그리고 함수의 파라미터에서 사용이 가능합니다.

     

    객체에서의 rest

    우선 객체에서의 예시를 알아볼까요?

    const purpleCuteSlime = {
      name: '슬라임',
      attribute: '귀여운',
      color: '보라'
    };
    
    const { color, ...rest } = purpleCuteSlime;
    
    console.log(color);  // "보라"
    console.log(rest);   // {name: "슬라임", attribute: "귀여운"}

    rest 안에 color 값을 제외한 값이 들어있습니다.

    rest 는 객체와 배열에서 사용 할 때는 이렇게 비구조화 할당 문법과 함께 사용됩니다. 주로 사용 할때는 위와 같이 rest 라는 키워드를 사용하게 되는데요, 추출한 값의 이름이 꼭 rest 일 필요는 없습니다.

     

    배열에서의 rest

    다음, 배열에서의 사용 예시를 알아봅시다.

    const numbers = [0, 1, 2, 3, 4, 5, 6];
    
    const [one, ...rest] = numbers;
    
    console.log(one);  // 0
    console.log(rest); // [1, 2, 3, 4, 5, 6]

    배열 비구조화 할당을 통하여 원하는 값을 밖으로 꺼내고, 나머지 값을 rest 안에 넣었습니다.

     

    반면 rest 문법을 먼저 선언하는 건 안됩니다.

    const numbers = [0, 1, 2, 3, 4, 5, 6];
    
    const [..rest, last] = numbers;

    함수 파라미터에서의 rest

    rest 를 함수파라미터에서 사용 할 수도 있습니다.

    예를 들어서 우리가 파라미터로 넣어준 모든 값들을 합해주는 함수를 만들어주고 싶다고 가정해봅시다.

    function sum(a, b, c, d, e, f, g) {
      return a + b + c + d + e + f + g;
    }
    
    const result = sum(1, 2, 3, 4, 5, 6);
    console.log(result) // NaN

    위에서의 sum 함수는 7개의 파라미터를 받아오는데, 아래에서 사용 할때에는 6개만 넣어줬습니다. 그러면, g 값이 undefined 가 되기 때문에 sum 에 더하는 과정에서 += undefined 를 하게 되면 결과는 NaN 이 되버립니다. 그렇기 때문에 함수에서 하나하나 유효한 값인지 확인을 해줬지요.

     

    함수의 파라미터가 몇개가 될 지 모르는 상황에서 rest 파라미터를 사용하면 매우 유용합니다.

    코드를 다음과 같이 수정해보세요.

    function sum(...rest) {
      return rest;
    }
    
    const result = sum(1, 2, 3, 4, 5, 6);
    console.log(result);    // [1, 2, 3, 4, 5, 6]

    result 가 가르키고 있는 것은 함수에서 받아온 파라미터들로 이루어진 배열입니다.

    우리가 이제 파라미터들이 들어가있는 배열을 받았으니, 그냥 모두 더해주면 되겠죠?

    function sum(...rest) {
      return rest.reduce((acc, current) => acc + current, 0);
    }
    
    const result = sum(1, 2, 3, 4, 5, 6);
    console.log(result); // 21

    Scope

    Scope 란, 우리가 변수 혹은 함수를 선언하게 될 때 해당 변수 또는 함수가 유효한 범위를 의미합니다. Scope 는 총 3가지 종류가있습니다.

     

    · Global (전역) Scope : 코드의 모든 범위에서 사용이 가능합니다.

    · Function (함수) Scope : 함수 안에서만 사용이 가능합니다.

    · lock (블록) Scope : if, for, switch 등 특정 블록(중괄호 : {}) 내부에서만 사용이 가능합니다.

     

    예시를 통한 Scope 이해

    const value = 'hello!';
    
    function myFunction() {
      // function scope
      console.log(value);  // "hello!"
    }
    
    function otherFunction() {
      // function scope
      const value = 'bye!';
      console.log(value);  // "bye!"
    }
    
    myFunction();
    otherFunction();
    
    // global scope
    console.log(value);  // "hello!"

    코드의 맨 윗줄에서 선언된 value 값은 Global Scope 로 선언된 값입니다. Global Scope 로 선언된 값은 어디서든지 사용이 가능합니다. myFunction 에서 바로 사용을 할 수 있었지요?

     

    otherFunction 에서는 함수 내부에서 value 값을 'bye!' 로 새로 선언을 해주었습니다. 이렇게 되면, value 라는 값은 Function Scope 로 지정이 되서 해당 값은 otherFunction 내부에서만 유효한 값이 됩니다. 이렇게 값을 설정한다고 해서 기존에 Global Scope 로 선언된 value 값이 바뀌지 않습니다.

     

    또 다른 예시를 확인해봅시다.

    const value = 'hello!';
    
    function myFunction() {
      // function scope
      const value = 'bye!';
      const anotherValue = 'world';
      function functionInside() {
        // function scope
        console.log(value);        // "bye!"
        console.log(anotherValue); // "world"
      }
      functionInside();
    }
    
    
    myFunction()
    // global scope
    console.log(value);        // "hello!"
    console.log(anotherValue); // ReferenceError: anotherValue is not defined

    myFunction 내부에 anotherValue 라는 새로운 값을 선언했고, functionInside 라는 함수도 선언을 했습니다. functionInside 함수에서는 myFunction 에서 선언한 value 값과 anotherValue 값을 조회 할 수 있습니다.

    반면, myFunction 밖에서는 anotherValue 를 조회 할 수 없습니다.

     

    이제, 또 다른 예시를 알아봅시다.

    const value = 'hello!';
    
    function myFunction() {
      const value = 'bye!';
      if (true) {
        // block scope
        const value = 'world';
        console.log(value);  // "world"
      }
      // function scope
      console.log(value);    // "bye!" 
    }
    
    myFunction();
    
    // global scope
    console.log(value);      // "hello!"

    const 로 선언한 값은 Block Scope 로 선언이 됩니다. 따라서, if 문 같은 블록 내에서 새로운 변수/상수를 선언하게 된다면, 해당 블록 내부에서만 사용이 가능하며, 블록 밖의 범위에서 똑같은 이름을 가진 값이 있다고 해도 영향을 주지 않습니다. let 또한 마찬가지 입니다.

     

    하지만 var 는 어떨까요?

    var value = 'hello!';
    
    function myFunction() {
      // function scope
      var value = 'bye!';
      console.log(value);  // "bye!"
      if (true) {
        // block scope
        var value = 'world';
        console.log(value);  // "world"
      }
      // function scope
      console.log(value);    // "world"
    }
    
    myFunction();
    
    // global scope
    console.log(value);      // "hello!"

    var 는 Function Scope 로 선언이 되므로, if 문 블록 내부에서 선언한 value 값이 블록 밖의 value 에도 영향을 미치게 됩니다.


    Hoisting 이해하기

    Hoisting 이란, 자바스크립트에서 아직 선언되지 않은 함수/변수를 "끌어올려서" 사용 할 수 있는 자바스크립트의 작동 방식을 의미합니다.

    다음 코드를 확인해보세요.

    myFunction();  // "hello world!"
    
    function myFunction() {
      console.log('hello world!');
    }

    위 코드에서는 myFunction 함수를 선언하기 전에, myFunction() 을 호출해주었습니다. 함수가 아직 선언되지 않았음에도 불구하고 코드는 정상적으로 작동하게 됩니다.

     

    이게 잘 작동하는 이유는, 자바스크립트 엔진이 위 코드를 해석하는 과정에서, 다음과 같이 받아들이게 되기 때문입니다.

    function myFunction() {
      console.log('hello world!');
    }
    
    myFunction();  // "hello world!"

    이러한 현상을, Hoisting 이라고 부릅니다.

     

    변수 또한 Hoisting 됩니다.

    예를 들어서, 다음과 같은 코드를 실행한다면,

    console.log(number);  // ReferenceError: number is not defined

    이런 오류가 발생합니다.

     

    그렇지만, 다음과 같은 코드는

    console.log(number);  // undefined
    var number = 2;

    undefined 가 출력됩니다.

    자바스크립트 엔진이 위 코드를 해석 할 때는 다음과 같이 받아들이게 됩니다.

    var number;
    console.log(number);  // undefined
    number = 2;

    반면, const 와 let 은 hosting은 되지만 변수 생성 과정이 달라 일시적인 사각지대(TDZ)가 생성되어 초기화 전에는 액세스할 수 없다는 ReferenceError를 표시합니다.

    function fn() {
        console.log(a);  // Uncaught ReferenceError: Cannot access 'a' before initialization
        let a = 2;
    }
    fn();

    Hoisting 은 자바스크립트 엔진이 갖고 있는 성질이며, Hoisting 을 일부러 할 필요는 없지만, 방지하는 것이 좋습니다. 왜냐하면 Hoisting 이 발생하는 코드는 이해하기 어렵기 때문에 유지보수도 힘들어지고 의도치 않는 결과물이 나타나기 쉽기 때문입니다.

     

    Hoisting 을 방지하기 위해서, 함수의 경우 꼭 선언 후에 호출을 하도록 주의를 하시고, var 대신 const, let 을 위주로 사용하세요. 추가적으로, 나중에 자바스크립트 개발을 본격적으로 하게 될 때에는 ESLint 라는것을 사용하여 Hoisting 이 발생하는 코드는 에디터상에서 쉽게 발견해낼 수 있습니다.

    반응형

    댓글

Luster Sun