본문 바로가기

프로그래밍(TA, AA)/자바스크립트

[자바스크립트] ES2015(ES6) 알아보기

ES6과 ES205는 같은 의미를 가집니다. ES6 이후에도 ES2016, ES2017으로 자바스크립트 문법은 계속 발전해 나가고 있습니다. 오늘 포스팅은 ES6에 대해서 다뤄볼 것입니다. 우선 ES2015는 개선된 JavaScript문법입니다. ES6는 주목할만한 변화는 개선된 JavaScript문법을 사용한다는 점입니다. 새로운 Syntax들이 지원되어 개발자들이 코드량, 생산성을 늘릴 수 있게 되었습니다. ES6는 현재 해외에서 거의 표준처럼 사용되고 있으며, 브라우저 호환성이 훌륭하며, ES6를 기반으로 한 JavaScript 생태계가 확산되고 있습니다. 


ES6 브라우저 호환성은 https://kangax.github.io/compat-table/es6/ 에서 살펴볼 수 있습니다.


ES6 코드는 Babel을 이용하여, ES5 코드로 변환하는 것이 가능합니다. ES5의 경우 지원 브라우저의 폭이 더 넓기때문에 Babel을 이용하여 브라우저 호환성을 확보할 수도 있습니다. 



Scope 활용 전략


ES6 이전에는 아래와 같이 function 단위의 scope만 존재했습니다. 따라서, function 안에 있는 지역변수를 먼저 찾고 이후에 scope chain을 따라 전역변수를 찾는 방식이었습니다.


var name = "global var";

function home() {
  var homevar = "homevar";
  for(var i=0; i<100;i++) {
  }
  console.log(i);
}

home();


새로운 키워드 let을 사용해보겠습니다.


var name = "global var";

function home() {
  var homevar = "homevar";
  for(let i=0; i<100;i++) {
    console.log(i);
  }
  console.log(i);
  
  if(true) {
    let myif = "myif";
  }
  
  console.log(myif);
}

home();


let은 for문 안에서만 유효한 값입니다. 따라서 for문 바깥에서 i의 값을 찍은 경우에는 오류가 나고 for문안에서 i값을 찍은 결과만 유효하게 나타납니다. 이 규칙은 if문에도 해당됩니다. 결국 let은 블록 단위의 scope만 지원하는 타입형입니다.


자바스크립트를 개발하다보면 closure라는 스코프라는 개념에 대해 알게됩니다. closure 스코프가 생기면 예상치 않은 동작이 생길수 있는데, closure 상황에서 let이 어떻게 동작하는지 살펴보겠습니다.


보통 event delegation를 이용하여 이벤트 버블링 기법을 사용하여 이벤트를 적용합니다. event delegation을 적용하는 상황은 보통 두가지입니다. 동일 element 유형들에 대해 같은 event handler를 등록해야 할때, <div id="tour"></div> 아래 <button></button>이 100개 있다고 하면, 100개의 eventhandler를 등록해야 합니다. 이는 곧 성능에 악영향을 주고, 소스도 길어지게 됩니다. callback 함수, timer에 의해 element가 생길 경우, callback 함수 실행 후 또는 특정 시간 후 element가 생기기때문에 element 생기기 전 직접 event handler를 등록할 수 없습니다. delegation의 경우 상위 element에 event handler를 등록하기 때문에, delegation을 통해서 가능하게 됩니다.


하지만 지금은 ES6 문법을 통해 리스트에 대한 이벤트를 적용해보겠습니다. 아래는 일반적인 클로져 scope문제를 해결합니다. 클로져의 개념은 다소 어려운 부분이므로 추후에 따로 포스팅으로 정리하겠습니다.


var list = document.querySelectorAll("li");
for(let i=0; i<list.length; i++) {
  list[i].addEventListener("click", function(){
    console.log(i + "번째 리스트 입니다");
  });
}


ES6는 또한 const 타입을 제공합니다. 과거 javascript에서는 const 타입을 지원하지않아 변수명을 대문자 HOME_NAME등으로 선언하여 해결하기도 하였지만, 이는 근본적인 해결책은 아니었습니다. 아래는 const 타입에 대한 예제로 const 타입을 이용하면 변수에 대한 새로운 assignment가 불가능 합니다. 


function home() {
  const homename = 'my house';
  // homename = "your house";
  console.log(homename);
  
  const homearray = [1,2,3,4];
  // homearray = ["1", "2"];
  console.log(homearray);
  
  /*
   하지만 const와 let을 동시에 사용하는 것은 매우 좋지 못한 방법입니다.
   두가지 모두를 사용할 수 있는 경우라면, const를 사용하는 것을 권장합니다.
   만약 변경이 될 수 있는 변수라면, let을 사용합니다.
   
   var는 사용하지 않는 것이 일종 scope 전략입니다.
  */
}

home();


상황에 따라 let, const들을 사용한다면 이는 ES6를 사용하는데 매우 좋은 전략이 될 것입니다.


function home() {
  const list = ["apple", "orange", "watermelon"];
  list.push("banana");
  console.log(list);
}
// const를 사용하더라도 배열과 오브젝트의 값을 변경하는 것은 가능.
// const 값을 재할당하는 코드 형태만 불가능합니다.
home();

잠깐, 여기서 의문이 생깁니다. immutable array는 과연 어떻게 만들까요? immutable array는 뒤로가기, 앞으로가기 등 어떤 값을 되돌리고 싶을때, 그때그때 저장한 데이터 값을 가져와서 사용할때 사용하는 array 저장방식입니다. 


// immutable array를 어떻게 만들지?
// 뒤로가기, 앞으로가기 어떤 값을 되돌리고 싶을때, 그때그때
// 저장한 데이터 값을 가져와서 사용해야할 필요가 생깁니다.

const list = ["apple", "orange", "watermelon"];
list2 = [].concat(list, "banana");
console.log(list, list2);

console.log(list === list2);

위와 같은 방식은 react 사용시, state 값이 바뀔때 이용하면 매우 유용하게 쓰일 수 있습니다. react의 redux를 사용하여 reducer에서 새로운 객체를 만들어 계속 상태값을 바꾸어 사용해야 할때, 이를 많이 이용하게 됩니다. immutable.js를 사용하면 관련 방법들을 제공하기도 합니다. 


const는 재할당은 안되지만 배열, 오브젝트 값을 변경하는 것은 가능합니다.



ES6의 String 메소드



// ES2015 string에 새로운 메소드들
let str = 'hello world! ^^ ~~';
let matchstr = 'hello';
console.log(str.startsWith(matchstr));
console.log(str.endsWith(matchstr));
console.log("indluce test", str.includes("world"));

ES6는 위와 같이 정규식같은 처리 없인 String관련 작업을 처리할 수 있는 메소드들을 지원합니다. 



Array


아래는 Array 반복문에 관한 예제입니다. 일반적으로 우리는 for...in 방식을 이용하여 배열 순회를 많이 했자만, ES6에서는 for...of 방식을 지원합니다. for...in 배열 반복의 경우 예상치 않은 동작이 많으므로 실무에서는 쓰지 않는 것이 좋습니다.


var data = [1, 2, undefined, NaN, null, ""];
data.forEach(function(value){
  console.log("value is", value);
});

// natvie 메소드를 확장하여 쓰는 것은 권장하지 않습니다.
// 왜냐하면, 부작용이 발생할 수 있습니다.
Array.prototype.getIndex = function(){};

for(let idx in data) {
  console.log(data[idx]);
}

// 배열 for문을 순회할때는 for..in 방식보다는 for..of 를 쓰면
// 위 사항과 같은 부작용 방지가 가능합니다.
for(let value of data) {
  console.log(value);
}

var str = "hello world!!!!";
for(let value of str) {
  console.log(value);
}


아래 코드 실행시, pre와 newData의 값은 같은 결과가 출력되지만 동치여부 비교시에는 false가 출력됩니다. 메모리 공간안에 새로운 데이터가 생성되어 완전히 다른 참조를 가지게 되는 방식입니다. concat을 이용하여 배열을 복사하는 것과 같은 개념이라고 생각하는 것이 좋겠습니다. 

// spread operator, 펼침연산자
let pre = ["apple", "orange", 100];
let newData = [...pre];

console.log(pre, newData);


아래는 spread operator의 또다른 예입니다. 아래와 같이 배열을 다른 배열 중간에 삽입하는 등의 연산을 수행할때 spread operator는 매우 유용합니다.

let pre = [100, 200, "hello", null];
let newData = [0,1,2,3,4, ...pre, 4];

console.log(newData);


또한 배열형태의 인자값을 던져줄때도 매우 유용합니다.


function sum(a, b, c) {
  return a+b+c;
}

let pre = [100, 200, 300];

console.log(sum.apply(null, pre));
console.log("result=>", sum(...pre));


다음은 ES2016(ES6) from 메소드에 대해서 살펴보겠습니다. 자바스크립트 function 내에서는 argument라는 값을 이용하면 function의 인자값이 없더라도 인자값을 받아서 처리하는 것이 가능합니다. argument는 가변적인 파라미터가 들어오는 경우에 가끔씩 이용됩니다. 권장되는 패턴은 아니지만, 파라미터가 얼마나 들어올지 모를때는 매우 유용합니다.


function addMark() {
  let newData = [];
  
  for(let i=0; i<arguments.length; i++) {
    newData.push(arguments[i] + "!");
  }
  
  console.log(newData);
}

addMark(1,2,3,4,5);


또한 javascript내에는 가짜 배열이라는 것이 존재합니다. 배열로 이루어졌을 것 같은데, 실제로는 배열이 아닌 경우의 변수들이 매우 많습니다. 이때 바로 from을 쓰면 매우 유용합니다.


function addMark() {
  let newArray = Array.from(arguments);
  let newData = newArray.map(function(value) {
    return value + "!";
  });
  
  console.log(newData);
}

addMark(1,2,3,4,5,6,7,8,9);


Object


기존의 객체 생성 방식은 아래와 같습니다.


const name = "cron";
const age = 33;

const obj = {
  name : name,
  age : age
}

console.log(obj);


function getObj() {
  
  const name = "crong";
  
  const getName = function() {
    return name;
  }
  
  const setName = function(newName) {
    name = newName;
  }
  
  return {
    getName : getName,
    setName : setName
  }
}

var obj = getObj();
console.log(obj);


ES6의 경우, key, value 매칭없이 value 값만 넣듯이 Object를 생성하는 방법도 함께 제공합니다.


const data = {
  name,
  getName() {
    
  },
  age
}


Destructuring


다음은 Destructuring을 배열에 적용한 방법입니다. 아래와 같은 코드를 통해 배열의 값을 변수에 적당하게 지정 가능하게 됩니다.


// Destructuring
let Data = ["crong", "honux", "jk", "jinny"];

// 방법1
let jisu = data[0];
let jung = data[2];

// 방법2
let [jisu,,jung] = data;
console.log(jisu, jung);


Destructuring은 배열뿐만이 아니라 Object에서도 막강한 기능을 제공하곤 합니다.


let obj = {
  name : "crong",
  address : "Korea",
  age : 10
}

let {name, age} = obj;
console.log(name, age);

let {name:myName, age:myAge} = obj;
console.log(myName, myAge);


다음은 Object에서 Destructuring을 더 유용하게 사용하는 방법의 한 예입니다. 


var news = [
  {
    title : "sbs",
    imgurl : "http://static.naver.net/image/001.jpg",
    newslist : [
      "[가보니] 가상 경주도 즐기고, 내 손으로 자동차도 만들고",
      "리캡차가 사라진다",
      "갤럭시S8 출시? 갤노트7 처리 계획부터 밝혀야",
      "블로코-삼성SDS, 블록체인 사업 '맞손",
      "[블록체인 돌아보기] 퍼블릭 블록체인의 한계와 프라이빗 블록체인"
    ]
  },
  {
    title : "mbc",
    imgurl : "http://static.naver.net/image/002.jpg",
    newslist : [
      "Lorem ipsum dolor sit amet, consectetur adipisicin",
      "ipsum dolor sit amet, consectetur adipisicin",
      "dolor sit amet, consectetur adipisicin",
      "amet, consectetur adipisicin"
    ]
  }
];

let [,mbc] = news;
console.log(mbc);

let {title, imgurl} = mbc;
console.log(title, imgurl);

let [, {title, imgurl}] = news;
console.log(imgurl);

위와 같이 Destructuring을 이용하면 JSON parsing을 손쉽게 할 수 있습니다. 이를 ajax 응답에서 처리하면 아주 유용할 것입니다. 그리고 function을 이용하여 Destructuring을 활용할 수도 있는데요. 아래가 바로 그 예제입니다.


function getNewsList([,{newslist}]) {
  console.log(newslist);
}

getNewsList(news);


document.querySelector("div").addEventListener("click", function({target}){
  console.log(target.innerText);
});



Set과 Map



Set은 유일한 값을 가지는 집합입니다. 실제 array를 이용하여 해당 자료구조를 구현할 수 있는데, ES6에서는 이러한 자료구조를 default로 제공하고 있습니다.

let mySet = new Set();
console.log(toString.call(mySet));

// set: 중복없이 유일한 값을 저장하려고 할때. 이미 존재하는지 체크할 때 유용.

mySet.add("crong");
mySet.add("hary");
mySet.add("crong");

console.log(mySet.has("crong"));
mySet.delete("crong");

mySet.forEach(function(v){
  console.log(v);
});

또한, WeakSet이란 자료구조도 존재하는데요. 참조를 가지고 있는 객체만 저장 가능한 Set 타입입니다. 객체는 더이상 참조하지 않으면 GC에 의해서 제거가 되는데 이러한 참조에 대한 모니터링이 가능하고, GC에 의해 참조가 사라지면 WeakSet안에서도 사라지게 됩니다. 이러한 기능을 이용하면 효율적으로 코딩을 할수 있습니다. 객체형태를 중복없이 저장하려고 할때 매우 유용합니다. WeakSet의 실제 사용 예들은 인터넷에 많이 존재하니, 한예로 객체 타입의 생성자 타입을 밖에서 호출하는 것을 막는다던가 하는 실례를 학습해 보는 것도 도움이 될 것입니다.


let arr = [1, 2, 3, 4];
let arr2 = [5, 6, 7, 8];
let obj = {arr, arr2};
let ws = new WeakSet();

ws.add(arr);
ws.add(arr2);
ws.add(obj);

arr = null;

console.log(ws);
console.log(ws.has(arr), ws.has(arr2));



자바스크립트에서 제공하는 자료구조는 Array와 Object가 존재합니다. Array를 개선한 자료구조가 Set이고 Object를 개선한 자료구조가 Map이라고 이해하면 좋습니다. 여기서 개선의 의미는 더 낫다가 아니라 무언가 기능이 추가된 개념으로 이해하면 됩니다. 


이제 Map에 대해서 알아보겠는데요. Map은 Key/value로 이루어져 있고, WeakMap에 대한 예제를 한번 살펴보겠습니다.


let wm = new WeakMap();
let myfun = function() {};
// 이 함수가 얼마나 실행됐는지를 알려고 할때.

wm.set(myfun, 0);
console.log(wm);

let count = 0;
for(let i=0; i<10; i++) {
  count = wm.get(myfun); //get value
  count++;
  wm.set(myfun, count);
}

console.log(wm.get(myfun));
myfun = null;
console.log(wm.has(myfun));


// WeakMap 활용

function Area(height, width) {
  this.height = height;
  this.width = width;
}

Area.prototype.getArea = function() {
  return this.height * this.width;
}

let myarea = new Area(10, 20);
console.log(myarea.getArea());
console.log(myarea.height);


// WeakMap을 이용하면, 인스턴스관리를 효율적으로 할 수 있습니다.
// GC 대상이 되는 저장소를 따로 만들 수 있습니다.

const wm = new WeakMap();

function Area(height, width) {
  // 해당 객체에 대한 추가 변수를 만들어준다 (private하게..)
  wm.set(this, {height, width});
}

Area.prototype.getArea = function() {
  const {height, width} = wm.get(this);
  return height * width;
}

let myarea = new Area(10, 20);
console.log(myarea.getArea());

console.log(wm.has(myarea));

myarea = null;

console.log(wm.has(myarea));