Vue.js

ECMAScript 2015

하이라이터 2019. 6. 2. 22:41
728x90

단일 HTML 파일에 <script> 태그로 vue.js 라이브러리를 직접 참조하는 방법으로는 대규모 앱 개발이 힘듦

→  대규모 앱을 개발하려면 SPA(Single Page Application) 구조여야 하는데, Vue.js 기반의 SPA 애플리케이션 개발에는 Vue Router, Vuex 등의 다양한 요소가 필요

→ 이들을 이용하기 위해서는 ECMAScript2015(ES2015 또는 ES6)나 TypeScript 등을 사용해야함

→ 하지만 ES2015는 최신브라우저에서만 지원하므로 하위브라우저에서는 사용 불가, TypeScript는 브라우저에서 직접 실행 안됨

→ 따라서 트랜스파일러(Transpiler)를 사용해 ES2015, TypeScript 코드를 오래된 브라우저에서도 사용할 수 있는 하위 버전의 자바스크립트로 번역

* 대표적인 트랜스파일러 : Babel, TSC(TypeScript Compiler)

 

  • ECMAScript란?
    1995년 넷스케이프(Netscpae) 웹브라우저에서 동적인 요소를 구현하기 위해 자바스크립트(JavaScript)가 발명되었다. 그 후 많은 웹 브라우저들에서 자바스크립트를 탑재하였으며, 현재는 대부분의 브라우저에서 자바스크립트를 제공하고있다. 다양한 브라우저들의 호환을 위해서 자바스크립트에 대한 표준 규격이 필요하게되었고, 이에 따라 ECMA 국제기구에서 "ECMAScript Standard"라는 표준을 만들게된다. 
    현재의 자바스크립트는 ECMAScirpt와 BOM(Browser Object Model)와 DOM(Document Object Model)을 포괄하는 개념이다. 

 


Babel

ES2015(ES6) / ES2016(ES7) 코드를 ES2009(ES5) 코드로 트랜스파일링하기 위한 도구

최신버전의 자바스크립트 문법은 하위 브라우저에서 사용할 수 없기 때문에 babel을 통해 브라우저가 이해할 수 있는 문법으로 변환하기 위해 사용한다.

//ES6 문법
let name = 'world';
console.log(`Hello ${name}`);

    ↓트랜스파일링

//ES5 문법
var name = 'world';
console.log('Hello ' + name);

let과 const

var 키워드의 문제점

  • 함수 단위 스코프
    - 함수 단위의 스코프만 허용하며, { }로 묶여진 블록 단위 스코프를 지원하지 않는다. 따라서 for문의 변수 선언문에서 선언한 변수를 for 문의 코드 블록 외부에서 참조할 수 있다.
  • var 키워드 생략 허용
    - 암묵적 전역 변수가 양산될 수 있다.
  • 변수 중복 선언 허용
    - 의도치 않은 변수 값의 변경이 일어날 수 있다.
  • 변수 선언 전 사용
    - 선언하기 이전의 변수를 참조할 수 있다.

ES2015에서는 이러한 문제들을 해결하기 위해 let 키워드를 지원

let msg = "GLOBAL";
function outer(a) {
    let msg = "OUTER";
    console.log(msg);
    if (true) {
        let msg = "BLOCK";
        console.log(msg);
    }
}

    ↓트랜스파일링

var msg = "GLOBAL";
function outer(a) {
    var msg = "OUTER";
    console.log(msg);
    if (true) {
        var _msg = "BLOCK";
        console.log(_msg);
    }
}

트랜스파일한 코드에서 let으로 선언한 msg 변수명은 충돌을 피하기위해 _msg로 변경됨

 


기본 파라미터와 가변 파라미터

ES2015에서는 기본 파라미터(Default Parameter)를 이용해 함수 파라미터의 기본값을 지정할 수 있다.

function addContact(name, mobile,
        home = "없음",
        address = "없음",
        email = "없음") {
    var str = `name=${name}, mobile=${mobile}, home=${home}, address=${address}, email=${email}`;
    console.log(str);
}

addContact("홍길동", "010-222-3331")
addContact("이몽룡", "010-222-3331", "02-3422-9900", "서울시");

    ↓트랜스파일링

function addContact(name, mobile) {
    var home = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : "없음";
    var address = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : "없음";
    var email = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : "없음";

    var str = "name=" + name + ", mobile=" + mobile + ", home=" + home + ", address=" + address + ", email=" + email;
    console.log(str);
}

addContact("홍길동", "010-222-3331");
addContact("이몽룡", "010-222-3331", "02-3422-9900", "서울시");

 

가변 파라미터(Rest Parameter)를 이용해 여러 개의 파라미터 값을 배열로 받을 수도 있다.

function foodReport(name, age, ...favoriteFoods) {
    console.log(name + ", " + age);
    console.log(favoriteFoods);
}

foodReport("이몽룡", 20, "짜장면", "냉면", "불고기");
foodReport("홍길동", 16, "초밥");

    ↓트랜스파일링

function foodReport(name, age) {
    console.log(name + ", " + age);

    for (var _len = arguments.length, favoriteFoods = Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) {
        favoriteFoods[_key - 2] = arguments[_key];
    }

    console.log(favoriteFoods);
}

foodReport("이몽룡", 20, "짜장면", "냉면", "불고기");
foodReport("홍길동", 16, "초밥");

구조분해 할당(destructuring assignment)

ES2015에서는 배열, 객체의 값들을 추출하여 여러 변수에 할당할 수 있는 기능을 제공한다.

let arr = [10,20,30,40];
let [a1,a2,a3] = arr;
console.log(a1, a2, a3);

let p1 = {name:"홍길동", age:20, gender:"M"};
let { name:n, age:a, gender } = p1;
console.log(n,a,gender);

arr의 배열 값을 순서대로 a1,a2,a3 변수에 각각 10,20,30이 할당

p1 객체의 name 속성을 변수 n에 할당, age 속성을 변수 a에 할당

p1 객체의 속성과 할당하려는 변수명이 동일할 때는 변수명 생략 가능

    ↓트랜스파일링

var arr = [10, 20, 30, 40];
var a1 = arr[0],
    a2 = arr[1],
    a3 = arr[2];

console.log(a1, a2, a3);

var p1 = { name: "홍길동", age: 20, gender: "M" };
var n = p1.name,
    a = p1.age,
    gender = p1.gender;

console.log(n, a, gender);

 

구조분해 할당은 함수 파라미터에서도 사용할 수 있다.

function addContact({name, phone, email="이메일 없음", age=0}) {
    console.log("이름 : " + name);
    console.log("전번 : " + phone);
    console.log("이메일 : " + email);
    console.log("나이 : " + age);
}

addContact({
    name : "이몽룡", 
    phone : "010-3434-8989"
})

addContact 함수를 호출할 때 자바스크립트 객체를 파라미터 값으로 전달 → 전달된 객체는 구조분해 할당 수행

    ↓트랜스파일링

function addContact(_ref) {
    var name = _ref.name,
        phone = _ref.phone,
        _ref$email = _ref.email,
        email = _ref$email === undefined ? "이메일 없음" : _ref$email,
        _ref$age = _ref.age,
        age = _ref$age === undefined ? 0 : _ref$age;

    console.log("이름 : " + name);
    console.log("전번 : " + phone);
    console.log("이메일 : " + email);
    console.log("나이 : " + age);
}

addContact({
    name: "이몽룡",
    phone: "010-3434-8989"
});

화살표 함수(Arrow function)

ES2015의 화살표 함수는 기존 함수 표현식에 비해 간결함을 제공하며, 함수를 정의하는 영역의 this를 그대로 전달받을 수 있다.

//간결한 표현식 제공
var test1 = function(a,b) {
    return a+b;
}

let test2 = (a,b) =>{
    return a+b;
};

let test3 = (a,b) => a+b;

//this 바인딩
function Person(name, yearCount) {
    this.name = name;
    this.age = 0;
    var incrAge = ()=> {
        this.age++;
    }
    for (var i=1; i <= yearCount; i++) {
        incrAge();
    }
}
var p1 = new Person("홍길동",20);
//--this.age는 20이 출력됨.
console.log(p1.name + "님의 나이 : " + p1.age);

화살표 함수의 this는 함수를 둘러싸고 있는 영역의 this를 그대로 사용한다.

↓트랜스파일링

//간결한 표현식 제공
var test1 = function test1(a, b) {
    return a + b;
};

var test2 = function test2(a, b) {
    return a + b;
};

var test3 = function test3(a, b) {
    return a + b;
};

//this 바인딩
function Person(name, yearCount) {
    var _this = this;

    this.name = name;
    this.age = 0;
    var incrAge = function incrAge() {
        _this.age++;
    };
    for (var i = 1; i <= yearCount; i++) {
        incrAge();
    }
}
var p1 = new Person("홍길동", 20);
//--this.age는 20이 출력됨.
console.log(p1.name + "님의 나이 : " + p1.age);

전통적인 함수에서 this는 바깥쪽 영역의 this와 다른 값이다. 그래서 바깥쪽 영역의 this를 다른 변수(_this)에 할당하고 참조해서 사용해야 같은 결과를 얻을 수 있다.


새로운 객체 리터럴

ES2015에서는 객체의 속성을 작성할 때 변수명과 동일하다면 생략할 수 있다.

var name = "홍길동";
var age = 20;
var email = "gdhong@test.com";

//var obj = { name: name, age: age, email: email };
var obj = { name, age, email };
console.log(obj);

↓트랜스파일링

var name = "홍길동";
var age = 20;
var email = "gdhong@test.com";

//var obj = { name: name, age: age, email: email };
var obj = { name: name, age: age, email: email };
console.log(obj);

 

ES2015에서는 새로운 메서드 표기법도 제공한다.

let p1 = {
    name : "아이패드",
    price : 200000,
    quantity : 2,
    discount(rate) {
        if (rate > 0 && rate < 0.8) {
            this.amount = (1-rate) * this.price * this.quantity; 
        }
        console.log((100*rate) + "% 할인된 금액으로 구매합니다.");
    }
}
p1.discount(0.2);

function 키워드를 사용하지 않고 바로 { } 구현부를 작성할 수 있다.

↓트랜스파일링

var p1 = {
    name: "아이패드",
    price: 200000,
    quantity: 2,
    discount: function discount(rate) {
        if (rate > 0 && rate < 0.8) {
            this.amount = (1 - rate) * this.price * this.quantity;
        }
        console.log(100 * rate + "% 할인된 금액으로 구매합니다.");
    }
};
p1.discount(0.2);

템플릿 리터럴

템플릿 리터럴은 역따옴표(Backquote : ` )로 묶여진 문자열을 템플릿 대입문(${})을 이용해 동적으로 문자열을 끼워넣어 구성할 수 있는 방법을 제공한다. 템플릿 대입문에는 수식 구문, 변수, 함수 호출 구문 등 대부분의 표현식을 사용할 수 있다. 

var d1 = new Date();
var name = "홍길동";
var r1 = `${name} 님에게 ${d1.toDateString() }에 연락했다.`;
console.log(r1);

var product = "갤럭시S7";
var price = 199000;
var str = `${product}의 가격은
       ${price}원 입니다.`;
console.log(str);

↓트랜스파일링

var d1 = new Date();
var name = "홍길동";
var r1 = name + " \uB2D8\uC5D0\uAC8C " + d1.toDateString() + "\uC5D0 \uC5F0\uB77D\uD588\uB2E4.";
console.log(r1);

var product = "갤럭시S7";
var price = 199000;
var str = product + "\uC758 \uAC00\uACA9\uC740\n       " + price + "\uC6D0 \uC785\uB2C8\uB2E4.";
console.log(str);

트랜스파일된 코드에서는 문자열을 이어붙이는 코드로 변경되었으며, 한글은 유니코드 이스케이프 형식으로 변환


컬렉션

ES2015에서는 Set, Map, WeakSet, WeakMap과 같은 집합, 맵을 제공한다.

Set은 중복을 허용하지 않으며 합집합(Union), 교집합(Inersect)과 같은 다양한 집합 연산을 제공한다.

var s1 = new Set();
s1.add("사과");   s1.add("배");
s1.add("사과");   s1.add("포도");
//실행 결과 : Set { '사과', '배', '포도' }
console.log(s1);

var john = new Set(["사과", "포도", "배"]);
var susan = new Set(["파인애플", "키위", "배"]);

//합집합 : Set { '사과', '포도', '배', '파인애플', '키위' }
var union = new Set([...john.values(), ...susan.values()]);
console.log(union);

//교집합 : Set { '배' }
var intersection = new Set([...john.values()].filter(e => susan.has(e)));
console.log(intersection);

//차집합 : Set { '사과', '포도' }
var diff = new Set([...john.values()].filter(e => !susan.has(e)));
console.log(diff);

↓트랜스파일링

function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }

var s1 = new Set();
s1.add("사과");s1.add("배");
s1.add("사과");s1.add("포도");
//실행 결과 : Set { '사과', '배', '포도' }
console.log(s1);

var john = new Set(["사과", "포도", "배"]);
var susan = new Set(["파인애플", "키위", "배"]);

//합집합 : Set { '사과', '포도', '배', '파인애플', '키위' }
var union = new Set([].concat(_toConsumableArray(john.values()), _toConsumableArray(susan.values())));
console.log(union);

//교집합 : Set { '배' }
var intersection = new Set([].concat(_toConsumableArray(john.values())).filter(function (e) {
  return susan.has(e);
}));
console.log(intersection);

//차집합 : Set { '사과', '포도' }
var diff = new Set([].concat(_toConsumableArray(john.values())).filter(function (e) {
  return !susan.has(e);
}));
console.log(diff);

 

Map은 Key-Value 쌍의 집합체이며, 키는 고유한 값이여야 한다.

let teams = new Map();
teams.set('LG', '트윈스');     teams.set('삼성', '라이온스');
teams.set('NC', '다이노스');   teams.set('기아', '타이거스');
teams.set('한화', '이글즈');   teams.set('롯데', '자이언츠');

console.log(teams.has("SK"));       //false
console.log(teams.get("LG"));       //트윈스

↓트랜스파일링

var teams = new Map();
teams.set('LG', '트윈스');teams.set('삼성', '라이온스');
teams.set('NC', '다이노스');teams.set('기아', '타이거스');
teams.set('한화', '이글즈');teams.set('롯데', '자이언츠');

console.log(teams.has("SK")); //false
console.log(teams.get("LG")); //트윈스

(똑같은데??)


클래스

ES2015에서는 공식적으로 클래스를 지원한다.

//static Method와 Instance Method, 생성자를 모두 지원
class Person {
    constructor(name, tel, address) {
        this.name = name;
        this.tel = tel;
        this.address = address;
        if (Person.count) { Person.count++; } else { Person.count = 1; }    
    }
    static getPersonCount() { 
        return Person.count; 
    }
    toString() {
        return `name=${this.name}, tel=${this.tel}, address=${this.address}`;
    }
}

var p1 = new Person('이몽룡', '010-222-3332', '경기도');
var p2 = new Person('홍길동', '010-222-3333', '서울');
console.log(p1.toString());
console.log(Person.getPersonCount());

//Person을 상속받은 Employee 클래스
class Employee extends Person {
    constructor(name, tel, address, empno, dept) {
        super(name,tel,address);
        this.empno = empno;
        this.dept = dept;
    }
    toString() {
        return super.toString() + `, empno=${this.empno}, dept=${this.dept}`;
    }
    //메서드를 추가하여 기능 확장
    getEmpInfo() {
        return `${this.empno} : ${this.name}은 ${this.dept} 부서입니다.`;
    }
}

let e1 = new Employee("이몽룡", "010-222-2121", "서울시", "A12311", "회계팀");
console.log(e1.getEmpInfo());
console.log(e1.toString());
console.log(Person.getPersonCount());

다른 프로그래밍 언어의 클래스와 유사하게 생성자, 정적 메서드, 인스턴스 메서드와 상속도 지원한다.

↓트랜스파일링

var _get = function get(object, property, receiver) { if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { return get(parent, property, receiver); } } else if ("value" in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } };

var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();

function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }

function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

var Person = function () {
    function Person(name, tel, address) {
        _classCallCheck(this, Person);

        this.name = name;
        this.tel = tel;
        this.address = address;
        if (Person.count) {
            Person.count++;
        } else {
            Person.count = 1;
        }
    }

    _createClass(Person, [{
        key: 'toString',
        value: function toString() {
            return 'name=' + this.name + ', tel=' + this.tel + ', address=' + this.address;
        }
    }], [{
        key: 'getPersonCount',
        value: function getPersonCount() {
            return Person.count;
        }
    }]);

    return Person;
}();

var p1 = new Person('이몽룡', '010-222-3332', '경기도');
var p2 = new Person('홍길동', '010-222-3333', '서울');
console.log(p1.toString());
console.log(Person.getPersonCount());

var Employee = function (_Person) {
    _inherits(Employee, _Person);

    function Employee(name, tel, address, empno, dept) {
        _classCallCheck(this, Employee);

        var _this = _possibleConstructorReturn(this, (Employee.__proto__ || Object.getPrototypeOf(Employee)).call(this, name, tel, address));

        _this.empno = empno;
        _this.dept = dept;
        return _this;
    }

    _createClass(Employee, [{
        key: 'toString',
        value: function toString() {
            return _get(Employee.prototype.__proto__ || Object.getPrototypeOf(Employee.prototype), 'toString', this).call(this) + (', empno=' + this.empno + ', dept=' + this.dept);
        }
    }, {
        key: 'getEmpInfo',
        value: function getEmpInfo() {
            return this.empno + ' : ' + this.name + '\uC740 ' + this.dept + ' \uBD80\uC11C\uC785\uB2C8\uB2E4.';
        }
    }]);

    return Employee;
}(Person);

var e1 = new Employee("이몽룡", "010-222-2121", "서울시", "A12311", "회계팀");
console.log(e1.getEmpInfo());
console.log(e1.toString());
console.log(Person.getPersonCount());

모듈

ES2015에서는 독립성을 가진 재사용 가능한 코드블록인 모듈 기능을 제공한다.

// utils/utillity1 모듈 파일
export let var1 = 1000;
export function add(a,b) {
    return a+b;
}

//모듈 import 파일
import { add, var1 } from './utils/utility1';

console.log(add(4, 5));
console.log(var1);

↓트랜스파일링

// utils/utillity1 모듈 파일
Object.defineProperty(exports, "__esModule", {
    value: true
});
exports.add = add;
var var1 = exports.var1 = 1000;
function add(a, b) {
    return a + b;
}

//모듈 import 파일
var _utility = require('./utils/utility1');

console.log((0, _utility.add)(4, 5));
console.log(_utility.var1);

Promise

AJAX 처리를 위한 비동기 처리를 수행할 때 비동기 처리가 완료되면 콜백함수가 호출되도록 작성하는것이 일반적인 형태이지만, 비동기로 처리되는 작업이 반복되면 콜백 함수들이 중첩되어 예외 처리가 힘들어진다.

 

ES2015에서는 Promise 객체를 지원해 비동기 처리를 좀 더 깔끔하게 수행할 수 있다.

var p = new Promise(function(resolve, reject) {
    setTimeout(function() {
        var num = Math.round(Math.random()*20);
        var isValid = num % 2;
        if (isValid) { resolve(num); }
        else { reject(num); }
    }, 2000);
});

p.then(function(num) {
    console.log("홀수 : " + num);
}).catch(function(num) {
    console.log("짝수 : " + num);
});

console.log("20까지의 난수중 홀수/짝수?");
console.log("결과는 2초후에 나옵니다.!!");

첫 번째 인자로 전달된 resolve 함수를 호출하면 Promise 객체의 then 메서드에 등록된 함수가 호출된다.

두 번째 인자로 전달된 reject 함수를 호출하면 Promise 객체의 catch 메서드에 등록된 함수가 호출된다. 

↓트랜스파일링

var p = new Promise(function (resolve, reject) {
    setTimeout(function () {
        var num = Math.round(Math.random() * 20);
        var isValid = num % 2;
        if (isValid) {
            resolve(num);
        } else {
            reject(num);
        }
    }, 2000);
});

p.then(function (num) {
    console.log("홀수 : " + num);
}).catch(function (num) {
    console.log("짝수 : " + num);
});

console.log("20까지의 난수중 홀수/짝수?");
console.log("결과는 2초후에 나옵니다.!!");

(똑같네)


전개 연산자(Spread Operator)

  • 가변 파라미터 : ...연산자를 함수의 인자로 사용. 개별 값을 나열하여 함수의 인자로 전달하면 함수의 내부에서 배열로 사용할 수 있도록 함.
  • 전개 연산자 : ...연산자를 배열이나 객체와 함께 사용. 객체 리터럴, 배열 리터럴을 분해된 값으로 전달
let obj1 = { name:"박문수", age:29 };
let obj2 = { ...obj1 };
let obj3 = { ...obj1, email:"mspark@gmail.com" };

console.log(obj2);
console.log(obj3);  
console.log(obj1 == obj2);      //false

let arr1 = [ 100, 200, 300 ];
let arr2 = [ "hello", ...arr1, "world"];
console.log(arr2);

↓트랜스파일링

var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };

var obj1 = { name: "박문수", age: 29 };
var obj2 = _extends({}, obj1);
var obj3 = _extends({}, obj1, { email: "mspark@gmail.com" });

console.log(obj2);
console.log(obj3);
console.log(obj1 == obj2); //false

var arr1 = [100, 200, 300];
var arr2 = ["hello"].concat(arr1, ["world"]);
console.log(arr2);

 

실행화면

obj3의 결과와 같이 기존 객체의 속성이나 배열의 요소들을 포함하여 새로운 객체, 배열을 생성하고자 할 때 사용

728x90