본문 바로가기
Development/Javascript

JS ES6~ES12 에서 추가된 주요 문법들 (모던 리액트 이해에 필요한 ECMA Script ES6~ES12 문법들)

by Nahwasa 2022. 1. 15.

  현재 리액트를 공부중이다. 그런데 너무 생소한 문법들이 많았다. 처음엔 리액트만의 문법이겠거니 생각했는데, 찾아보니 자바스크립트에서 ES6 이상에서 추가된 문법들이 대부분이었다. 그래서 모던 리액트 문법을 이해하는데 필요한 ES6~ES12에서 추가된 문법들과, 딱히 리액트와 관련 없더라도 유용해보이는 ES6~ES12 문법을 정리해봤다. 특히 ES6에 새로운 문법들이 많다.

 

Contents

     

     

     

    [ ES6 (ES2015) ]

    1. const, let

    1.1 var

      ES6이전에 사용하던 var는 함수 스코프 변수이다. 그래서 일반적인 다른 언어들과는 동작 방식 자체가 다르다. 보통 함수내에 변수를 선언하고, if나 for, while 등을 사용한다면 일반적으로 별도의 스코프 영역을 가질거라 예상하지만 var로 사용시엔 그렇지 않다. 호이스팅되서 함수 맨 위로 올라가버린다. 그래서 다음과 같이 이상한 동작이 가능하다.

    function test() {
        var str = "aaa";
        // var str = "zz"; 함수스코프라서 여기로 올라와 버린다. 
    
        if (true) {
            var str = "zz";
            console.log(str);
        }
        console.log(str);
    }
    
    > test();
      zz
      zz

     

    마찬가지로 다음과 같은 경우의 문제점(다른언어 보다는 js에만 익숙하다면 문제점이 아니라 특징으로 여길수도 있을 것 같다.)도 있다. 뭘 눌러도 '6번째 div'라고 출력된다. var i가 함수 시작점으로 호이스팅 됐으므로 for문이 끝난 후 i는 6이 되서, 전부 저렇게 호출되는 것이다.

    <!DOCTYPE html>
    <html>
    <head>
    <style>
    	div {
    		width:200px;
    		height:50px;
    		background-color:#aaa;
    		margin-bottom:10px;
    	}
    </style>
    
    <script type="text/javascript">
    	document.addEventListener("DOMContentLoaded", function(){
    		var body = document.getElementById('test');
    		
    		for (var i = 0; i < 5; i++) {
    			var div = document.createElement('div');
    			div.onclick = function() {
    				alert((i+1) + "번째 div");
    			};
    			
    			body.appendChild(div);
    		}
    		
    	});
    </script>
    </head>
    <body id='test'>
    </body>
    </html>

     

    1.2 let

      let은 일반적으로 개발자들이 알고있는 언어에서의 변수처럼 블록 스코프를 가진다. 따라서 바로 위의 예시를 let으로 변경하면 모든 div가 예상했던대로 동작한다.

      let과 이후에 작성할 const도 호이스팅이 되긴 한다. 하지만 var와 다르게 let과 const는 실제 사용되기 전엔 TDZ (Temporal Dead Zone)에서 관리되면서, 아직 선언이 나오기 전인데 사용한다면 에러(ReferenceError)를 출력한다. js 호이스팅 고수라서 굳이 var를 사용해 자기만 알아볼 수 있는 레거시 코드를 만들려는 계획이 아니라면 let, const만 사용하면 될 것 같다. 

     

    1.3 const

      스코프 동작 방식 자체는 let과 동일하지만, 선언 시 초기화를 해줘야하고 이후 변경이 불가하다. 한마디로 상수이다. 

     

     

    2. Template string

      c 계열의 'printf("%s %s", a, b);' 자바의 'String.format("%s %s", a, b);' 와 같은 동작을 js에서도 할 수 있게 기능이 추가되었다. 아래를 보면 바로 이해 가능하다.

    let a = "11";
    let b = "22";
    let c = "33";
    
    console.log(a+", "+b+", "+c);		// 11, 22, 33
    console.log(`${a}, ${b}, ${c}`);	// 11, 22, 33

      주의점은 작은따옴표( ' ) 가 아니라 ( ` ) 이다(일반적으로 Tab 버튼 위에 있는 grave accent).

    특히 뭐 동적으로 html 만들어서 붙여넣을 경우에

     

    let tmp = "<div>" + contents + "</div>"; 이런식으로 많이 썼었는데 이런게 많아지면 상당히 알아먹기 힘들다.

    앞으론 let tmp = `<div>${contents}</div>`; 이런식으로 하면 되서 매우 유용할 것 같다.

     

    또 이런것도 가능하다.

    let a = 50;
    let b = 23;
    
    console.log(`${a+b}`);	// 73

     

     

    3. Default parameter

      함수 파라미터에 디폴트 값 선언이 가능하다. 만약 a나 b에 파라미터가 없을 경우 디폴트 값이 필요하다면 다음과 같이 짤 수 있을 것이다.

    function test(a, b) {
        if (a == null) 
            a = "aaaa";
        if (b == null)
            b = "bbbbb";
        console.log(`a=${a}, b=${b}`);
    }
    
    test("ccc");	// a=ccc, b=bbbbb

     

    ES6에 추가된 Default parameter를 사용하면 다음과 같이 짤 수 있다. 물론 문자열 뿐 아니라 오브젝트 등 아무거나 들어갈 수 있다.

    function test(a="aaaa", b="bbbbb") {
        console.log(`a=${a}, b=${b}`);
    }
    
    test("ccc");	// a=ccc, b=bbbbb

     

     

    4. Arrow function

      기존 function 보다 좀 더 짧게 함수를 만들 수 있다.

     

    기존 function은 다음과 같은 방식이다.

    function test(a) {
    	return "parameter : " + a;
    }
    
    또는
    
    let test = function(a) {
    	return "parameter : " + a;
    }

     

    Arrow function을 사용하면 다음과 같이 나타낼 수 있다.

    let test = (a) => { return "parameter : " + a; }
    
    // parameter가 1개 일땐 괄호를 생략할 수 있다.
    let test = a => { return "parameter : " + a; }
    
    // 함수에서 처리할 내용이 한 줄일때는 중괄호와 return도 생략할 수 있다.
    let test = a => "parameter : " + a;
    
    // Template string, Default Parameter와 함께 사용하면 다음과 같다.
    let test = (a="dd") => `parameter : ${a}`;

     

      단순히 function 보다 코드가 짧아지기만 했다면 굳이 arrow function에 큰 특징이 있다고 보기 힘들 것 같다. 추가적인 특징으로는 arrow function은 'this'를 새로 바인딩하지 않는다. 이건 뭐 장점같은 것이라기보다는 상황에 맞춰 사용하면 될 것 같다.

    let testObj = {
        a: "abcd",
        func1: function() {
            setTimeout(function() { console.log(this.a) }, 1000);
        },
        func2: function() {
            setTimeout(() => console.log(this.a), 1000);
        }
    }
    
    
    testObj.func1(); 	// undefined
    testObj.func2();	// abcd

     

     

    5. Destructuring

      Object내의 key의 value를 원하는 변수로 빼내거나, 배열의 각 값을 변수로 빼낼 수 있는 기능이다.

    우선 Object부터 확인해보자. 다음과 같이 '{ }' 내에 원하는 key를 넣어주면 된다.

    let obj = {
        a: "aaaa",
        b: "bbb",
        c: "cc",
        d: "d"
    }
    
    let {a, b} = obj;
    
    a;	// aaaa
    b;	// bbb
    
    
    // 변수 명을 변경하려는 경우
    let obj = {
        a: "aaaa",
        b: "bbb",
        c: "cc",
        d: "d"
    }
    
    let {valueA=a, valueB=b} = obj;
    
    valueA;	// aaaa
    valueB;	// bbb

     

      이번엔 위에서 봤었던 ES6 문법들 몇가지를 합쳐봤다. destructuring을 통해 parameter를 바로 받고 Arrow function과 Template string으로 출력했다.

    let obj = {
        a: "aaaa",
        b: "bbb",
        c: "cc",
        d: "d"
    }
    
    let func = ({tmp1=a, b}) => console.log(`param: ${tmp1}, ${b}`);
    
    func(obj);	// param: aaaa, bbb

     

      배열도 마찬가지로 '[ ]'를 통해 Destructuring이 가능하다. 이 경우 key값이 따로 없으므로 idx 순서대로 변수에 매칭된다.

    let arr = [1,23,45,67,890];
    
    let [a1, a2, a3] = arr;
    let [,,,,a5] = arr;
    
    console.log(`${a1}, ${a2}, ${a3}, ${a5}`);	// 1, 23, 45, 890

     

      리액트 공부하면서 제일 궁금했던게 useState 사용 시 let [a, b] 와 같은 방식의 사용과, 컴포넌트 만들 때 부모에게서 값을 받으면서 ({ a })와 같이 사용하는 방식이었다. 이게 모두 Destructuring에 포함된 내용이었다.

     

     

     

    6. Object literal enhancement

      객체에 대한 Destructuring의 반대개념이다. 다음과 같이 사용 시, 알아서 변수명이 key로 넣어진다.

    let a = "aaaaa";
    let b = "bbbb";
    
    let obj = {a, b};  // {a:a, b:b}와 동일. {tmp1:a, b} 이런식으로 섞어서도 가능

     

    변수는 물론 function에 대해서도 마찬가지로 동작한다.

    let obj = {
        func1: function() { console.log("기존방식"); },
        func2() { console.log("추가된 방식"); }
    }

     

     

    7. Spread operator

      배열이나 오브젝트의 내용을 조합하는데 쓸 수 있다. '...' 으로 사용한다.

    // 배열
    let arr1 = [1,2,3,4];
    let arr2 = ['a','b','c'];
    let arrSum = [...arr1, ...arr2];
    
    arrSum; 	// (7) [1, 2, 3, 4, 'a', 'b', 'c']
    
    
    // 오브젝트
    let obj1 = {
        a: 'aaaa',
        b: 'bbb',
        c: 'cc'
    }
    
    let obj2 = {
        pr1: 'ddd',
        pr2: 'zzzz',
    }
    
    let objSum = {
        ...obj1,
        ...obj2
    }
    
    objSum;		// {a: 'aaaa', b: 'bbb', c: 'cc', pr1: 'ddd', pr2: 'zzzz'}

     

    Destructuring과 함께 사용해 나머지 값들을 획득하는데도 쓸 수 있다.

    let arr = [1,2,3,4,5,6,7];
    let [idx0, ...others] = arr;
    
    idx0;		// 1
    
    others;		// (6) [2, 3, 4, 5, 6, 7]

     

     

    8. Module

      함수, 오브젝트, 변수, 상수 등을 모듈화해서 외부로 export 할 수 있게 되었다. 이후 다른 js 파일에서 import해서 사용할 수 있다. 이하 예시에서 'test-export.js'에서는 'consoleLog'와 'alertMsg'를 export한다. test-default-export에서는 'maxLen'만 export 하는데, default를 붙여서 export 하면 해당 js에서 단 하나만 export 한다는 의미이다. 또한 이 경우 import 시에 '{ }'를 쓰지 않고 사용할 수 있다. 'test-main.js'에서 위 두개를 import해서 사용한다. 이 때 'as'를 통해 이름을 바꿔서 사용할 수 있다. 

    // test-export.js
    export const consoleLog(msg) => console.log(msg);
    export const alertMsg(msg) => alert(msg);
    
    
    // test-default-export.js
    const maxLen = 100;
    export default maxLen;
    
    
    // test-main.js
    import {consoleLog, alertMsg as am} from './test-export'
    import maxLen from './test-default-export'
    
    consoleLog("test"); 	// test
    am("abcd");				// (alert 창으로) abcd
    console.log(maxLen);	// 100

     

     

    9. Promise

      ajax나 axios(promise 기반으로 만들어짐)와 같이 비동기 요청을 편하게 사용할 수 있도록 해준다. 

    // 성공시 resolve 호출, 실패시 reject 호출 하도록 하겠음
    const nahwasaChk = () => new Promise((resolve, reject) => {	
        const req = new XMLHttpRequest();
        req.open('GET', 'https://nahwasa.tistory.com');
        req.onload = () => {
            if (req.status === 200) 
                resolve('ok');	// 성공시
            else
                reject(Error(req.statusText));	// 실패시
        };
        req.onerror = err => reject(err);
        req.send();
    });
    
    // then 함수의 파라미터로 resolve, reject 정한 순서대로 처리할 함수 넣으면 됨
    nahwasaChk().then(
        res => console.log(res),
        err => console.log(err)
    );

     

     

     

    [ ES7 (ES2016) ]

    1. Exponentiation operator

      '**'로 제곱 연산이 가능하다.

    let test = 2**10;
    
    test;	// 1024

     

    2. Array.prototype.includes()

      배열에 특정 값이 있는지 확인할 수 있다.

    let arr = ['ff', 'aa', 'dd'];
    
    Array.includes(arr, 'ff');	// true
    arr.includes('cd');		// false

     

     

    [ ES8 (ES2017) ]

    1. Object.values(), Object.entries()

      기존에서 keys()는 있었는데, 거기에 값만 보는 values()와 둘다 리턴해주는 entries()가 추가되었다. 사실 keys()로도 가능했던거라 그냥 편의성 측면이다.

    let obj = {
        name: 'nahwasa',
        url: 'https://nahwasa.com',
        rmk: 'js jemiupsu..'
    };
    
    
    Object.keys(obj).forEach(key => {
        console.log(`${key} : ${obj[key]}`);
    });
    // name : nahwasa
    // url : https://nahwasa.com
    // rmk : js jemiupsu..
    
    
    Object.values(obj).forEach(value => {
        console.log(value);
    });
    // nahwasa
    // https://nahwasa.com
    // js jemiupsu..
    
    
    Object.entries(obj).forEach(entry => {
        let [key, value] = entry;	// entry[0]이 key, entry[1]이 value
        console.log(key, '->', value);
    });
    // name -> nahwasa
    // url -> https://nahwasa.com
    // rmk -> js jemiupsu..

     

    2. Async, Await

      기존에 ES6의 Promise에서 then이 연속적으로 구성되던 점과 일명 'Callback Hell' 문제점을 해결하기 위해 추가된 기능이다. await 뒤에 Promise 객체를 리턴하는 함수를 넣어주고, await을 사용하는 함수앞에 예약어로써 async를 붙여주는 방식이다.

    const nahwasaChk = () => new Promise((resolve, reject) => {	
        const req = new XMLHttpRequest();
        req.open('GET', 'https://nahwasa.tistory.com');
        req.onload = () => {
            if (req.status === 200) 
                resolve('ok');	// 성공시
            else
                reject(Error(req.statusText));	// 실패시
        };
        req.onerror = err => reject(err);
        req.send();
    });
    
    
    // ES6의 then을 사용한 promise 사용
    nahwasaChk().then(
        res => console.log(res),
        err => console.log(err)
    );
    
    
    // ES8의 Async, Await을 사용한 promise 사용
    let waitNahwasaChk = async () => {
    // arrow function으로 안하려면 async function waitNahwasaChk() {...
        let res = await nahwasaChk();
        console.log(res);
    };
    waitNahwasaChk(); //ok
    
    
    // ES8의 Async, Await 또다른 사용문법
    (async() => {
        let res = await nahwasaChk();
        console.log(res);	//ok
    })();

     

    [ ES9 (ES2018) ]

    1. Promise finally

      Promise에 '.then', '.catch'이외에 '.finally'가 추가되었다. 성공, 실패와 관계없이 무조건 수행된다.

    // 성공시 resolve 호출, 실패시 reject 호출 하도록 하겠음
    const nahwasaChk = () => new Promise((resolve, reject) => {	
        const req = new XMLHttpRequest();
        req.open('GET', 'https://nahwasa.tistory.com');
        req.onload = () => {
            if (req.status === 200) 
                resolve('ok');	// 성공시
            else
                reject(Error(req.statusText));	// 실패시
        };
        req.onerror = err => reject(err);
        req.send();
    });
    
    // then 함수의 파라미터로 resolve, reject 정한 순서대로 처리할 함수 넣으면 됨
    nahwasaChk()
    .then(
        res => console.log(res),
        err => console.log(err)
    ).finally(() => alert("end!"))	// finally는 성공 실패와 관계없이 수행

     

     

    2. for await...of

      for ...of의 비동기 버전이다. 

    async function* numDecrease() {
        let num = 5;
        while(num-->0) {
            yield num;
        }
    }
    
    (async function() {
      for await (let num of numDecrease()) {
        console.log(num);
      }
    })();
    
    // 4
    // 3
    // 2
    // 1
    // 0

     

     

     

    [ ES10 (ES2019) ]

    1. Object.fromEntries()

      ES8의 entries()를 반대로 한다.

    let arr = [
        ['name', 'nahwasa'],
        ['url', 'https://nahwasa.com']
    ];
    let resObj = Object.fromEntries(arr);
    
    resObj; 	// {name: 'nahwasa', url: 'https://nahwasa.com'}

     

    2. String.prototype.trimLeft(), trimRight()

      기존 trim()은 공백을 모두 제거한다. trimLeft와 trimRight는 좌측과 우측 중 어떤 공백을 제거할 지 정할 수 있다. trimLeft는 trimStart와 동일하고, trimRight는 trimEnd와 동일하다.

    let str = "       nahwasa         ";
    
    console.log(`[${str.trim()}]`);	//[nahwasa]
    
    console.log(`[${str.trimLeft()}]`);	//[nahwasa         ]
    console.log(`[${str.trimRight()}]`);	//[       nahwasa]
    
    console.log(`[${str.trimStart()}]`);	//[nahwasa         ]
    console.log(`[${str.trimEnd()}]`);	//[       nahwasa]

     

     

    [ ES11 (ES2020) ]

    1. Nullish coalescing operator

      어떠한 변수는 [false, undefined, null, 0, '']의 값을 가질 때 false가 된다. 프로젝트에 따라 다를 수 있겠으나, 일반적으로 rest api로 주고받는 json에서 해당 key값이 존재하지 않는 것으로 치는 것은 'undefined'와 'null'이다. 이럴 때 유용한 것이 추가되었다. '??'를 통해 사용할 수 있다. 'if ( a == undefined || a == null ) {~~;}' 와 같이 처리하던 부분을 많이 줄일 수 있다.

    let obj = {
        a: '',
        c: null,
        d: false
    };
    
    console.log( obj.a || '값 없음' );		//값 없음
    console.log( obj.a ?? '값 없음' );		//''
    
    console.log( obj.b || '값 없음' );		//값 없음
    console.log( obj.b ?? '값 없음' );		//값 없음
    
    console.log( obj.c || '값 없음' );		//값 없음
    console.log( obj.c ?? '값 없음' );		//값 없음
    
    console.log( obj.d || '값 없음' );		//값 없음
    console.log( obj.d ?? '값 없음' );		//false

     

     

    2. Optional chaining

      객체에서 해당 key의 value를 접근할 때 '.'을 사용하는데, 이걸 '?.'로 해주면 Nullish coalescing operator처럼 undefined나 null이 아닐때만 접근이 된다. 예를들어 obj.arr 에서 arr이 null이라면 기존엔 에러였지만, obj?.arr라고 하면 에러가 나지 않고 undefined가 리턴된다.

    let obj = {
        data: {
            sensor: {
                date: '2022-01-15',
                value: 50
            }
        }
    };
    
    // 기존에 에러를 회피하기 위한 체크구문
    if (obj != null && obj.data != null && obj.data.sensor != null && obj.data.sensor.date != null) {
        console.log(obj.data.sensor.date);	// 2022-01-15
    }
    
    // Optional Chaining을 사용해 훨씬 쉽게 가능!
    console.log(obj?.data?.sensor?.date);	// 2022-01-15
    
    // 중간에 없는 key를 넣으면 undefined 출력됨.
    console.log(obj?.data?.sensorAAAAAA?.date);	// undefined

     

     

     

    [ ES12 (ES2021) ]

    1. Logical assignment operators

      logical AND ('&&'), logical OR ('||'), ES11에서 추가된 logical nullish assignment('??')에 대해 compound assignment operators가 생겼다. 'a=a+b' 대신에 'a+=b'와 같이 사용하던것과 마찬가지다.

    // or
    let a = false;
    a ||= true;		// true
    let b;
    b ||= 10;		// 10
    let c = '';
    c ||= 10		// 10
    
    
    // and
    let a = false;
    a &&= true;		// false
    let b = 10;
    b &&= 15;		// 15
    
    
    // nullish
    let a;
    a ??= 5;		// 5 (a가 undefined 이므로)
    let b = '';
    b ??= 10		// ''

     

      참고로 ES11의 '2'에서도 썼듯이 어떠한 변수는 [false, undefined, null, 0, '']의 값을 가질 때 false가 된다. 이에 따른 '&&', '||', '??' 연산은 다음과 같이 이해하면 편하다.

     

     

    2. Numeric separators

      '_'로 숫자 구분기호를 넣을 수 있다.

    let a = 100_000_000;
    a;	// 100000000

     

     

     

     

    [ References ]

    A. W3 schools : https://www.w3schools.com/js/js_es6.asp

     

    JavaScript ES6

    W3Schools offers free online tutorials, references and exercises in all the major languages of the web. Covering popular subjects like HTML, CSS, JavaScript, Python, SQL, Java, and many, many more.

    www.w3schools.com

     

    B. (Book) Learning React (by Alex Banks, Eve Porcello)

     

    C. 자바스크립트 최신 문법 - 드림코딩 : https://youtu.be/36HrZHzPeuY

     

    D. https://262.ecma-international.org/6.0/

     

    ECMAScript 2015 Language Specification – ECMA-262 6th Edition

    5.1.1 Context-Free Grammars A context-free grammar consists of a number of productions. Each production has an abstract symbol called a nonterminal as its left-hand side, and a sequence of zero or more nonterminal and terminal symbols as its right-hand sid

    262.ecma-international.org

     

    E. https://github.com/tc39/proposals/blob/HEAD/finished-proposals.md

     

    GitHub - tc39/proposals: Tracking ECMAScript Proposals

    Tracking ECMAScript Proposals. Contribute to tc39/proposals development by creating an account on GitHub.

    github.com

     

    F. https://ko.javascript.info/arrow-functions

     

    화살표 함수 다시 살펴보기

     

    ko.javascript.info

     

    G. https://medium.com/korbit-engineering/let%EA%B3%BC-const%EB%8A%94-%ED%98%B8%EC%9D%B4%EC%8A%A4%ED%8C%85-%EB%90%A0%EA%B9%8C-72fcf2fac365

     

    let과 const는 호이스팅 될까?

    let 또는 const가 호이스팅 되는지 여부를 알아보기 전에 호이스팅과 Temporal Dead Zone(TDZ)이란 개념에 대해 알아보겠다.

    medium.com

     

    H. https://www.geeksforgeeks.org/arrow-functions-in-javascript/

     

    Arrow functions in JavaScript - GeeksforGeeks

    A Computer Science portal for geeks. It contains well written, well thought and well explained computer science and programming articles, quizzes and practice/competitive programming/company interview Questions.

    www.geeksforgeeks.org

     

    I. https://www.javascripttutorial.net/es-next/javascript-logical-assignment-operators/

     

    JavaScript Logical Assignment Operators

    In this tutorial, you'll learn about JavaScript logical assignment operators, including logical OR assignment operator (||=), logical AND assignment operator (&&=), and nullish assignment operator (??=).

    www.javascripttutorial.net

     

    J. https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference

     

    JavaScript 참고서 - JavaScript | MDN

    이 페이지는 JavaScript 언어에 대한 정보 보관소입니다. 이 참고서에 대해 더 읽어보세요.

    developer.mozilla.org

     

    댓글