ES6의 주요 기능과 활용

Vue나 React를 처음 사용할 때, 우리는 종종 ES6를 기술 스택에 추가하게 됩니다. 하지만 ES6에는 수많은 기능이 있는데, 모든 것을 다 마스터해야 할까요? 80/20 원칙에 따라, 일반적으로 사용되고 유용한 기능에 집중하면 빠르게 실력을 향상시킬 수 있습니다.

이제 ES6의 유용한 새로운 기능들에 대해 알아보겠습니다.

  1. 변수 선언: const와 let

ES6 이전에는 var 키워드로 변수를 선언했습니다. 선언 위치와 상관없이 항상 함수의 최상단(함수 내부가 아니라면 전역 스코프의 최상단)에서 선언된 것으로 간주되었습니다. 이것이 바로 함수의 변수 호이스팅입니다. 예를 들어:

  function aa() {
    if(bool) {
        var test = 'hello man'
    } else {
        console.log(test)
    }
  }

위 코드는 실제로 다음과 같이 동작합니다:

  function aa() {
    var test // 변수 호이스팅
    if(bool) {
        test = 'hello man'
    } else {
        // 여기서 test에 접근하면 undefined 값
        console.log(test)
    }
    // 여기서 test에 접근하면 undefined 값
  }

따라서 bool이 true인지 false인지는 중요하지 않습니다. 어떤 경우에든 test는 생성되어 선언됩니다.

이제 ES6의 주인공이 등장합니다: 일반적으로 let과 const를 사용하여 선언하며, let은 변수를, const는 상수를 나타냅니다. let과 const는 모두 블록 레벨 스코프를 가집니다. 블록 레벨 스코프를 어떻게 이해해야 할까요?

  • 함수 내부에서
  • 코드 블록 내부에서

간단히 말해 {} 중괄호 내부의 코드 블록이 let과 const의 스코프입니다.

다음 코드를 보세요:

  function aa() {
    if(bool) {
       let test = 'hello man'
    } else {
        // test는 여기서 접근할 수 없음
        console.log(test)
    }
  }

let의 스코프는 현재 코드 블록 내부에 있지만, 함수의 최상단으로 호이스팅되지는 않습니다.

이제 const에 대해 알아보겠습니다.

    const name = 'lux'
    name = 'joe' // 다시 할당하면 오류 발생

면접 질문 하나를 살펴보겠습니다.

    var funcs = []
    for (var i = 0; i < 10; i++) {
        funcs.push(function() { console.log(i) })
    }
    funcs.forEach(function(func) {
        func()
    })

이런 유형의 질문은 자주 볼 수 있으며, 많은 개발자들이 10이 10번 출력된다는 것을 알고 있습니다. 하지만 0부터 9까지 차례대로 출력하려면 어떻게 해야 할까요? 두 가지 해결 방법이 있습니다. 바로 코드로 살펴보겠습니다.

    // ES5에서는 클로저를 사용하여 이 문제를 해결할 수 있었습니다
    var funcs = []
    for (var i = 0; i < 10; i++) {
        funcs.push((function(value) {
            return function() {
                console.log(value)
            }
        }(i)))
    }
    // es6
    for (let i = 0; i < 10; i++) {
        funcs.push(function() {
            console.log(i)
        })
    }

동일한 결과를 얻을 수 있지만, ES6의 간결한 해결책이 더 매력적이지 않나요?

  1. 템플릿 문자열

ES6 템플릿 문자열은 개발자에게 큰 도움이 되는 기능으로, ES5에서 문자열 기능에 있던 문제점들을 해결해주었습니다.

첫 번째 용도는 기본적인 문자열 형식화입니다. 표현식을 문자열에 삽입하여 연결합니다. ${}를 사용하여 구분합니다.

    //es5 
    var name = 'lux'
    console.log('hello' + name)
    //es6
    const name = 'lux'
    console.log(`hello ${name}`) //hello lux
    

두 번째 용도는 ES5에서 역슬래시()를 사용하여 여러 줄 문자열을 만들거나 문자열을 한 줄씩 연결했습니다. ES6에서는 백틱(``)을 사용하여 간단하게 해결할 수 있습니다.

    // es5
    var msg = "Hi \
    man!
    "
    // es6
    const template = `<div>
        <span>hello world</span>
    </div>`

문자열 관련해서 ES6는 매우 유용한 많은 메서드를 제공합니다. 주로 사용되는 몇 가지를 소개하겠습니다.

    // 1.includes: 문자열 포함 여부를 확인하고 불리언 값으로 반환
    let str = 'hahay'
    console.log(str.includes('y')) // true
    // 2.repeat: 문자열을 n번 반복
    let s = 'he'
    console.log(s.repeat(3)) // 'hehehe'
    // 소수점이 입력된 경우, Math.floor(num)으로 처리

  1. 함수

함수 기본 매개변수

ES5에서 함수의 기본 매개변수를 어떻게 정의했을까요?

    function action(num) {
        num = num || 200
        // num이 전달되면 num은 전달된 값이 됨
        // 매개변수가 전달되지 않으면 num은 기본값 200이 됨
        return num
    }

하지만 주의 깊게 관찰하는 개발자들은 num이 0으로 전달되면 false가 되어 num = 200이 되는 것이 원래 의도와 다르다는 것을 발견할 것입니다.

ES6는 매개변수에 기본값을 제공합니다. 함수를 정의할 때 매개변수를 초기화하여, 매개변수가 전달되지 않을 경우 사용됩니다.

    function action(num = 200) {
        console.log(num)
    }
    action() //200
    action(300) //300

화살표 함수

ES6의 흥미로운 부분 중 하나는 함수의 간결한 작성 방식, 즉 화살표 함수입니다.

화살표 함수의 가장 직관적인 세 가지 특징은 다음과 같습니다.

  • function 키워드 없이 함수 생성
  • return 키워드 생략
  • 현재 컨텍스트의 this 키워드 상속
// 예:
    [1,2,3].map( x => x + 1 )
    
// 다음과 같음:
    [1,2,3].map((function(x){
        return x + 1
    }).bind(this))

작은 팁 하나.

함수에 하나의 매개변수만 있는 경우 괄호를 생략할 수 있습니다. 함수가 하나의 표현식만 반환하는 경우 중괄호를 생략할 수 있습니다. 예를 들어:

    var people = name => 'hello' + name
    // 매개변수 name에 괄호가 없음

참고로:

    var people = (name, age) => {
        const fullName = 'h' + name
        return fullName
    } 
    // () 또는 {}가 없으면 오류 발생

  1. 객체 기능 확장

객체 초기화 간소화

ES5에서는 객체를 키-값 쌍의 형식으로 작성했으며, 키-값 쌍이 중복될 수 있었습니다. 예를 들어:

    function people(name, age) {
        return {
            name: name,
            age: age
        };
    }

키-값 쌍이 중복되는 경우, ES6에서는 다음과 같이 간소화할 수 있습니다:

    function people(name, age) {
        return {
            name,
            age
        };
    }

ES6는 객체 리터럴 메서드에 값을 할당하는 구문도 개선했습니다. ES5에서 객체에 메서드를 추가하는 방법:

    const people = {
        name: 'lux',
        getName: function() {
            console.log(this.name)
        }
    }

ES6는 콜론과 function 키워드를 생략하여 이 구문을 더 간결하게 만들었습니다.

    const people = {
        name: 'lux',
        getName () {
            console.log(this.name)
        }
    }

ES6 객체는 Object.assign() 메서드를 제공하여 얕은 복사를 구현합니다. Object.assign()은 임의의 수의 소스 객체의 자체 열거 가능한 속성을 대상 객체로 복사한 후 대상 객체를 반환합니다. 첫 번째 매개변수는 대상 객체입니다. 실제 프로젝트에서 원본 객체를 변경하지 않기 위해 일반적으로 대상 객체로 {}를 전달합니다.

    const obj = Object.assign({}, objA, objB)

  1. 더 쉬운 데이터 접근 - 구조 분해

배열과 객체는 JS에서 가장 많이 사용되고 중요한 데이터 표현 형식입니다. 정보 추출을 단순화하기 위해 ES6에 **구조 분해(Destructuring)**가 추가되었습니다. 이는 데이터 구조를 더 작은 부분으로 분해하는 과정입니다.

ES5에서 객체의 정보를 추출하는 방식은 다음과 같습니다:

    const people = {
        name: 'lux',
        age: 20
    }
    const name = people.name
    const age = people.age
    console.log(name + ' --- ' + age)

매우 익숙하죠? ES6 이전에 우리는 이렇게 객체 정보를 하나씩 가져왔습니다. 이제 구조 분해를 사용하여 객체나 배열에서 데이터를 가져와 변수에 저장할 수 있습니다. 예를 들어:

    // 객체
    const people = {
        name: 'lux',
        age: 20
    }
    const { name, age } = people
    console.log(`${name} --- ${age}`)
    // 배열
    const color = ['red', 'blue']
    const [first, second] = color
    console.log(first) //'red'
    console.log(second) //'blue'

  1. 전개 연산자 (Spread Operator)

ES6의 또 다른 재미있는 기능은 전개 연산자(Spread Operator)로, 마침표 세 개(...)를 사용합니다. 다음으로 그 용도를 살펴보겠습니다.

객체나 배열 조합

    // 배열
    const color = ['red', 'yellow']
    const colorful = [...color, 'green', 'pink']
    console.log(colorful) //[red, yellow, green, pink]
    
    // 객체
    const alp = { fist: 'a', second: 'b'}
    const alphabets = { ...alp, third: 'c' }
    console.log(alphabets) //{ "fist": "a", "second": "b", "third": "c"
}

때로는 배열이나 객체의 처음 몇 항목을 제외한 나머지 항목을 얻고 싶을 때가 있습니다.

    // 배열
    const number = [1,2,3,4,5]
    const [first, ...rest] = number
    console.log(rest) //2,3,4,5
    // 객체
    const user = {
        username: 'lux',
        gender: 'female',
        age: 19,
        address: 'peking'
    }
    const { username, ...rest } = user
    console.log(rest) //{"address": "peking", "age": 19, "gender": "female"
}

객체의 경우, 새로운 객체를 조합하는 데에도 사용할 수 있습니다. (ES2017 stage-2 제안) 물론 중복된 속성명이 있는 경우, 오른쪽이 왼쪽을 덮어씁니다.

    const first = {
        a: 1,
        b: 2,
        c: 6,
    }
    const second = {
        c: 3,
        d: 4
    }
    const total = { ...first, ...second }
    console.log(total) // { a: 1, b: 2, c: 3, d: 4 }

  1. import와 export

import로 모듈을 가져오고, export로 모듈을 내보냅니다.

// 모두 가져오기
import people from './example'

// 특수한 경우: 전체 모듈을 단일 객체로 가져오기 허용
// 모듈의 모든 내보내기는 객체의 속성으로 존재
import * as example from "./example.js"
console.log(example.name)
console.log(example.age)
console.log(example.getName())

// 일부만 가져오기
import {name, age} from './example'

// 기본 내보내기, 하나만 가능
export default App

// 일부 내보내기
export class App extend Component {};

이전에 import할 때 중괄호의 차이가 무엇인지 물어보는 사람이 있었습니다. 다음은 제가 업무에서 정리한 내용입니다:

1. export default people으로 내보낼 때, import people으로 가져옵니다 (중괄호 없음)

2. 하나의 파일에는 export default가 하나만 존재할 수 있습니다. 여러 개의 export는 가능합니다.

3. export name으로 내보낼 때, import { name }으로 가져옵니다 (중괄호 포함)

4. 하나의 파일에 export default people과 여러 개의 export name 또는 export age가 있는 경우, import people, { name, age }로 가져옵니다.

5. 하나의 파일에 n개의 export로 많은 모듈을 내보낼 때, 하나씩 가져오는 대신 import * as example을 사용할 수 있습니다.

  1. Promise

promise 이전에는 콜백이나 중첩이 너무 많아 코드 가독성이 떨어지고, 결합도가 높으며, 확장성이 낮았습니다. Promise 메커니즘을 통해 평탄한 코드 구조로 코드 가독성을 크게 향상시키고, 동기 프로그래밍 방식으로 비동기 코드를 작성하여 선형적인 코드 로직을 유지함으로써 코드 결합성을 크게 낮추고 프로그램의 확장성을 높였습니다.

간단히 말해 동기 방식으로 비동기 코드를 작성하는 것입니다.

비동기 요청 보내기

    fetch('/api/todos')
      .then(res => res.json())
      .then(data => ({ data }))
      .catch(err => ({ err }));

최근 면접 질문 관련해서 흥미로운 글을 하나 봤습니다.

    setTimeout(function() {
      console.log(1)
    }, 0);
    new Promise(function executor(resolve) {
      console.log(2);
      for( var i=0 ; i<10000 ; i++ ) {
        i == 9999 && resolve();
      }
      console.log(3);
    }).then(function() {
      console.log(4);
    });
    console.log(5);

Excuse me? 이 프론트엔드 면접은 무슨 일을 하는 거죠?

물론 위 promise 지식은 빙산의 일각에 불과합니다. 더 많은 학습과 적용이 필요합니다.

  1. 제네레이터 (Generators)

제네레이터(generator)는 **반복자(iterator)**를 반환할 수 있는 함수입니다. 제네레이터 함수도 일종의 함수이며, 가장 직관적인 표현은 일반적인 function보다 별표(*) 하나가 더 붙는 것이고, 함수 내에서 yield 키워드를 사용할 수 있습니다. 재미있는 점은 함수가 각 yield 후에 일시 중지된다는 것입니다.

여기서 생활에 비유할 만한 예시가 있습니다. 은행에서 업무를 보려면 대기실의 기계에서 번호표를 뽑아야 합니다. 당신이 번호표를 뽑으면, 기계는 자동으로 다음 번호표를 내지 않습니다. 즉, 발권기가 "일시 중지"된 상태이며, 다음 사람이 다시 불러야만 계속해서 티켓을 냅니다.

좋습니다. 이제 반복자에 대해 이야기해 보겠습니다. 제네레이터를 호출하면 반복자 객체가 반환됩니다. 이 반복자 객체에는 제네레이터 함수를 다시 시작하고 다음 값을 얻는 데 도움이 되는 next 메서드가 있습니다. next 메서드는 값뿐만 아니라 done과 value라는 두 속성을 가진 객체를 반환합니다. value는 얻은 값이고, done은 제네레이터가 더 이상 값을 제공하지 않는지 여부를 나타냅니다. 방금 뽑은 번호표 예시로 돌아가면, 각 번호표가 value이고, 인쇄용지가 다 떨어졌는지 여부가 done입니다.

    // 제네레이터
    function *createIterator() {
        yield 1;
        yield 2;
        yield 3;
    }
    
    // 제네레이터는 일반 함수처럼 호출할 수 있지만, 반복자를 반환합니다
    let iterator = createIterator();
    
    console.log(iterator.next().value); // 1
    console.log(iterator.next().value); // 2
    console.log(iterator.next().value); // 3

그렇다면 제네레이터와 반복자는 어떤 용도가 있을까요?

제네레이터와 관련된 많은 흥미로운 점은 모두 비동기 프로그래밍과 직접적으로 관련이 있습니다. 비동기 호출은 우리에게 매우 어려운 일입니다. 함수는 비동기 호출이 완료될 때까지 기다리지 않습니다. 콜백 함수를 생각할 수도 있겠죠. (물론 Promise나 Async/await 같은 다른 방법도 있습니다).

제네레이터는 코드가 대기할 수 있도록 합니다. 중첩된 콜백 함수를 사용할 필요가 없습니다. 제네레이터를 사용하면 비동기 호출이 제네레이터 함수의 다음 코드 라인 실행 전에 완료되었음을 보장할 수 있습니다.

그렇다면 문제가 생깁니다. 우리가 계속 수동으로 next() 메서드를 호출할 수는 없습니다. 제네레이터를 호출하고 반복자를 시작하는 메서드가 필요합니다. 다음과 같이:

    function run(taskDef) { // taskDef는 제네레이터 함수

        // 다른 곳에서 사용할 수 있도록 반복자 생성
        let task = taskDef();

        // 작업 시작
        let result = task.next();
    
        // next() 호출을 계속 유지하기 위해 재귀 함수 사용
        function step() {
    
            // 더 할 일이 있는 경우
            if (!result.done) {
                result = task.next();
                step();
            }
        }
    
        // 처리 시작
        step();
    
    }

제네레이터와 반복자가 가장 흥미롭고 흥미로운 측면은 명확한 외관의 비동기 작업 코드를 생성할 수 있다는 것입니다. 콜백 함수를 곳곳에 사용할 필요 없이, yield를 사용하여 비동기 작업이 끝날 때까지 기다리는 것처럼 보이는 동기 코드를 만들 수 있습니다.

태그: JavaScript ES6 프로그래밍 웹개발

7월 22일 17:41에 게시됨