티스토리 뷰

let과 const

ES2015가 등장하기 전까지는 변수를 선언하기 위해 var 키워드를 주로 사용했다. var 키워드는 이름이 같은 변수를 중복 선언해도 오류가 발생하지 않는다.

또한 블록 단위의 스코프를 지원하지 않는다. var 키워드는 함수 단위의 스코프만 지원한다. { }로 묶여진 블록 내에서 선언한 변수는 별도의 스코프를 만들지 않는다는 것을 의미한다.

ES2015에서는 이러한 문제를 해결하기 위해 let 키워드를 지원한다. 블록 단위의 스코프도 해결했고, 변수의 중복 선언을 방지할 수 있다.

const는 상수 기능을 제공한다. 즉 한 번 값이 주어지면 다시 변경할 수 없다. const 또한 블록 스코프를 제공한다.

기존 var 키워드는 중복 선언을 허용한다. 즉 아래 코드는 오류를 일으키지 않는다.
var a = 100;
var a = 'hello';
var a = { name: '홍길동', age: 20 };

반면 letconst는 중복 선언을 허용하지 않는다. 위 코드에서 varlet으로 변경하면 오류가 발생한다.


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

ES2015에서는 기본파라미터(Default Parameter)를 이용해 함수 파라미터의 기본 값을 지정할 수 있다. 예제를 통해 확인해보자.
function addContact(name, mobile, home='없음', address='없음', email='없음') {
    var str = `name=${name}, mobile=${mobile}, email=${email}`;
    console.log(str);
}

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

addContact 함수의 home, address, email 파라미터는 값을 전달하지 않을 경우 주어진 기본값이 할당된다. 위 코드의 경우는 각각 '없음'이라는 기본값이 할당된다.


가변 파라미터(Rest Parameter)는 여러 개의 파라미터 값을 배열로 받을 수 있도록 한다. 전달하는 파라미터의 개수는 가변적으로 적용할 수 있다.

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

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

파라미터의 앞 부분이 ...으로 시작하는 favoriteFoods가 가변 파라미터이다. 함수 호출시 가변 파라미터가 주어진 3번째부터 주어진 인자들은 favoriteFoods에 배열 형태로 전달된다.


구조분해 할당(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);

2행은 arr의 배열 값을 순서대로 a1, a2, a3 변수에 각각 10, 20, 30을 할당한다. 6행의 코드에서는 p1객체의 name 속성을 변수 n에 할당하고 p1.age를 변수 a에 할당한다. p1 객체의 속성과 할당하려는 변수의 이름이 동일할 때는 변수명을 생략할 수 있다.


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

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 함수를 호출할 때 자바스크립트  객체를 파라미터 값으로 전달하고 있다. 전달된 객체는 구조분해 할당을 수행한다. 기본 파라미터에 해당하는 인자에 대한 객체의 속성이 존재하지 않을 경우 기본값이 할당된다. 이와 같이 객체로 파라미터 값을 전달하는 경우에는 파라미터의 전달 순서는 실행 결과에 영향을 주지 않는다.


화살표 함수(Arrow function)

ES2015의 화살표 함수는 기존 함수 표현식에 비해 간결함을 제공한다. 또한 함수를 정의하는 영역의 this를 그대로 전달받을 수 있다. 얼마나 간결한 표현식을 사용하는지 확인해보자. 아래 3개의 함수는 동일한 기능을 수행한다.
var test1 = function(a, b) {
    return a+b;
}

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

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

console.log(test1(3,4));
console.log(test2(3,4));
console.log(test3(3,4));

하지만 주의할 점이 하나 있다. 바로 화살표 함수와 전통적인 함수는 서로 다른 this 값이 바인딩된다는 점이다. 우선 전통적인 함수를 사용했을 때를 살펴보자.

function Person(name, yearCount) {
    this.name = name;
    this.age = 0;
    var incrAge = function() {
        this.age++;
    }
    for (var i=1; i<=yearCount; i++) {
        incrAge();
    }
}

var p1 = new Person("홍길동", 20);
// -- 여기서 this.age는 0이 출력됨.
console.log(p1.name + "님의 나이 : " + p1.age);

위와 같이 Person 함수를 생성자를 이용해서 객체를 생성하면 Person 함수 안에서의 this객체 p1을 가리킨다. incrAge 함수는 Person 함수 안에 정의되어 있고, 반복문을 통해 반복적으로 호출되고 있는데, 간단히 생각하면 incrAge 함수 안에서의 this.age는 함수를 둘러싸고 있는 환경의 this.age가 전달될 것 같지만 그렇지 않으며 결과적으로 p1.age 값이 20이 될 것 같지만 그렇지 않다.


자바스크립트에서 this는 호출하는 문맥에 의해 좌우된다. 문맥을 넘어서서 this를 연결하려면 bind, apply, call 등의 함수 수준의 메서드를 이용해야 한다. 이 메서드들은 직접 this를 연결할 수 있는 기능을 제공한다. 


위 코드가 의도한대로 작동하도록 해결하기 위해서 다음과 같이 변경할 수 있다.

...
for (var i=1; i<=yearCount; i++) {
    incrAge.apply(this);
}
...

함수 수준의 apply 메서드를 이용해 incrAge 함수를 둘러싸고 있는 영역의 thisincrAge 함수 내부의 this로 강제 지정하는 것이다. 


또는 같은 결과를 위해 아래 코드와 같이 바깥쪽 영역의 this를 다른 변수에 할당하고 참조하는 방법을 사용할 수도 있다.

var outerThis = this;
var incrAge = function() {
    outerThis.age++;
}
for (var i=1; i<=yearCount; i++) {
    incrAge();
}
...

하지만 화살표 함수는 함수를 둘러싸고 있는 영역의 this를 화살표 함수 내부에서 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);

 

새로운 객체 리터럴

ES2015에서는 객체의 속성 표기법이 개선되었다. 객체의 속성을 작성할 때 변수명과 동일하다면 생략할 수 있다.
var name = "홍길동";
var age = 20;
var email = "gdhhong@test.com";

// var obj = { name: name, age: age, email: email };  // 기존 표기법
var obj = { name, age, email }; // 속성명과 변수명이 같을 경우 개선된 표기법
console.log(obj);

이와 같이 객체를 생성할 때 변수 값을 객체의 속성으로 지정하는 경우, 위와 같이 속성 값을 생략하여 표기할 수 있다.


또한 아래 예제와 같이 새로운 메서드 표기법도 제공한다.

let p1 = {
    name: '아이패드',
    price: 200000,
    quantity: 2,
    order: function() {  // 기존 메서드 표기법
        if (!this.amount) {
            this.amount = this.quantity * this.price;
        }
        console.log('주문금액 : ' + this.amount);
    },
    discount(rate) { // 새로운 메서드 표기법 
        if (rate > 0 && rate < 0.8) {
            this.amount = (1 - rate) * this.price * this.quantity;
        }
        console.log((100 * rate) + '% 할인된 금액으로 구매합니다.');
    }
}
p1.discount(0.2);
p1.order();
위 코드에서 ES2015가 제공하는 방식으로 작성된 discount() 메서드 부분을 보면 function 키워드를 사용하지 않고 바로 { } 구현부가 따라오는 것을 알 수 있다.

템플릿 리터럴

템플릿 리터럴(Template Literal)은 역따옴표(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);


컬렉션

자바스크립트의 배열도 List 형태의 컬렉션이기는 하지만 집합(set)이나 맵(map) 형식의 데이터로 사용하기에는 불편함이 있다. 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);

여러 개의 요소를 가진 집합을 초기화할 때는 Set 생성자 함수에 배열값을 인자로 전달하면 된다. 또한 교집합(Intersect), 합집합(Union), 차집합(Difference)을 연산하기 위해서는 배열의 기능을 활용한다. 교집합, 차집합의 경우는 배열의 filter 메서드를 이용했다.


맵(Map)은 키-값 쌍의 집합체이며, 키는 고유한 값이어야 한다.

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'));   // 트윈스
위 예제에서는 set(), get(), has() 메서드만 사용했다. 각각 값의 설정, 획득, 키의 존재 여부를 확인하는 메서드이다. 이 밖에도 clear(), delete()와 같은 메서드를 사용할 수 있다.

클래스

이전 버전의 자바스크립트(ES5, ES5.1)까지는 클래스가 제공되지 않았다. 그래서 함수를 이용해 유사 클래스를 만드는 방법을 사용해왔는데 ES2015에서는 공식적으로 클래스를 지원한다.
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=${name}, tel=${tel}, address=${address}`;
    }
}

var p1 = new Person('이몽룡', '010-111-2222', '경기도');
var p2 = new Person('홍길동', '010-333-4444', '서울');
console.log(p1.toString());
console.log(Person.getPersonCount());
다른 프로그래밍 언어의 클래스와 유사하게 생성자(Constructor), 정적 메서드(Static Methhod), 인스턴스 메서드(Instance Method)를 모두 잘 지원하고 있다.

또한 ES2015는 상속도 지원한다.
// 위 예제 코드에 이어서 작성...

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=${empno}, dept=${dept}`;
    }
    getEmpInfo() {
        return `${this.empno} : ${this.name}은 ${this.dept} 부서입니다.`;
    }
}

let e1 = new Employee('이몽룡', '010-222-1111', '서울시', 'A12311', '회계팀');
console.log(e1.getEmpInfo());
console.log(e1.toString());
console.log(Person.getPersonCount());
이 예제의 Employee 클래스는 Person 클래스로부터 상속 받았다. 기존 클래스의 기능들을 상속받아 사용하고, getEmpInfo()와 같은 메서드를 추가하여 기능을 확장했다. 자바C#과 같은 객체지향프로그래밍을 다루어 본 경험이 있다면 익숙한 코드일 것이다.

모듈

전통적인 자바스크립트에서는 <script> 태그로 js 파일을 참조하는 정도는 가능했지만 모듈이라는 개념은 희박하다. 

ES2015부터 공식적으로 모듈 기능을 제공한다. 모듈이란 독립성을 가진 재사용 가능한 코드블록이다. 여러 개의 코드 블록을 각각의 파일로 분리한 후 필요한 모듈들을 조합해 애플리케이션을 개발할 수 있다.

ES2015에서는 모듈을 js 코드를 포함하고 있는 파일이라고 간주해도 무방하다. 코드블록 안에서 import, export 구문을 이용해서 모듈을 가져오거나 내보낼 수 있다. 

모듈 내부에서 선언된 모든 변수, 함수, 객체, 클래스는 지역적인 것(local)으로 간주된다. 따라서 재사용 가능한 모듈을 만들려면 반드시 외부로 공개하고자 하는 것을 export 해야 한다. export 된 모듈은 다른 모듈에서 import 구문으로 참조하여 사용할 수 있다. export 할 수 있는 대상은 변수, 함수, 객체, 클래스 등이며 다음과 같이 export 할 수 있다.
export let a = 1000;
export function f1(a) { ... }
export { n1, n2 as othername, ... }
이와 같이 변수나 함수 등을 export 하기 위해서 export 키워드를 앞에 붙여주면 된다. 다른 방법으로는 일단 함수나 변수, 클래스를 작성한 다음 한 번에 export 하는 방법이 있다.
let var1 = 1000;
function add(a, b) {
    return a+b;
}

export { var1, add };
이제 export 한 요소들을 import 해 보자. 파일 단위로 모듈을 생성하므로 파일의 경로를 지정하면 된다. .js 확장자는 생략될 수 있다.
import { add, var1 } from './utils/utility1';

console.log(add(4, 5);
console.log(var1;
import 할 때 주의 사항은 상대경로를 사용한다는 것이다. import 할 때 이름을 변경하고 싶다면 as 예약어를 사용한다.
...
import { add, var1 as v } from '.utils/utility1';
...
모듈 단위에서 export 하는 값이 여러 개인 경우를 위해서 { add, var1 }과 같이 import 했지만, 만일 export 하는 값이 단일 값, 단일 객체, 단일 함수, 단일 클래스라면 default 키워드를 이용해 export 한 후 단일 값으로 import 할 수 있다.
let calc = {
    add(x, y) {
        return x+y;
    },
    multiply(x, y) {
        return x*y;
    }
}

export default calc;
이 예제는 단일 객체를 export 하기 위해 default 를 사용했다. 단일 객체이므로 import 할 때 import { calc } from ... 와 같이 구조분해 할당(destructuring assignment)를 사용하지 않고 import calc from ... 와 같이 단일 객체로 가져올 수 있다.
import calc from './utils/utility2';

console.log(calc.add(4, 5);
console.log(calc.multiply(4, 5);


Promise

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

ES2015에서는 Promise 객체를 지원해 비동기 처리를 좀 더 깔끔하게 수행할 수 있다. 최근 서버와 통신하기 위한 여러가지 라이브러리들은 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초 후에 나옵니다!!');
첫 행에서 Promise 객체를 생성할 때 전달하는 함수가 비동기로 실행된다. 첫 번째 인자로 전달된 resolve 함수를 호출하면 Promise 객체then 메서드에 등록된 함수가 호출되고 두 번째 인자로 전달된 reject 함수를 호출하면 Promise 객체catch 메서드에 등록된 함수가 호출된다. 이로써 비동기로 실행할 코드와 비동기 처리 결과를 받아 실행하는 코드를 분리할 수 있다.

전개 연산자(Spread Operator)

앞에서 가변 파라미터를 살펴보면서 ... 연산자를 살펴본 적이 있다. ... 연산자를 함수의 인자로 사용하면 가변 파라미터(Rest Parameter)라고 부른다. 가변 파라미터는 개별 값을 나열하여 함수의 인자로 전달하면 함수의 내부에서 배열로 사용할 수 있도록 한다. 

전개 연산자(Spread Operator)는 가변 파라미터와 사용법이 다르다. 배열이나 객체를 ... 연산자와 함께 객체 리터럴, 배열 리터럴에서 사용하면 분해된 값으로 전달한다. 아래 예제로 기능을 확인해 보자.
let obj1 = { name: '박문수', age: 29 }; 
let obj2 = { ...obj1 }; 
let obj3 = { ...obj1, email: 'mspark@test.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);

위 예제의 결과를 확인해 보면 obj1 객체의 속성이 obj2에 복제된 것을 확인할 수 있다. obj1과 obj2는 동일한 속성 값을 가지고 있지만 서로 다른 객체이다. obj2는 새로운 객체를 만든 후 obj1의 속성 값들을 obj2의 속성의 값으로 할당했을 뿐이다. 


obj3 을 정의한 코드를 보면 전개 연산자의 목적을 명확하게 이해할 수 있다. obj3은 obj1의 속성값을 모두 포함하면서 새로운 속성이 추가된 새로운 객체이다.


이와 같이 전개 연산자는 기존 객체의 속성이나 배열의 요소들을 포함하여 새로운 객체, 배열을 생성하고자 할 때 사용한다.



공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크