ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Javascript] ES6 이후 - (1) ES2016, ES2017, ES2018
    Front-End(Web)/Javascript 2022. 2. 8. 19:37
    반응형

    이전 포스팅에서, 최근 Javascript의 문법이 격변했던 ES6(ES2015)에 대해 정리한 적이 있다.

    (포스팅 링크 : https://abangpa1ace.tistory.com/146?category=910462)

     

    [Javascript] ES6(ES2015)

    🧐 서론 드디어, 내가 이전부터 포스팅하고 싶었던 Javascript의 ES6 문법이다! 나는 2020년에 Javascript를 처음 공부했고, 오히려 ES6 문법을 당연하다는 듯이 사용하게 된 뉴비 중 한명이다. 😚😚😚

    abangpa1ace.tistory.com

     

    이 당시에, Javascript 문법에 많은 수정과 추가가 진행되었으며, 이는 컴파일링이 필요할 정도로 호환성 문제가 계속해서 대두되었다. (통상 IE)

     

    이 포스팅은, 2015년 ES6 버전업 이후 매년 진행되었던 Javascript의 버전업에 대해서, 그리고 각 버전에 지원된 문법들을 간단히 정리해보는 포스팅이다.


    📒 ES6(ES2015) 그 이후?

     

    ES는 ECMA라는 정보통신 기술 표준을 제정하는 단체에서 지정한 Script 프로그래밍 언어라는 뜻이며, 이것의 6버전이 2015년에 업데이트된 것이다.

     

    하지만, Javascript가 초기에 급하게 설계된 언어인만큼 보완되어야 할 부분이 많았으며, ES6 이후로는 매년 업데이트를 진행하고 있다.

    (그래서, ES7, 8, 9.. 버전 카운팅이 모호하여, ES2016, 2017.. 연도 카운팅으로 버저닝하기도 한다.)

     

    현재는 ECMA의 TC39에서 매년 6월 ECMAScript의 새 버전을 출시하고 있다.

    * TC39(Technical Committee 39) : JS의 전신인 Mocha를 개발한 Brendan Eich가 속한 팀. ECMA-262 제정을 담당한다.

     


     

    - ECMAScript7 (2016)

    TC39는 2015년 이후로, 매년 ECMAScript의 새 버전을 출시하기로 했다.

    ES6와 같은 대규모 업데이트의 단점(팔로우업의 소요, 브라우저 호환성) 을 겪다보니, 연간 업데이트가 나을 것이라 생각한 것이다.

    * ES2016은 이전 ES2015가 호환성에 애를 먹는 단계에 있어서인지, 2가지 기능만이 출시되었던 해이다.

     

    1) Array.prototype.includes()

    arr.includes([item], [from])

    배열에 특정요소(item)가 포함됬는지 여부를 Boolean으로 반환하는 메서드이다.

    일치여부는 === 연산자를 활용하며, 두 번째 인자인 from을 넣으면 해당 인덱스부터 검색한다.

     

    let arr = [1, 'a', 4, 'b', NaN]
    
    arr.includes('a')	// true
    arr.includes('a', 2)	// false
    arr.includes(3)		// false
    
    arr.includes(NaN)	// true
    arr.indexOf(NaN)	// -1

    위 예시처럼 사용할 수 있으며, indexOf 에서는 불가능했던 NaN의 감지가 가능하다.

     

     

    2) Exponentiation Operator

     

    제곱연산을 할 수 있는 연산자(**)이다. ES2015까지는 Math.pow()로 계산했다.

    // ES7
    2 ** 10		// 1024
    
    // ES6
    Math.pow(2, 10)		// 1024

     


     

    - ECMAScript8 (2017)

     

    1) Async / Await

     

    어쩌면 ES2017의 핵심이라고 할 수 있겠다.

    기존 비동기를 위한 Promise()의 문제점인, Promise Chaining 개선 및 가독성 향상(동기로직 플로우)을 위한 문법이다.

    Async/Await의 자세한 설명은, 이전 포스팅한 링크를 참고하기 바란다. (https://abangpa1ace.tistory.com/83?category=910462)

     

    [Javascript] 비동기 - #3. Async & Await

    프로젝트를 마무리하고, 이제서야 비동기 3탄이자 꽃인 async/await 에 대해 포스팅을 한다. async/await 은 JS의 비동기 처리를 개선하기 위해(콜백지옥), Promise 처럼 ES6 이후 문법에서 등장했으며 가장

    abangpa1ace.tistory.com

     

    여기서는, Async / Await을 쓰면서 기존 Promise와 달라진 비동기 플로우만 예시로 보고 넘어가겠다.

    // ES8 : async/await 함수
    async function getPersonInfo(id) {  
        try {
            const info = await fetch(id);
            const text = await info.text();
    
            return JSON.parse(text);
        } catch (err) {
            console.log(`Error: ${err.message}`);
        }
    }
    
    
    // ~ES7 : Promise 객체
    function getPersonInfo(id) {  
        return fetch(id)
            .then(info => info.text())
            .then(text => JSON.parse(text))
            .catch(err => {{
                console.log(`Error: ${err.message}`);
            });
    }

     

    * Async / Await 구현 : Generator(제너레이터) 문법

     

    Async / Await의 구현원리에 대한 좋은 글이 있어 간단하게 정리하고자 한다. (링크)

    async function은 제네레이터로 구현되어있다. 함수 앞에 async 키워드를 붙이면 내부로직을 제네레이터 함수로 감싸게 된다.

    // ES8 : async function
    // 클래식 함수
    async function getHello() {
        // await 3s...
        return "Hello, World!";
    }
    
    //
    // 화살표 함수
    const getHello = async () => {
        // await 3s...
        return "Hello, world!";
    };
    // ES7 : translated to generator
    // 클래식 함수
    function getHello() {
        function* getHello() {
            // await 3s...
            return "Hello, World!";
        }
        return __await(this, undefined, getHello);
    }
    
    //
    // 화살표 함수
    const getHello = () => {
        function* getHello() {
            // await 3s...
            return "Hello, World!";
        }
        return __await(this, undefined, getHello);
    };

     

    await 키워드는 제네레이터로 구현된 async function의 yield, next() 기능을 통해 내부적으로 비동기 역할을 수행한다. 

    // ES8
    function asyncSleep(t) {
        return new Promise((resolve) => {
            setTimeout(resolve, t);
        });
    }
    
    async function asyncGenerateHello() {
        await sleep(123);
        await sleep(456);
        return "Hello, World!";
    }
    
    async function main() {
        const hello = await asyncGenerateHello();
        console.log(hello);
    }
    
    main();
    // ES7
    function __await(thisArg, _arguments, generator) {
        //
        // value를 프로마이즈로 변환하는 함수.
        // 이미 프로마이즈라면 그대로 반환한다.
        function adopt(value) {
            return value instanceof Promise
                ? value
                : new Promise(function (resolve) {
                      resolve(value);
                  });
        }
    
        //
        // 프로마이즈로 감싸지 않으면 동기적으로 수행됨에 주의.
        return new Promise(function (resolve, reject) {
            generator = generator.apply(thisArg, _arguments || []);
    
            function fulfilled(value) {
                try {
                    step(generator.next(value));
                } catch (e) {
                    reject(e);
                }
            }
    
            function rejected(value) {
                try {
                    step(generator.throw(value));
                } catch (e) {
                    reject(e);
                }
            }
    
            //
            // 내부로직이 끝날 때 까지 반복.
            function step(result) {
                result.done
                    ? resolve(result.value)
                    : adopt(result.value).then(fulfilled, rejected);
            }
            step(generator.next());
        });
    }
    
    function asyncSleep(t) {
        return new Promise((resolve) => {
            setTimeout(resolve, t);
        });
    }
    
    function asyncGenerateHello() {
        return __await(this, undefined, function* () {
            yield asyncSleep(123);
            yield asyncSleep(456);
            return "Hello, World!";
        });
    }
    
    function main() {
        return __await(this, undefined, function* () {
            const hello = yield asyncGenerateHello();
            console.log(hello);
        });
    }
    
    main();

    __await()는 내부 로직을 제네레이터를 통해 비동기적으로 실행하며, 실제 await 키워드가 적혔던 부분에 yield가 들어간다.

    내부로직은 Promise() 를 통해 비동기적으로 실행되며, 결과값 역시 adopt() 메서드를 통해 Promise() 객체로 반환된다.

     

     

    2) Shared memory and atomics

     

    SharedArrayBuffer 객체, Atomics 객체를 통한 메모리 공유 역시, ES2017의 주요 기능 중 하나이다.

    쉽지 않은 내용이지만, 싱글 스레드 언어인 Javascript에서 멀티 스레드적인 메모리 공유를 위한 성능 향상 정도로 이해하면 되겠다!

     

    * SharedArrayBuffer 객체 (Mozila 링크)

    길이가 고정된 바이너리 데이터 버퍼이다. ArrayBuffer 객체와 유사하지만 공유된 메모리 뷰를 생성할 때 사용된다.

     

    웹 언어인 Javascript는 병렬작업을 진행하기 위해 Web Workers들을 활용한다.

    이 worker들은 각자 분리된 전역 환경에서 실행되기에, worker 혹은 메인 스레드와 통신하지 않고서는 데이터를 공유할 수 없다.

     

    이러한 단점을 개선하기 위해 SharedArrayBuffer 객체가 등장했고, 각각의 에이전트(메인 및 worker 스레드)가 데이터를 직접 공유할 수 있게 한다.

    // 1024바이트 크기의 버퍼 생성
    var sab = new SharedArrayBuffer(1024);
    
    // worker를 통해 다른 에이전트와 데이터 공유
    worker.postMessage(sab);

     

    * ArrayBuffer와 SharedArrayBuffer를 만화로 보자!(링크

     

    만화로 소개하는 ArrayBuffer 와 SharedArrayBuffer ★ Mozilla 웹 기술 블로그

    이 글은 3부작 시리즈의 두번째 글입니다. 메모리 특강 만화로 소개하는 ArrayBuffer 와 SharedArrayBuffer Atomics 를 이용해서 SharedArrayBuffer 레이스 컨디션 피하기 지난 글에서는, JavaScript 같은 메모리 자

    hacks.mozilla.or.kr

    SharedArrayBuffer를 통한 데이터 공유와, 문제점인 race condition이 잘 설명되어 있다. 이 내용이 다음 Atomics 객체와 이어진다.

     

     

    * Atomics 객체(Mozila 링크)

    아토믹 연산(atomic operation)을 위한 정적 메서드를 제공하며, SharedArrayBuffer 객체와 같이 사용된다.

     

    위 만화에서, SharedArrayBuffer를 쓰면 2개의 스레드가 동시 접근하는 위험상황(race condition)이 발생할 수 있다고 서술한다.

    라이브러리 개발자들이 이러한 레이스 컨디션을 회피하기 위해, Atomics 객체가 제공하는 메서드들을 활용하는 것이다.

    에이전트 간 메모리를 공유할 때, 읽기/쓰기가 동시에 진행되고 있을 것이다. 이 작업을 질서있게 관리하기 위해 Atomics 객체가 사용된다.

     

    에이전트는 이전 에이전트의 데이터 쓰기 작업이 끝날 때까지 대기 Queue에 포함된다.(sleep 상태)

    Atomics.wait() 메서드로 sleep이 끝나는 조건(Atomics.wake())까지 대기하고, 데이터 읽기가 필요한 경우 Atomics.load()로 특정 지점의 값을 읽는다.

    // 1024바이트 크기의 버퍼 생성
    var sab = new SharedArrayBuffer(1024);
    
    // shared typedArray 객체 생성
    var int32 = new Int32Array(sab);
    
    // 읽기 스레드가 typedArray 객체의 인덱스 번호가 '0'인 위치의 값이 '0'인지 테스트한다. 'true'가 반환되면 'sleep' 상태에서 대기한다.
    // 대기 타임아웃을 지정할 수 있다(기본값: Infinity).
    Atomics.wait(int32, 0, 0);
    
    // 쓰기 스레드가 새로운 값을 저장하면 깨어나 새로운 값을 반환한다.
    console.log(int32[0]); // 123

     

    * Atomics 객체를 만화로 보자! (링크)

     

    Atomics 를 이용해서 SharedArrayBuffer 레이스 컨디션 피하기 ★ Mozilla 웹 기술 블로그

    이 글은 3부작 시리즈의 세번째 글입니다. 메모리 특강 만화로 소개하는 ArrayBuffer 와 SharedArrayBuffer Atomics 를 이용해서 SharedArrayBuffer 레이스 컨디션 피하기 지난 글에서, 저는 SharedArrayBuffer 를 사용

    hacks.mozilla.or.kr

     

     

    3) Object.values(), Object.entries()

     

    기존 Object.keys()로 key값만 순회했으나, ES2017부터는 value, 그리고 둘 모두를 튜플[key, value]로 순회하는 메서드를 지원한다.

    const obj = { a: 1, b: 2, c: 3 };
    
    console.log(Object.values(obj)); // [ 1, 2, 3 ]
    console.log(Object.entries(obj)); // [ [ 'a', 1 ], [ 'b', 2 ], [ 'c', 3 ] ]

     

     

    4) Object.getOwnPropertyDescriptors()

     

    기존 getOwnPropertyDescriptor() 메서드의 복수형인 것이다. (객체)를 인자로 넘겨, 해당 프로퍼티의 설명자들을 반환한다.

    const obj = { a: 1, b: 2, c: 3 };
    
    console.log(Object.getOwnPropertyDescriptor(obj, "a"));
    // { value: 1, writable: true, enumerable: true, configurable: true }
    console.log(Object.getOwnPropertyDescriptors(obj));
    /*
    {
      a: { value: 1, writable: true, enumerable: true, configurable: true },
      b: { value: 2, writable: true, enumerable: true, configurable: true },
      c: { value: 3, writable: true, enumerable: true, configurable: true }
    }
    */

     

     

    5) String Padding : padStart(), padEnd()

     

    문자열의 패딩 메서드다. 인자는 (string 길이, 채울값) 2개를 넘기며, 앞에서부터 채우면 padStart / 뒤에서부터면 padEnd 를 사용한다.

    const month = "8";
    const number = "1.2";
    
    const padMonth = month.padStart(2, "0");
    const padNumber = number.padEnd(5, "x");
    
    console.log(padMonth); // 08
    console.log(padNumber); // 1.2xx

     

     

    6) Trailing Commas

     

    객체뿐만 아니라, 함수에서도 인자(매개변수)의 마지막에 콤마를 붙일 수가 있다. 이것이 추가된 이유는, 기능보다는 Lint에 이유가 있다.

    // 수정 전
    myFunc({x : 2 , y: 4} ,
      () => {} 
    );
    
    // 수정 후
    myFunc({x : 2 , y: 4} ,
      () => {} ,
      77777
    );

    위처럼 함수 내 매개변수가 긴 코드를 예시로 들자. 아래처럼 수정되면 실제 수정한 건 Line3, 4뿐이지만, Line 2도 수정되었다 인식될것이다.

    이러한 이유로 Trailing Commas가 함수에도 추가됬으며, ESLint는 이러한 측면에서 객체 리터럴에서 마지막 콤마를 장려한다.

     


     

    - ECMAScript9 (2018)

    ES2018은 정규식과 비동기 작업에 대한 새로운 기능이 주요 변경점이다.

     

    1) for-await-of 반복문 : Async Iterator

     

    for-of 반복문에 이터레이터가 Promise()를 반환한다면, for-await-of 반복문으로 이를 비동기로 가져올 수 있다.

    // ES9 : for-await-of
    for await (const value of promises) {
        console.log(value);
    }
    
    
    // ~ES8 : for-of + 내부 await
    async myFunc() {
      for (const promise of promises) {
        const value = await promise;
        console.log(value);
      }
    }
    
    myFunc()

     

    기존의 Symbol.iterator와 다르게, Symbol.asyncIterator는 async 키워드를 사용할 수 있게 된 것이다.

    // ES9 : Symbol.asyncIterator
    
    const myIterator = {
        async *[Symbol.asyncIterator]() {
            for (let i = 0; i < 5; i++) {
                await new Promise((resolve) => setTimeout(resolve, 1000));
                yield i;
            }
        },
    };
    
    
    // ~ES8 : Symbol.iterator
    const myIterator = {
        *[Symbol.iterator]() {
            for (let i = 0; i < 5; i++) {
                yield new Promise((resolve) => {
                    setTimeout(() => resolve(i), 1000);
                });
            }
        },
    };

     

     

    2) Promise.prototype.finally()

     

    기존 Promise()는 fulfilled(.then()) / rejected(.catch()) 2가지 상태만 가능했으나, .finally() 메서드를 통해 성공/실패 여부에 상관없이 반드시 실행해야하는 로직을 작성할 수 있다.

    Promise.resolve('reslove')
      .then((res) => console.log('success'))
      .catch((err) => console.log('fail'))
      .finally(() => console.log('finally'))

     

     

    3) Object : Rest / Spread Properties

     

    기존에 배열에서 사용하던 rest, spread 연산자를 객체에서도 사용할 수 있다.

    // Rest Properties
    const { a, b, ...others } = {
        a: 1,
        b: 2,
        x: 3,
        y: 4,
    };
    console.log(a); // 1
    console.log(b); // 2
    console.log(others); // { x: 3, y: 4 }
    // Spread Properties
    // ES9
    const oldBox = { x: 1, y: 2 };
    const newBox = {
        a: 1,
        b: 2,
        ...oldBox,
    };
    
    
    // ~ES8
    const oldBox = { x: 1, y: 2 };
    const newBox = Object.assign({ a: 1, b: 2 }, oldBox);

     

     

     

    4) 정규식(RegExp) 강화

     

    Named Capture Groups : 기존에는 캡쳐그룹을 숫자(인덱스)로만 참조했지만, 네이밍이 가능해졌다. 문법은 (?<이름>정규식)

    let result = re.exec('2015-01-02');
    // result.groups.year === '2015';
    // result.groups.month === '01';
    // result.groups.day === '02';
    
    // result[0] === '2015-01-02';
    // result[1] === '2015';
    // result[2] === '01';
    // result[3] === '02';

     

    Unicode Property Escapes : 유니코드 패턴을 제공하며, 문법은 \p{유니코드셋} 이며, 유니코드셋은 카테고리를 명시해준다.

    /\p{General_Category=Letter}/gu.test("a"); // 구체적 명시
    /\p{Letter}/gu.test("a"); // 카테고리값만 명시
    /\p{L}/gu.test("a"); // Letter의 축약형
    /\p{Emoji}/gu.exec("👌"); //

    유니코드가 추가되어도, Node.js 에서 업데이트를 해주기 때문에 활용하기 편하다. 카테고리 종류는 링크 참조.

     

    LookBehind Assertions : 특정 패턴이 앞서나오거나, 앞서나오지 않는지를 확인한다. 문법은 아래와 같다.

    • Positive LookBehind : (?<=regex1)regex2
    • Negative LookBehind : (?<!regex1)regex2
    /(?<=[xyz])123/.test("x123"); // true
    /(?<=[xyz])123/.test("m123"); // false
    /(?<![xyz])123/.test("x123"); // false

     

    s Flag(dotAll 모드) : 정규식에서 "."은 개행문자(\n, line terminator)를 제외했다. 이를, 뒤에 s 플래그를 붙여 포함시키는 문법이다.

    // ES9
    
    /foo.bar/su.test('foo\nbar');
    // → true
    /foo.bar/u.test('foo\nbar');
    // → false
    
    
    // ~ES8
    /foo.bar/u.test('foo\nbar');
    // → false

    생각보다 내가 자주 사용하던 문법이 ES6 이후로 보완된 부분들이 많았다. (Object.values() 나 Promise.finally() 등)

     

    ES2016 ~ 2021 모두를 한 포스팅에 담으려 했으나, 중요한 내용(async function, SharedArrayBuffer) 등을 나름 정리하다보니 분량이 길어지고 몰입도가 떨어진다고 판단되서 ES2019(ES10) 부터는 별도로 포스팅하겠다!

     

    📎 출처

    - [Javascript History] Madasamy M's blog : https://madasamy.medium.com/javascript-brief-history-and-ecmascript-es6-es7-es8-features-673973394df4

    - [ES 변경점] AeroCode 님의 블로그 : https://aerocode.net/383?category=822607  

    - [Javascript 동향] Naver D2 블로그 : https://d2.naver.com/helloworld/7495331  

    - [SharedArrayBuffer & Atomics] hacks 모질라 만화(3부작) : http://hacks.mozilla.or.kr/2017/11/a-crash-course-in-memory-management/  

    반응형
Designed by Tistory.