ES6

ECMAScript 6 이라는 의미로 javascript의 버전이라고 생각하면 간단하다.

특히 ES6는 전 버전에 비해 굉장이 많은 update가 있었기 때문에 주목할만 하다.

추가된 사항들 개요

  • Variable Declarations with ‘let’ and ‘const’
  • Blocks and IIFEs
  • Strings
  • Arrow Functinos
  • Destructuring
  • Arrays
  • The Spread Operator
  • Rest and Default Paramaters
  • Maps
  • Classes and subclasses

let and const

2가지의 새로운 variable 선언 방법이 생겼다. let은 variable, const는 constant로 정의되는 순간 바꿀 수 없다.(바꾸면 에러)

‘var’는 function scoped이지만 ‘let’과 ‘const’는 block scoped이다.

// ES5
function driversLicence5(passedTest) {
    
    if (passedTest) {
        
        //1. var는 execution context를 만들때 hoisting되어 undefined로 먼저 정의 된다.
        console.log(firstName);     //undefined
        var firstName = 'John';
        var yearOfBirth = 1990;
    }
    
    //2. 위 if block안에서 var를 선언 및 정의해도 function scoped이기 때문에
    //block바깥에서도 변수의 사용이 가능하다
    console.log(firstName + ', born in ' + yearOfBirth + ', is now officially allowed to drive a car.');
}

driversLicence5(true);


// ES6
function driversLicence6(passedTest) {
    
    //1. var와 달리 let은 hoisting될 때 정의 되지 않으므로(정확히는 hoisting되어 변수의 존재는 확인이 되나 var처럼 undefined로 메모리가 할당되어 정의되지 않는다. = 에러)
    //선언과 정의까지 해주어야 사용이 가능하다.
    //console.log(firstName);       //error
    let firstName;
    const yearOfBirth = 1990;
    
    if (passedTest) {
        firstName = 'John';
    }
    
    //2. if block안에 let이 선언되면 if block이 끝남과 동시에 let으로 선언된 변수들은 사라지기 때문에 이곳에선 사용이 불가하다.
    //그러나 let을 바깥에 선언하고 if block안에서 정의하면 if block바깥에 let변수가 존재하게 되므로 사용이 가능하다
    console.log(firstName + ', born in ' + yearOfBirth + ', is now officially allowed to drive a car.');
}

driversLicence6(true);



var i = 23;

for (var i = 0; i < 5; i++) {
    console.log(i);
}

console.log(i);     //let일 경우 '23'출력
                    //for의 i와 바깥의 i는 별개의 변수 취급(for문이 끝나면 안에 있는 let i는 사라지기 때문)
                    
                    //var일 경우 '5'출력
                    //for의 i와 같은 변수 취급이기 때문에 바깥의 i가 갱신된다.

Blocks and IIFEs

block scoped인 let과 const로 블록을 이용해 ES5의 (function(){})();처럼 IIFE를 만드는 것이 가능하다

// ES6
{
    const a = 1;
    let b = 2;
    var c = 3;
}

//console.log(a + b);   //error
console.log(c);


// ES5
(function() {
    var c = 3;
})();

//console.log(c);       //error

Strings in ES6

ES5보다 더 쉽게 string을 다룰 수 있게 되었다.


let firstName = 'John';
let lastName = 'Smith';
const yearOfBirth = 1990;

function calcAge(year) {
    return 2016 - year;
}

// ES5
console.log('This is ' + firstName + ' ' + lastName + '. He was born in ' + yearOfBirth + '. Today, he is ' + calcAge(yearOfBirth) + ' years old.');

// ES6
console.log(`This is ${firstName} ${lastName}. He was born in ${yearOfBirth}. Today, he is ${calcAge(yearOfBirth)} years old.`);


const n = `${firstName} ${lastName}`;
console.log(n.startsWith('j'));     //대소문자구분
console.log(n.endsWith('Sm'));
console.log(n.includes('oh'));
console.log(`${firstName} `.repeat(5));

Arrow Functions

anonymous function을 간단하게 표현할 수 있는 기능이다.

const years = [1990, 1965, 1982, 1937];

// ES5
var ages5 = years.map(function(el) {
    return 2016 - el;
});
console.log(ages5);


// ES6
let ages6 = years.map(el => 2016 - el);
console.log(ages6);

//argument가 여러개인 경우 ()필요
ages6 = years.map((el, index) => `Age element ${index + 1}: ${2016 - el}.`);
console.log(ages6);

//안의 코드가 여러줄인 경우 {}필요
ages6 = years.map((el, index) => {
    const now = new Date().getFullYear();
    const age = now - el;
    return `Age element ${index + 1}: ${age}.`
});
console.log(ages6);

Arrow function의 this

The arrow Function shares the lexical ‘this’ keyword of its surroundings.

일반 함수는 함수를 선언할 때 this에 바인딩할 객체가 정적으로 결정되는 것이 아니고, 함수를 호출할 때 함수가 어떻게 호출되었는지에 따라 this에 바인딩할 객체가 동적으로 결정된다고 하였다.

화살표 함수는 함수를 선언할 때 this에 바인딩할 객체가 정적으로 결정된다. 동적으로 결정되는 일반 함수와는 달리 화살표 함수의 this 언제나 상위 스코프의 this를 가리킨다. 이를 Lexical this라 한다. 화살표 함수는 앞서 살펴본 Solution 3의 Syntactic sugar이다.

따라서 화살표 함수는 call, apply, bind 메소드를 사용하여 this를 변경할 수 없다.

[https://poiemaweb.com/es6-arrow-function]

// ES5
var box5 = {
    color: 'green',
    position: 1,
    clickMe: function() {
       
       /* 
        function안에 function은 regular function이므로 그 안의 this는
        window object를 가리킨다.
       */
       var self = this; 
       document.querySelector('.green').addEventListener('click', function() {
            var str = 'This is box number ' + self.position + ' and it is ' + self.color;
            alert(str);
        });
    }
}
box5.clickMe();


// ES6
const box6 = {
    color: 'green',
    position: 1,
    clickMe: function() {
        // arrow function의 this는 부모 function의 this를 공유한다.
        //따라서 this는 box6 object를 가리킨다.
        document.querySelector('.green').addEventListener('click', () => {
            var str = 'This is box number ' + this.position + ' and it is ' + this.color;
            alert(str);
        });
    }
}
box6.clickMe();


const box66 = {
    color: 'green',
    position: 1,
    // arrow function의 this는 부모 function의 this를 공유한다.(항상 상위 object를 바라본다)
    //따라서 this는 box66 object가 아닌 window object를 가리킨다.
    clickMe: () => {
        document.querySelector('.green').addEventListener('click', () => {
            var str = 'This is box number ' + this.position + ' and it is ' + this.color;
            alert(str);     //undefined
        });
    }
}
box66.clickMe();

//////////////////////////////////////////////////////

function Person(name) {
    this.name = name;
}

// ES5
Person.prototype.myFriends5 = function(friends) {
    
    var arr = friends.map(function(el) {
        //function안에 function = regular function, this는 window object를 가리킴
        //bind가 없을 경우, this.name = ""
        //bind로 Person 객체를 묶어줬을 경우, this.name = 'bob', 'jane', 'mark'
       return this.name + ' is friends with ' + el; 
    }.bind(this));
    //bind의 this는 Person object를 가리키고 해당 객체를 copy한다.
    
    console.log(arr);
}

var friends = ['Bob', 'Jane', 'Mark'];
new Person('John').myFriends5(friends);


// ES6
Person.prototype.myFriends6 = function(friends) {

    //this는 Person object를 가리킨다.
    var arr = friends.map(el => `${this.name} is friends with ${el}`);

    console.log(arr);
}

new Person('Mike').myFriends6(friends);

Destructuring

여러 데이터가 묶여있는 객체에서 각 데이터를 하나씩 빼와 변수에 한번에 저장할 수 있도록 하는 기능

// ES5
var john = ['John', 26];
//var name = john[0];
//var age = john[1];

// ES6
//각각 name과 age에 저장된다.
const [name, age] = ['John', 26];
console.log(name);      //'John'
console.log(age);       //26

const obj = {
    firstName: 'John',
    lastName: 'Smith'
};

//주의할 점 : 객체의 키값과 {}안의 이름이 동일해아한다.
const {firstName, lastName} = obj;
console.log(firstName);
console.log(lastName);

//변수값을 바꿔서 꺼내고 싶다면 다음과같이 하면 된다.
const {firstName: a, lastName: b} = obj;
console.log(a);
console.log(b);


function calcAgeRetirement(year) {
    const age = new Date().getFullYear() - year;
    //여러 변수를 한번에 return하고 싶다면 다음과 같이 하면 된다.(여러개의 데이터를 return하고 싶을 때 굳이 객체를 return하지 않아도 된다.)
    return [age, 65 - age];
}

//array형태로 return했으므로 array형태로 destructuring하면 된다.
const [age2, retirement] = calcAgeRetirement(1990);
console.log(age2);
console.log(retirement);

Arrays in ES6

Array에 많은 기능들이 추가 되었다.

const boxes = document.querySelectorAll('.box');

/*
    class 이름 중에 box가 들어온걸 모두 가져오면 boxes에는 array가 아닌
    각 class의 DOM이 node list로 존재하기 때문에 Array로 변환시켜 줄 필요가 있다.
*/

//ES5
var boxesArr5 = Array.prototype.slice.call(boxes);
boxesArr5.forEach(function(cur) {
    cur.style.backgroundColor = 'dodgerblue';
});

//ES6
const boxesArr6 = Array.from(boxes);
//이렇게 한줄로 표현 할 수도 있다.
Array.from(boxes).forEach(cur => cur.style.backgroundColor = 'dodgerblue');

///////////////////////////////////////////

//Loops
//ES5
for(var i = 0; i < boxesArr5.length; i++) {
    
    if(boxesArr5[i].className === 'box blue') {
        continue;
    }
    
    boxesArr5[i].textContent = 'I changed to blue!';
    
}

//ES6
//aka.'for of'
for (const cur of boxesArr6) {
    if (cur.className.includes('blue')) {
        continue;
    }
    cur.textContent = 'I changed to blue!';
}

///////////////////////////////////////////


//ES5
var ages = [12, 17, 8, 21, 14, 11];

var full = ages.map(function(cur) {
    return cur >= 18;
});
console.log(full);                      //[false, false, false, ture, false, false]

console.log(full.indexOf(true));        //3
console.log(ages[full.indexOf(true)]);  //21


//ES6
//findIndex에 넣어준 callback function이 true를 반환할 때의 index를 반환한다.
console.log(ages.findIndex(cur => cur >= 18));  //3
//findIndex에 넣어준 callback function이 true를 반환할 때의 값을 반환한다.(index를 따로 찾을 필요가 없다.)
console.log(ages.find(cur => cur >= 18));       //21

Spread Operator

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

var sum1 = addFourAges(18, 30, 12, 21);
console.log(sum1);              //81

//ES5
var ages = [18, 30, 12, 21];
//apply는 array형태의 parameter를 받기 위한 method이다.(기능은 call,bind와 같다.)
var sum2 = addFourAges.apply(null, ages);
console.log(sum2);              //81

//ES6
//array의 data를 spread하여 각 parameter에 설정하고 있다. (spread operator)
const sum3 = addFourAges(...ages);
console.log(sum3);              //81


const familySmith = ['John', 'Jane', 'Mark'];
const familyMiller = ['Mary', 'Bob', 'Ann'];
const bigFamily = [...familySmith, 'Lily', ...familyMiller];
console.log(bigFamily);


const h = document.querySelector('h1');
const boxes = document.querySelectorAll('.box');
//all은 node list이다.
const all = [h, ...boxes];

//node list를 array로 바꾼 후 foreach를 하였다.
Array.from(all).forEach(cur => cur.style.color = 'purple');

Function Parameters

Rest Parameters

spread operator와 사용법은 같지만, 여러 single parameter를 받아 array나 multiple parameter로 받는것, 즉 spread operator와 정반대의 역할을 한다.

spread operator는 function expression에서

Rest Parameters는 function declaration에서 사용한다는 차이점이 있다.

//ES5
function isFullAge5() {
    /*
        arguments의 형태는 object(따라서 array prototype이 없음)
        변수들은 0,1,2 순서의 key에 맞춰 배치됨
    */
    //console.log(arguments);
    //array형태가 아니기 때문에 변환해줘야한다.
    var argsArr = Array.prototype.slice.call(arguments);
    
    argsArr.forEach(function(cur) {
        console.log((2016 - cur) >= 18);
    })
}

//여러 변수들이 하나의 object로 생성된다.
//isFullAge5(1990, 1999, 1965);
//isFullAge5(1990, 1999, 1965, 2016, 1987);

//ES6
function isFullAge6(...years) {
    /*
        years의 형태는 array형태(따라서 array prototype이 존재함)
    */
    //받을때부터 array형태이기 때문에 변환없이 바로 array의 method를 사용할 수 있다.
    years.forEach(cur => console.log( (2016 - cur) >= 18));
}

//이 모든 parameter들이 years라는 하나의 array형태의 변수가 된다.
isFullAge6(1990, 1999, 1965, 2016, 1987);

///////////////////////////////////
//rest parameter와 다른 것들을 함께 받을 때

//ES5
function isFullAge5(limit) {
    //,1 로 가장 첫번째 argument를 제외하고 array화를 시킬 수 있다.
    var argsArr = Array.prototype.slice.call(arguments, 1);

    argsArr.forEach(function(cur) {
        console.log((2016 - cur) >= limit);
    })
}

//isFullAge5(16, 1990, 1999, 1965);
isFullAge5(1990, 1999, 1965, 2016, 1987);


//ES6
function isFullAge6(limit, ...years) {
    //argument라는 애매한 변수를 사용하지 않아도 되고
    //변수명이 명확하게 나눠져있고, 사용하기 쉬운 형태로 알아서 변환해주기때문에
    //훨씬 간결하게 작성이 가능하다.
    years.forEach(cur => console.log( (2016 - cur) >= limit));
}

isFullAge6(16, 1990, 1999, 1965, 2016, 1987);

Default Parameter

아무것도 들어오지 않은 parameter의 default값을 설정해 줄 수 있다.

// ES5
function SmithPerson(firstName, yearOfBirth, lastName, nationality) {
    
    lastName === undefined ? lastName = 'Smith' : lastName = lastName;
    nationality === undefined ? nationality = 'american' : nationality = nationality;
    
    this.firstName = firstName;
    this.lastName = lastName;
    this.yearOfBirth = yearOfBirth;
    this.nationality = nationality;
}


//ES6
function SmithPerson(firstName, yearOfBirth, lastName = 'Smith', nationality = 'american') {
    this.firstName = firstName;
    this.lastName = lastName;
    this.yearOfBirth = yearOfBirth;
    this.nationality = nationality;
}


var john = new SmithPerson('John', 1990);
var emily = new SmithPerson('Emily', 1983, 'Diaz', 'spanish');

Maps

ES6에서 새롭게 추가된 Data Structure 이다.

가장 흔한 것은 ‘Hash map’으로 key와 value로 이루어진 Data Structure이다.

ES5에서도 있었지만 반드시 Object로 구현해야했고 key도 String만 가능했던 반면,

Map에선 모든 primitive type을 key로 사용할 수 있다.

Map의 장점

  1. 모든 primitive type을 key로 사용가능
  2. iterable
  3. 수정과 데이터 추출이 용이한 object에는 없는 method들(size, delete, has, get …)
const question = new Map();
//Map형태의 객체이므로 prototype으로 Map을 가지게 된다.
question.set('question', 'What is the official name of the latest major JavaScript version?');
question.set(1, 'ES5');
question.set(2, 'ES6');
question.set(3, 'ES2015');
question.set(4, 'ES7');
question.set('correct', 3);
question.set(true, 'Correct answer :D');
question.set(false, 'Wrong, please try again!');

console.log(question.get('question'));      //What is the official name of the latest major JavaScript version?
console.log(question.size);                 //8


if(question.has(4)) {
    //question.delete(4);
    //console.log('Answer 4 is here')
}

//question.clear();

//Map is Iterable! (object는 불가능)
question.forEach((value, key) => console.log(`This is ${key}, and it's set to ${value}`));

//Destructuring을 이용하여 for를 돌린다.
for (let [key, value] of question.entries()) {
    //map은 여러 행태의 key가 가능하기 때문에 이런 if문이 가능하다
    if (typeof(key) === 'number') {
        console.log(`Answer ${key}: ${value}`);
    }
}

const ans = parseInt(prompt('Write the correct answer'));
console.log(question.get(ans === question.get('correct')));

Classes

ES5의 function constructors와 같은 기능이다. (Object의 blueprint)

Java의 class와 똑같이 사용하면 된다.

기존의 function과 다른 점

  1. Class definition은 hoist되지 않는다. = 반드시 instance화 시켜야만 사용이 가능하다!!
  2. property를 추가 할 수 없고 오직 method만 추가가 가능하다(하지만 function의 prototype과 같이 모든 instance에 똑같이 inherit되므로 같다.)
  3. static을 사용할 수 있다.
//ES5
var Person5 = function(name, yearOfBirth, job) {
    this.name = name;
    this.yearOfBirth = yearOfBirth;
    this.job = job;
}

Person5.prototype.calculateAge = function() {
    var age = new Date().getFullYear - this.yearOfBirth;
    console.log(age);
}

var john5 = new Person5('John', 1990, 'teacher');

//ES6
//class definition은 hoist되지 않는다!!!! = 반드시 instance화 시켜야만 사용이 가능하다!!
//property를 추가 할 수 없고 오직 method만 추가가 가능하다(그렇지만 property를 통해 object에 inherit하는 것은 그닥 바람직한 방법이 아니다.)
class Person6 {
    //모든 class는 constructor가 있어야한다.
    constructor (name, yearOfBirth, job) {
        this.name = name;
        this.yearOfBirth = yearOfBirth;
        this.job = job;
    }
    
    //class의 method(prototype과 동일)
    calculateAge() {
        var age = new Date().getFullYear - this.yearOfBirth;
        console.log(age);
    }
    
    //class만의 특징 'static'
    //class에 붙어는 있지만 instance에 inherit되지 않는 것
    static greeting() {
        console.log('Hey there!');
    }
}

const john6 = new Person6('John', 1990, 'teacher');

//instance에 inherit되지 않고 class에 붙어있기 때문에(function definition과 동일) instance없이도 class를 불러 바로 사용이 가능
Person6.greeting();

SubClasses

class끼리의 상속을 확인

prototype으로 object끼리 상속했던 것처럼 class만의 상속방법이 있다.

//ES5
//Parent
var Person5 = function(name, yearOfBirth, job) {
    this.name = name;
    this.yearOfBirth = yearOfBirth;
    this.job = job;
}

Person5.prototype.calculateAge = function() {
    var age = new Date().getFullYear() - this.yearOfBirth;
    console.log(age);
}

//Child
var Athlete5 = function(name, yearOfBirth, job, olymicGames, medals) {
    //Person5를 상속한다.
    /*
      this는 Athelete5를 new할 때, Athelete5를 가리키기 때문에,
      해당 코드의 의미는 Athelete5에 Person5의 property들을 정의하고 넣어주라는 말이다.
    */
    Person5.call(this, name, yearOfBirth, job);
    this.olymicGames = olymicGames;
    this.medals = medals;
}

//Person5의 prototype을 Athlete5의 prototype에 넣는다. = prototype chain이 만들어짐
Athlete5.prototype = Object.create(Person5.prototype);

Athlete5.prototype.wonMedal = function() {
    this.medals++;
    console.log(this.medals);
}


var johnAthlete5 = new Athlete5('John', 1990, 'swimmer', 3, 10);

//Person5를 상속한 Athlete5는 Person5의 모든 method를 Person5의 prototype을 통해 상속하여 가지고 있다.
johnAthlete5.calculateAge();
johnAthlete5.wonMedal();


//ES6
class Person6 {
    constructor (name, yearOfBirth, job) {
        this.name = name;
        this.yearOfBirth = yearOfBirth;
        this.job = job;
    }

    calculateAge() {
        var age = new Date().getFullYear() - this.yearOfBirth;
        console.log(age);
    }
}

//클래스 Athlete6는 Person6를 상속합니다.
class Athlete6 extends Person6 {
    constructor(name, yearOfBirth, job, olympicGames, medals) {
        //슈퍼 클래스인 Person6에 해당 parameter를 세팅합니다.
        super(name, yearOfBirth, job);
        this.olympicGames = olympicGames;
        this.medals = medals;
    }
    
    wonMedal() {
        this.medals++;
        console.log(this.medals);
    }
}

const johnAthlete6 = new Athlete6('John', 1990, 'swimmer', 3, 10);

johnAthlete6.wonMedal();
//Person6를 상속했기 때문에 Person6의 method를 가지고 있다.
johnAthlete6.calculateAge();