본문 바로가기

FRONT-END/JAVASCRIPT

자바스크립트 표준 #2

반응형

템플릿 문자열 표현식

기존에 없엇던 완전히 새로운 기능 중 하나가 바로 템플릿 문자열 표현식이다.

기존의 작은 따옴표('), 큰따옴표(")로 여닫는 문자열 표현식이 아닌 역따옴표(`)로 여닫는 것을 '템플릿 문자열 표현식'이라고 한다.

기존에는 문자열 중간중간 다른 변수를 넣어야 할때 문자열을 합치는 등의 작업이 필요했지만, 템플릿 문자열을 이용하면 간단하게 하게 해결

<!DOCTYPE html>

<html lang="ko">

 <head>

   <meta charset="utf-8">

   <title>ES6 template-string</title>

 </head>

 <body>

   <label>Name: <input id="name"></label>

   <div id="greetings"></div>

 <script>

  (()=>{

      var inputName = document.getElementById("name"),

          divGreetings = document.getElementById("greetings");


      inputName.addEventListener("change", ()=>{

        divGreetings.innerHTML = `Hello, ${inputName.value.toUpperCase()}`;

      });

  })();

 </script>

 </body>

 </html>

change 이벤트 핸들러를 통해서 별도의 <div> 태그에 인사말을 보여준다.


역따옴표의 변수 설정은 ${} 안에 이루어지면 여기에는 일반변수뿐만 아니라 함수호출이나 계산식도 넣을 수 있다.

${inputName.value.toUpperCase()} 코드로 대문자로 변경한다.


템플릿 문자열의 특징은 동적 변수 문자열 할당뿐아니라 여러 줄 문자열을 쉽게 지원한다.

이전에는 문자열을 여러 줄로 출력할 때는 특수문자 "\n" 으로 줄의 끝을 표기해줘야 했지만, 템플릿 문자열 표현식을 이용하면 셀이나 PHP의  EOL키워드와 유사하개 개행을 보이는 대로 적용해준다.

<!DOCTYPE html>

<html lang="ko">

 <head>

   <meta charset="utf-8">

   <title>ES6 template-string</title>

 </head>

 <body>

   <label>Name: <input id="name"></label>

   <pre id="greetings"></pre>

   <pre id="greetingsES5"></pre>

 <script>

  (()=>{

      var inputName = document.getElementById("name"),

          preGreetings = document.getElementById("greetings"),

          preGreetingsES5 = document.getElementById("greetingsES5");


      inputName.addEventListener("change", ()=>{

        preGreetings.innerHTML = `Hello,

        ${inputName.value.toUpperCase()}\nWelcome to ES6`;


        preGreetingsES5.innerHTML = "Hello, \n" +

        inputName.value.toUpperCase() +

        "\nI'm using ES5";

      });



  })();

 </script>

 </body>

 </html>

자바스크립트가 서버에서 동작하여 템플릿을 제공해야 할 때 코드상에서 보이는 것과 유사하게 프로그래밍할수 있어서 개발자에게 편리하다.

템플릿 문자열 표현식의 또  다른 특징은 전체 문자열을 분석하거나 입력된 값에 따라서 다른 문자열이나 현재 문자열을 변경해서 처리할 수도 있다.

    function analyze(strings, ...values){

      console.log(strings);

      console.log(values);

      return "Conditional string";

    }


    var name ="World";

    console.log(analyze `Hello, ${name}!\nECMAScript${2*2+2} is easy`);

함수뒤에 괄호를 열지 않고 바로 템플릿 문자열 표현식을 사용하면 함수에 첫번째 인자로는 변수값을 기준으로 나눈 문자열들이 배열로 넘긴다.

그리고 각 변수들이 이후의 인자들로 넘긴다.

여기서 인자의 확장 기능을 통해 values라는 하나의 배열로 나머지 실제 값들을 받는다.

그리고 해당 함수의 반환값이 최종적으로 표시되는 문자열이므로 조건부로 다른 문자열을 보여주거나 특수문자 변환 등의 전처리가 필요하다면 이러한 함수 호출을 통해서 변경하면된다.

이처럼 템플릿 문자열에 대한 정보를 함수로 넘겨줄때 괄호를 둘러싸면 템플릿 문자열이 먼저 처리되어 일반 문자열로 인자가 넘어가므로 이러한 함수를 활용할 때는 주의하면된다.


첫번째 인자인 string의 속성으로 raw라는 배열이 인자로 넘어오는데, 이 인자는 각 문자열을 실제로 어떻게 가지고 있는지 보여주는 순수 문자열이다.

analyze() 함수 안에서 console.log(strings.raw[1] === "!\nECMAScript"); // true

이러한 순수 문자열을 표현하고자 String.raw라는 새로운 함수로 추가되어 활용할 수 있다.

String.raw `Hello, ${name}!\nECMAScript${2 * 2 + 2} is easy`

순수한 문자열로 전체 문자열을 변경할수 도 있다.

이렇게 순수 문자열을 추력하는 것은 서버에서 템플릿 문자열을 가지고 웹페이지를 그릴 때 매우 유용하게 사용할 수 있다.


Destructuring


Destructuring은 기존 구조를 가지고 있던 객체를 분석하여 역으로 하나하나의 변수에 할당하는 것을 의미한다.

Destructuring의 특징은 JSON 규격의 객체나 배열은 쉽게 매핑하여 변수를 할당할 수 있고, 특히 함수의 반환값을 여러 개로 할 수 있다.

    var myArray = [1, 2, 3, 4, 5];

    var[a , b ,  , c , d] = myArray;


    console.log("Array shorthand: " + a + " , " + b + " , " + c + " , " + d);


    [a , b] = [b , a];

    console.log("Swap: " + a + " , " + b);


    var myObject = {

      name: "Unikys",

      gender: "Male",

      job: "Programmer"

    };


    var {name, gender, job} = myObject;


    console.log("Object shorthand: " + name + " , " + gender + " , " + job);


    var vehicles = {

      fourWheeles: {

        cars: [

          "SUV",

          "Sedan"

        ],

        trucks: [

          "Pullover",

          "Wagen"

        ]

      },

      twoWheels: [

        "bicycle",

        "motorcycle"

      ]

    };


    var {fourWheeles, twoWheels, fourWheeles: {cars, trucks}} = vehicles;

    console.log("Deep object: " + fourWheeles + ", "+  twoWheels + ", " + cars + ", " + trucks);

각각의 Destructuring 활용 예를 보면 첫번째로 특정 배열을 변수에 입력하고자 한다면 var  이후에 변수들을 배열 표현식으로 동일하게 배치하여 넣으면된다.

= 연산자 왼쪽에 이러한 표현식이 들어갈 수 있게 됨에 따라 swap 기능도 배열을 이용해서 쉽게 구현할 수 있다.

배열이 아닌 객체도 왼쪽에 객체 표현식으로 할당할 수 있다.

그리고 여러 단계를 가지는 객체도 같은 구조로 객체의 속성의 속성등을 할당하여 변수에 넣을 수 도 있다.


이처럼 객체나 배열을 통해서 여러 변수에 입력할수 있게 되면서 간혹 배열이나 객체가 잘못될 때 에러를 방지하기 위한 기본 값을 설정할 수 있다.

    var myArray2 = [1 , 2];


    var [a=10, b=9, c=8, d=7] = myArray2;


    console.log(a + " , " + b + " , " + c + " , " + d);  //1 , 2 , 8 , 7


이처럼 왼쪽의 표현식에 = 연산자와 함께 값을 적을 때, 오른쪽의 객체나 배열에 값이 없으면 해당 기본값으로 설정되고, 값이 있으면 객체나 배열의 값이 설정된다.

a, b는 배열의 값이 설정되고 c, d는 기본값으로 설정되는것을 확인할 수 있다.


Desstructuring은 변수 선언에서만 활용되는것이 아니라 함수의 인자로 객체를 넘겨줄 때도 정의하여 쉽게 변수를 할당할 수 있다.

    function destructArray([first, second]){

      console.log("Inside destructArray function: " + first + " , " + second);

      return [first + second, first - second, first * second, first / second];

    }


    function destructObject({name, gender}){

      console.log("Inside destructObject function: " + name + " , " + gender);

      return {

          greetings: "Hello, " + name,

          sayHello() {

            console.log("sayHello function : Hello " + name);

          }

      };

    }


    var [sum, sub, mul, div] = destructArray([1 , 2]);

    console.log(`Return value of destructArray: ${sum}, ${sub}, ${mul}, ${div}`);


    var {greetings, sayHello} = destructObject({name: "Unikys", gender:"Male"});

    sayHello();

함수에서  객체나 배열로 반환할 때 이를 Destructuring을 통해 여러 변수로 받을 수 있다는 점이다.

기존에는 이렇게 객체나 배열로 여러 값을 반환할 때 추가적인 처리가 필요했지만, ES6에서는 Destructuring을 통하여 한 줄로 모든 값 입력을 완료할 수 있어서 매우 편리해졌다.

특히 특정값과 함수를 많이 반환하는 자바스크립트에서 위의 destructuringObject의 반환값을 처리하는 부분처럼 함수를 반환하더라도 클로저를 동일하게 사용할 수 있다는 특징이다.


함수 인자 기능 확대

자바스크립트에서 함수의 중요성은 매우 크기 때문에 이러한 함수에 대한 지원 범위를 ES6에서는 크게 확대하고 있다.

기존 에는 함수가 호출되고 나면 arguments를 통해서 동적으로 인자를 받던 부분이나 apply를 통해서 동적으로 인자를 넘겨주던 부분에 대해서 표준을 정의하여 가변 인자에 대한 기능을 추가하였다.

가변인자는 C에서도 사용하고 있는 "..." 키워드가 추가되었다.

    function dynamicArguments(arg1, ...rest){

      console.log(arg1);

      console.log(rest);

    }


    dynamicArguments(1,2,3,4);


    var spread = ["P","R","E","A"];


    dynamicArguments("S", ...spread, "D");


    function previousDynamicArguments(arg1){

      var rest = Array.prototype.slice.call(arguments, 1);

      console.log(arg1);

      console.log(rest);

    }

    previousDynamicArguments(1,2,3,4);

    previousDynamicArguments.apply(this, ["S"].concat(spread).concat(["D"]));

인자를 받을 때 "..."키워드를 사용하면 그 이후의 모든 인자는 배열에 들어가게 되어 이들을 배열로 받는 것처럼 사용할 수 있다.

ES6 이전에는 이러한 기능이 없어서 arguments를 사용해야했고, "..."처럼첫번째 인자 이후를 별도의 배열로 저장하려면 Array.prototype.slice() 함수로 잘라야 했다.

반대로 함수를 호출할 때 "..." 키워드를 사용하면 해당 배열을 펼쳐서 여러 개의 인자를 넘겨주는 것이라고 생각하면된다.

이는 인자를 동적으로 다르게 넘길 때 유용하게 활용할 수 있다.

ES6 이전에는 함수를 호출하기 위하여 apply를 사용해서 전체 인자에 대한 배열을 넘겨야 했다.

그러나 "..."키워드를 사용하면 중간에 필요한 부분만 배열로 동적으로 인자를 넘겨줄 수 있다.


이러한 동적인 인자 관리 이외에도 인자가 넘어오지 않을 때 기본값을 설정할 수 있다.

이는 다른 프로그래밍 언어에서도 가능했지만, 자바스크립트에서는 항상 기본값이 undefined였던 것에서 확장할 수 있게 되었다.

    function defaultValue(color="black", isNull="Nullable"){

      console.log("color=" + color + ", isNull="+ isNull);

    }


    defaultValue(undefined, null); //color=black, isNull=null


    function previousDefaultValue(color, isNull){

      color = color || "black";

      isNull = isNull || "Nullable";

      console.log("color=" + color + ", isNull="+ isNull);

    }


    previousDefaultValue(undefined, null); //color=black, isNull=Nullable

인자 뒤에"=기본값"을 정의하면 인자가 undefined로 들어올때 해당 인자를 기본값으로 설정할 수 있다.

이전의 자바스크립트에서 흔히 사용했던 기본값 설정은 다음처럼 colo= color || "black" 으로 많이 코딩했다.

이러한 코드와 가장 큰 차이점이 있다면 이전의 ||를 통한 기본값 할당은 undefined뿐아니라 0, null, false, ""등의 값을 모두 처리하지만, ES6에서는 undefined만 처리한다는 점이니 주의해야한다.

기본값에 null은 기도래 인자를 받고 있으므로 변수를 null로 초기화할 때는 확인이 필요할 수도 있다.



iterator와 for-of 기능

C++와 JAVA에서 배열이나 연결리스트와 같은 데이터 구조를 순환할 때 가장 많이 사용하는 것이 iterator이다.

이러한 iterator는 특정 데이터 집합을 순서대로 접근하려고 할때 사용된다.

기존 자바스크립트에서는 속성을 순환할 때는 단순히 for 루프나 for-in을 사용할수 밖에 없었지만, 조금 더 다양한 순환 기능을 제공할 수 있도록 iterator 기능과 이를 순환하기 위한 for-of 키워드가 정의되었다.


for-of 키워드를 사용하면 iterator를 반환하는 함수를 호출한다.

이 함수는 for-of의 뒤에 오는 객체의 [Symbol.iterator]를 호출하며, iterator는 next()함수를 포함하고 있어야 한다.

그리고  next() 함수를 호출하면 내부적으로 제한적인 또는 무한의 순환을 돌며, 그 반환 값으로 value 속성과 done 속성을 가지고 있는 객체를 반환하면 된다.

반환되는 객체의 value 속성은 현재 iterator가 가리키는 곳의 값을 가지고 있고, done 속성은  iterator가 끝까지 도달했는지를 구분해주는 true/false 값을 가진다.


이러한 for-of를 지원하기 위하여 기존의 기본 객체들도 Symbol.iterator 속성을 제공하고 있는데, 대표적인 것이 배열과 문자열이다.

iterator를 사용하면 배열은 순서대로 접근하고, 문자열은 각 문자를 하나씩 접근한다. 그리고 사용자 객체에 사용하고 싶다면 위의 Symbol.iterator 속성에 iterator에 맞는 규격으로 객체를 반환하도록 만들면 된다.

    var fibonacci = {

      maxStep: 20,

      [Symbol.iterator](){

        var previous = 0, current = 1, step = 0, maxStep = this.maxStep;

        return {

          next(){

            [previous, current] = [current, previous + current];

            return {

              done: step++ >= maxStep, value: current

            };

          }

        };

      }

    };


    for(var fibonacciNumber of fibonacci){

      console.log(fibonacciNumber);

    }

피보나치 수열을 20단계까지 iterator를 이용해서 출력하는 예이다.

여기에서 fibonacci객체가 for-of 키워드를 이용할 것이라 Symbol.iterator를 속성으로 하는 함수를 정의하여 Symbol.iterator 함수는 next() 함수를 속성으로 가지는 객체를 반환한다.

그리고 next() 함수는 done과 value를 속성으로 가지는 객체를 반환한다.

for-of는 done이 true가 될때까지 루프를 돌며, done이 true가 되는 시점에는 루프를 빠져나온다.

사용자 정의 iterator를 이용할 때는 next()함수가 반환하는 객체의 done과 value를 순서에 맞게 반환하면된다.


위의경우에는 iterator를 처리하기 위한 규격으로 next() 함수와 반환객체를 맞춰야하는 번거러움이 있다.

이러한  iterator 규격을 간단하게 처리하기 위한 generator 기능이 표준으로 정의되었다.

이 generator를 이용하려면 새로운 키워드인 function*과 yield를 사용해야한다.

function*은 generator 함수라고 하며 iterator를 반환하는 함수이다.

yield는 return과 유사한 키워드로 return은 함수를 완전히 끝내지만, yield는 함수를 잠시 멈췄다가 다음에 호출되었을 때 다시 그 이후부터 이어서 시작한다.

    var fibonacciF = {

      maxStep: 20,

      *[Symbol.iterator](){

        var previous = 0, current = 1, step = 0, maxStep = this.maxStep;

        while(step++ < maxStep){

          [previous, current] = [current, previous + current];

          yield current;

        }

      }

    };


    for(var fibonacciNumberF of fibonacciF){

      console.log(fibonacciNumberF);

    }

사용자 정의 iterator 함수와 같은 결과를 나타낸다.

앞서  next()함수와 반환객체를 직접 작성하는 것보다 소스가 아주 간단해졌다.

먼저 피보나치 수열을 생성하는 로직은 같고, 기존에 done으로 종료 조건을 판단해서 객체로 반환했다면 generator함수는 전체 함수가 종료되면 iterator도 종료되며, yield current를 통해서 값이 전달된다.

yield current가 호출되면 generator 함수는 잠시 멈추었다가, 다음 for-of 루프를 돌 때 이전에 멈추었던 yield 키워드 이후에 이어서 실행된다.


두 예는 객체를 순환하기 위하여 Symbol.iterator 속성으로 정의된 함수를 사용하고 있지만, generator 함수는 직접 iterator 객체를 반환하여 사용할 수 도 있다.

    var iterFibonacci = function*(maxStep){

      var previous =0, current =1, step =0;

      while(step++ < maxStep){

        [previous, current] = [current, previous + current];

        yield current;

      }

    };


    for(var iterFibonacciNumber of iterFibonacci(20)){

      console.log(iterFibonacciNumber);

    }

generator 함수를 변수에 선언해서 사용하더라도 직접 iterator처럼 사용할 수 있고, 이전의 객체를 순환하는 경우와 달리 인자를 넘겨줄수 있다는 점이 다르다.

따라서 이렇게 generator 함수를 사용하면 조금 더 유연한 iterator 기능을 만들어서 사용할 수 있다.

또한 generator 함수 자체를 배열과 유사하게 취급할 수 있어서 앞서 살펴본 함수 인자 기능과 Destructuring을 활용하여 전체의 값을 가져올 수도 있다.

    var fibonacciResult = [...iterFibonacci(20)];

    var [n1, n2, n3, n4, n5, ...rest] = iterFibonacci(20);


    console.log(fibonacciResult);

    console.log([n1, n2, n3, n4, n5, ...rest]);

첫번째 경우처럼 배열을 나열하는 용도로 "..."을 사용하면 이는 배열을 순서대로 나열하기보다는 순서대로 순환한 결과를 배열로 만든다고 생각하는것이 정확하다.

    var myObject = {};

    myObject[Symbol.iterator] = function* (){

      yield "1";

      yield* [2,3];

      yield* "45";

      yield* innerGenerator();

      yield "9";

    };


    function* innerGenerator(){

      yield 6;

      yield* new Set(["7","8"]);

    }


    console.log([...myObject]);

yield 키워드로 값을 반환하는 경우에는 단일 값이 전달되며, yield* 키워드로 값을 반환하면 해당 값이 다시 하나의 iterator로 취급되어 내부 루프를 처리하듯이 해당 iterator도 순서대로 처리하게 된다.

따라서 [2,3]의 배열이나 "45"의 문자열, 그리고 새로운 iterator 객체는 각각 다시 iterator로 동작하여 값을 순서대로 반환한다.


이처럼 iterator 함수를 이용해서 단순하게 iterator 기능만 수행하는 것뿐만 아니라, function*과 yield 기능을 이용하여 차례로 처리해야하는 내용을 브라우저의 단일 스프레드를 잡아먹으면서 처리하지 않는 흐름 제어도 할 수 있다.

예를 들면 XMLHttpRequest를 통해서 순서 보장이 되어야할 때 functioni*과 yield를 사용한다면 쉽게 순서 보장을 제공할 수 있다.

    function getDOM(){

      var xhr = new XMLHttpRequest();

      xhr.open("/fetchDOM");

      xhr.onload = function () {

        xhrRequests.next(xhr.responseText);

      }

      xhr.send();

    }


    function getData(){

      var xhr = new XMLHttpRequest();

      xhr.open("/fetchData");

      xhr.onload = function () {

        xhrRequests.next(xhr.responseText);

      }

      xhr.send();

    }


    function mainpulateDOM(dom, data){

      // Fix DOM

    }


    var xhrRequests = function* () {

      var dom, data;

      dom = yield getDOM();

      data = yield getData();

      yield mainpulateDOM(dom, data);

    };

getDOM, getData, manipulateDOM을 순서대로 호출하고자 할때, 흐름을 제어할 수 있는 한 가지 예를 보여주고있다.

xhrRequests 변수는 iterator 객체로 전체흐름을 제어하고, 각 함수가 실제 처리하는 역할을 담당한다.

 xhrRequests 함수안에서는 각 순서 보장이 필요한 XMLHttpRequest들을 호출하고, 각 함수에서는 비동기 요청이 완료되면, onload 이벤트 핸들러에서 다음 단계를 실행할 수 있는 next()함수를 호출한다.

 여기서 yield 키워드에 도달하면 generator 함수 안에서 잠시 멈췄다가 호출된 함수 안에서 xhrRequests.next(xhr.responseText)가 호출되면 yield의 결과값으로 xhr.responseText를 받아서 각 변수에 값을 넣고 다음 단계로 넘어가면서 동작한다.


이처럼 전체 흐름은 xhrRequests 객체가 프락시 패턴과 유사하게 동작하여 흐름을 제어하고 필요에 따라서 요청을 변경할 수 있다.

또한 실제로 데이터를 요청하는 부분은 각 함수로 구분 지어서 모듈화 할수 있다.

각각의 XMLHttpRequest가 순서대로 실행되어야 한다면 위와 같이 흐름 제어용으로  generator 함수를 활용할 수도 있다.


Map과 Set 기능 추가

기존에 다른 프로그래밍 언어에서 많이 사용하는 데이터 구조도 추가되었다.,

이미 자바스크립트에서는 객체와 배열 자체가 해당 기능을 수행하고 있지만, 입력 키의 목록과 입력값의 목록이나 특정 값이 있는지 찾아내는 데는 별도의 처리 과정이 필요했다.

그러나 Map과 Set은 그러한 함수들을 기본으로 제공한다.

  var map = new Map(),

      key = {name: "Unikys"};

  map.set("Hello", "World").set("ECMAScript", 6).set(key, "javascript");


  for(let [key, val] of map.entries()){

    console.log(key + " = " + val);

  }


  var set = new Set();

  set.add("Hello").add("World").add("ECMAScript").add(6);


  console.log(set.has("ECMAScript"));

  for (let key of set.values()) {

    console.log(key);

  }

Map과 Set과 비교해서 다른 곳에 변수에 대한 메모리 참조가 없어진다면 저절로  Map과 Set에 있는 데이터를 삭제하는 WeakMap과 WeakSet이 표준으로 정의되었다.

WeakMap과 WeakSet에 대하여 참조가 없어지는 경우

  var weakSet = new WeakSet(),

      weakMap = new WeakMap(),

      obj = {

        name: "Unikys"

      },

      val = {

        language: "javascript"

      };


  weakSet.add(obj);

  weakMap.set(obj, val);


  console.log(weakSet.has(obj));

  console.log(weakMap.has(obj));


  obj = null;


  console.log(weakSet.has(obj));

  console.log(weakMap.has(obj));

WeakMap의 키 값으로 사용한 객체나 WeakSet에 추가하였던 객체는 더는 사용되지 않는다면 WeakMap과 WeakSet에 있었던 데이터도 함께 삭제된다.

자바스크립트의 메모리 관리 정책을 메모리에 있는 객체를 참조하는 레퍼런스가 없어야 객체를 가비지 컬렉터에 해제하는데, 객체가 여기저기 많이 참조되어 사용되고 있다면 이러한 메모리 해제가 어렵다.

그런데 WeakMap과 WeakSet은 이러한 레퍼런스에 추가되지 않아 메모리 해제를 방해하지 않고, 메모리가 해제되는 경우에는 해당 데이터 구조에서도 함께 삭제된다.

따라서 메모리 누수에 최적화된 데이터 구조라고 볼수 있다. 이는 서버에서 사용자의 데이터를 관리 할때 메모리 누수를 걱정하지 않아도 되므로 유용하게 활용할 수 있다.


Binary / Octal 표현식 추가

ES6에서는 2진수와 8진수에 대한 표현식이 추가되었다.

기존에 16진수 표현은 기본으로 있었으나, 2진수 8진수는 없어서 ES6에 추가되었다.

  console.log(0b1001101010 === 618);  //binary

  console.log(0o1152 === 618);  //octal

  console.log(0x26A === 618);  //hex



  console.log(parseInt("1001101010", 2) === 618);  //binary

  console.log(parseInt("1152", 8) === 618);  //octal

  console.log(parseInt("26A", 16) === 618);  //hex

기존에는 숫자를 문자열로 변환한 다음 parseInt() 함수를 통해 변환

ES6에서는 표현식을 통해서 직접 숫자로 표시

2진수를 통한 플래그 처리 소스에서 2진수 표현식을 사용한다면 코드의 이해도를 높일 수 있다.


TypedArray 기능 추가

기존의 자바스크립트에서는 객체 기반의 배열만 가능했다면 ES6에서는 타입 기반의 배열을 정의하는 것도 가능하다.

  var buffer = new ArrayBuffer(8);

  var byteArray = new Unit8Array(buffer);


배열  버퍼를 만든 다음 원하는 형태 배열을 할당해주면 해당 크기로 메모리에 할당되어 배열이 생성된다.

ES6에서 정의하고 있는 typedArray의 종류


기존에 변수타입이 모호했던 자바스크립트에서 배열을 사용하는것과 다르게 하나의 인덱스마다 크기를 명시할 수 있게 되었다.

이에 따라 다른 것보다 바이트 단위의 데이터를 주고받을 때 더욱 효율적으로 처리할 수 있게 되었다.

특히 TCP 프로토콜을 사용하는 다른 서버와 통신하기에 매우 좋아졌다고 볼수 있다.

특히 C로 만들어진 서버의 구조체와 통신하는데 매우 적합해졌다고 볼수 있다.

struct employer{

unsigned int id;

char name[16];

float man_moth;

}



class Employer {

  constructor(arrayBuffer = new ArrayBuffer(24)){

    this._arrayBuffer= arrayBuffer;

    this._id = new Unit32Array(arrayBuffer, 0, 1);

    this._name = enw Unit8Array(arrayBuffer, 4, 16);

    this._manMonth = new Float32Array(arrayBuffer, 20, 1);

  }

  set id(id){

    this._id = id;

    this._arrayBuffer.set(id);

  }


  get id() {

    return this._id[0];

  }


  set name(name) {

    this._name = name;    

  }


  get name(){

    return this._name[0];

  }

  set manMonth(manMonth){

    this._manMonth[0] = manMonth;

  }


  get ManMonth(){

    return this._manMonth[0];

  }


  get toByte(){

    return this._arrayBuffer;

  }

}



클래스는 생성자로 입력 버퍼를 받아서 각 속성을 설정한다.

TypedArray의 생성자는 크기, 배열 객체 또는 ArrayBuffer를 인자로 받으며, ArrayBuffer를 인자로 받을 때 두번째 인자는 시작 시점, 세번째 인자는 길이를 입력받는다.

따라서 Unit32Array는 0번째부터 1만큼, Unit8Array는 char 배열로 4번째부터 16만큼의 길이를 가져와서 변수로 설정한다ㅣ

이처럼 다른 서버와의 연동 규격을 정의할 때 자바스크립트 서버에서 바이트 단위로 정의하고 처리하기에 편리해졌다고 볼수 있다.


모듈기능 표준화

모듈은 Node.js와 CommonJs로부터 활성화하기 시작하여 ES6에서는 표준으로 정의되었다.

자바스크립트를 이용하는 웹페이지나 서버의 코드들이 복잡해지기 시작하면서 효율적인 소스 관리가 필요해져서 이제는 모듈을 정의하는 것 또한 표준으로 정의된 것이다.

모듈은 크게 export와 import로 나누어서 볼수 있으며 export하는 모듈 자바스크립트 파일과 이 모듈 자바스크립트 파일을 사용하는 쪽에서는 import 하면된다.


module.js

export function getJSON(url, callback) {

  var xhr = new XMLHttpRequest();

  xhr.open("GET", url, true);

  xhr.onload = function() {

    callback(JSON.parse(this.responseText));

  };

  xhr.send();

}


export default function (url, callback) {

  var xhr = new XMLHttpRequest();

  xhr.open("GET", url, true);

  xhr.onload = function (){

    callback(this.responseText);

  };

  xhr.send();

}



app.js

import getURL, * from "module.js"

getJSON("/getJSON", function (json) {

  console.log(json);

});


getURL("/getURL", function (data) {

  console.log(data);

});

module.js에서는 getJSON 함수와 default 함수를 정의하고 있으며, app.js에서는 이를 import 하는데, 모든 내용을 가져오면서 default 함수는 getURL로 정의하고 있다.

이것을 app.js에서는 해당 기능을 그대로 사용할 수 있다.

브라우저에서는 아직 모듈 문법이 제대로 지원되지는 않고 있으나, 미래의 브라우저들에서는 <script type="module">이나 <module> 태그를 이용하여 모듈 문법을 이용할 수 있게 될 것이다.


프락시 모듈

ES6에서 표준으로 프락시 모듈이 정의되어 내장 API로 제공된다.

  var target = {

    unikys : "Hello, Unikys"

  };


  var proxy = new Proxy(target,{

    get (target, name){

      return name in target ? target[name] : `Nice to meet you, ${name}`

    },

    set (target, name, value){

      if(name === "age"){

          if(typeof value === "number"){

            target[name] = value;

          }else{

            console.log("Wrong type, must be number");

          }

      }

    }

  });


  console.log(proxy.unikys);

  console.log(proxy.stranger);

  proxy.age = "4";


특정 객체에 대해서 프락시 API 를 사용하면 모든 get이나 set에 일어날 때 중간에 관련 정보를 가지고 프락시 객체에 검증이나 예외처리 또는 다른 부가적인 처리를 하도록 할 수 있다.

프락시 객체를 사용하면 생성자를 확장하거나 배열 검색을 문자열이나 다른 객체로 하도록 수정할 수 있다.


Symbol 모듈

앞서 iterator에 대해서 살펴봤을 때 나왔던 새로운 내장 모듈이 바로  Symbol.iterator였다.

이 Symbol 모듈은 기존의 속성을 정의할 때 사용할 수 있는 새로운 기본형을 만들어 주며, Symbol.iterator는 그러한 표준 속성으로 정의되어 있는 것이다.

사용자 정의 Symbol 또한 추가할 수 있는데, Symbol을 생성할 때 마다 서로 다른 Symbol로 취급하여 속성으로 설정하면 결과적으로 다른 속성이 된다.

  var myObject = {

    [Symbol()] : "First symbol",

    [Symbol()] : "Second symbol"

  };


  console.log(myObject);

  const key = Symbol();

  console.log("typeof key : " + typeof key);


  myObject[key] = "New symbol";


  console.log(Object.keys(myObject));

  console.log(Object.getOwnPropertyNames(myObject));

  console.log(Object.getOwnPropertySymbols(myObject));

Symbol은 새로운 기본형을 가지게 되어 typeof의 결과로 symbol을 반환한다.

그리고 Symbol을 이용해서 속성을 설정할 때 for-in이나 키의 목록을 가져오는 것으로 접근할 수 없고, Object.getOwnPropertySymbols() 함수를 통해서 Symbol에 접근할 수 있다.

이로 인하여 간단하게 private과 비슷한 용도로 사용할 수 있으나 실제로는 외부에서 접근할 수 있기에 때문에 큰 의미가 없다.


Symbol은 매번 다르게 호출되며 생성자 문자열을 넘겨줄 수 있지만, Symbol 생성에는 영향을 미치지 않고 디버깅 용도로만 사용된다.

Symbol을 글로벌로 사용하고 싶다면 Symbol.for()함수를 사용하면된다.

  console.log(Symbol("unikys") === Symbol("unikys"));    // false

  console.log(Symbol.for("unikys") === Symbol.for("unikys"));  //true


Promise 모듈

Promise는 특정 함수 호출이나 연산이 비동기로 이루어져서 앞으로 완료되었을 때, 이후에 처리할 함수나 에러를 처리하기 위한 함수를 설정하는 모듈이다.

  function sendMsg(msg, timeout){

    return new Promise((resolve, reject)=>{

      setTimeout((()=>{

        resolve(msg);

      }), timeout);

    });

  }


  sendMsg("Hello, ", 1000).then((msg)=>

    sendMsg(`${msg} World`, 1000)

  ).then((msg)=>{

    console.log(`${msg}! All done`);

  });




비동기인 상황을 만들고자 setTimeout을 사용하였지만, 이것은 XMLHttpRequest를 차례로 처리해야 하거나 그 직후에 다른 처리를 해야 할 때 이용할 수 있는 모듈이다.

Promise 모듈은 특히 반환값 Chaining을 통해 순서대로 처리해야하는 함수들을 쭉 나열해서 호출할 수 있어서 순차 보장을 위한 프로그래밍을 최소화할 수 있다.

자바스크립트에서 비동기 처리가 가장 많이 일어나는 XMLHttpRequest 처리에서 유용하게 사용할 수 있다.

  var promise = new Promise(function (resolve, reject) {

    var xhr = new XMLHttpRequest();

    xhr.open("GET","/longLoad");

    xhr.onload = function () {

      if(this.status >= 200 && this.status < 300) {

        resolve(this.responseText);

      }else {

        reject(this.statusText);

      }

    };

    xhr.onerror = function () {

      reject(this.statusText);

    }

    xhr.send();


  });


  promise.then(function (responseText) {

    console.log(responseText);

  }).catch(function (statusText) {

    alert("Error! " + statusText);

  });





ECMAScript 2016

ECMAScript 7에 추가된 기능


Array.prototype.includes 함수

ES6까지는 배열에 특정한 요소가 있는지 검색하기 위한 별도의 함수가 없어서 indexOf를 사용한다.

그리고 indexOf를 호출한 결과를 토대로 해당 배열에 있는지 없는지도 구분하는데, 이것이 함수를 호출하는 것만 보면 그대로 그 의미를 뜻하는 것인지 불명확하다는 판단이 있어서 배열중에 특정요소가 있는지 검사하는 함수가 표준에 추가되었다.

  // ES7 이상

  var array = ["a","b", 1, 2, NaN];

  console.log(array.includes("a"));

  console.log(array.includes(2));

  console.log(array.includes(NaN));


  //ES6 이하

  console.log(array.indexOf("a") >= 0);

  console.log(array.indexOf(2) !== -1);

  console.log(array.indexOf(NaN) !== -1);

  var includesNaN = false;

  for (var i = 0; i < array.length; i++) {

    if(isNaN(array[i])){

      includesNaN = true;

    }

  }

  console.log(includesNaN);

이전에는 indexOf를 한 다음 다시 한번 비교해야 했지만, 이제는 하나의 함수인 includes()를 통해서 해결할 수 있다.

그리고 배열중에 NaN이 있는지 검사하려면 이전에는 indexOf() 함수를 사용할 수 없어서 일일이 비교해야 했지만 이제는 includes()함수를 통해서 NaN이 있는지 쉽게 검사할 수 있다.


이러한 includes() 함수명은 다른 언어와 유사하게 contains나 has 등으로 사용되고 있지만, contains를 사용하고 있는 웹 프레임워크가 있기 때문에 이러한 웹페이지들이 내부적인 충돌이 일어나지 않게 하려고 표준에서 includes()로 함수명을 변경한것이 독특한 점이다.


지수연산자

지수연산자는 이미 자바스크립트에서 제공하고 있지만 조금 더 편한 방법으로 사용할수 있게 연산자가 추가되었다.

지수연산자는 곱 연산자 두개를 이어서 **처럼 사용하면된다.

  var squareRootOfTwo = 2 ** 0.5;

  var cubeTwo = 2 ** 3;

  console.log(squareRootOfTwo);  //1.4142135623730951

  console.log(cubeTwo);  //8


  var squareRootOfTwoES6 = Math.pow(2, 0.5);

  var cubeTwoES6 = Math.pow(2, 3);

  console.log(squareRootOfTwoES6); //1.4142135623730951

  console.log(cubeTwoES6);  //8

기존에는 지수 연산을 하려면 Math.pow() 함수를 직접사용해야 했지만, ES7부터는 ** 연산자를 이용하면 된다.

C나 자바에서는 지수 연산자가 ^ 연산자라서 조금 어색하게 느껴질수 있지만, 자바스크립트에서 ^연산자는 이미 XOR로 사용되고 있고, 파이썬, 루비등과 같은 다양한 스크립트 언어들은 지수연산자를 **로 사용하고 있기 때문에

같은 스크립트 언어인 자바스크립트에서도 이들과 같은 문법을 채택하였다고 볼수 있다.


출처-속깊은 자바스크립트 양성익 지음

반응형