모던 자바스크립트
EcmaScript 2015 (ES6)
ECMA-262
1995년 4월부터 Netscape Communications Corporation에서 일을 시작한 아이크는 브라우저 내부에 Scheme
프로그래밍 언어를 추가하려고 합류하였으나, 넷스케이프 상사는 그에게 Java와 유사한 언어를 브라우저에서 사용 가능
하도록 요구하였다. 결과적으로 그는 Scheme의 기능성, Self의 객체 지향성, Java 문법을 따와 언어를 만들어 낸다.
첫 번째 버전은 Navigator 2.0 베타 릴리스 일정을 수용하기 위해 10일 만에 완료 되었으며 Mocha로 불렸지만,
1995년 9월에 LiveScript로 이름이 바뀌었고 이후 같은 달에 마케팅을 이유로 JavaScript로 바꾸었다.
Technical
Committee
변수를 선언하는 3가지 방법. 각 차이와 용도
변수 선언
변수 초기화
프로그램 내부에서 접근 가능한 영역(범위) 설정
var declaration;
declaration = '초기화, 선언된 변수에 값을 할당';
JavaScript 는 글로벌(Global), 함수(Function) 영역을 가짐
함수 내부에 선언된 변수는 함수 내부에서만 접근 가능
// 함수 선언
function getDate() {
// 함수 내부에 변수 선언 및 초기화 (함수 범위 설정)
var date = new Date();
return date;
}
// 함수 실행
getDate();
// 외부 영역에서는 함수 영역에 접근할 수 없어 오류를 발생 시킵니다.
// Uncaught ReferenceError: date is not defined at <anonymous>
console.log(date);
function getDate() {
var date = new Date();
// 함수 내, 함수 선언
function formatDate() {
// 함수 영역 내부에 존재하는 함수는 상위 함수 영역에 접근 가능합니다.
return date.toISOString().slice(0,10);
}
return formatDate();
}
console.log( getDate() ); // '2017-11-29'
// 전역
var message = 'ECMAScript v5';
console.log('전역(1) message:', message); // 'ECMAScript v5'
// 블록 영역
{
var message = 'JavaScript';
console.log('블록 {} 영역 message:', message); // ?
var msg_list = [];
console.log('블록 {} 영역 msg_list:', msg_list); // ?
}
// 함수 영역
function scope() {
var message = 'Function Scope';
console.log('함수 영역 message:', message); // ?
}
scope();
// 전역
console.log('전역(2) message:', message); // ?
console.log('전역(2) msg_list:', msg_list); // ?
JavaScript는 블록(Block) 영역을 지원하지 않습니다. 함수 영역만 지원하는 언어입니다.
예제를 살펴보며 영역에 대해 이야기 해봅니다.
// 전역
var message = 'ECMAScript v5';
console.log('전역(1) message:', message); // 'ECMAScript v5'
// 블록 영역
{
let message = 'JavaScript';
console.log('블록 {} 영역 message:', message); // ?
const msg_list = [];
console.log('블록 {} 영역 msg_list:', msg_list); // ?
}
// 함수 영역
function scope() {
let message = 'Function Scope';
console.log('함수 영역 message:', message); // ?
}
scope();
// 전역
console.log('전역(2) message:', message); // ?
console.log('전역(2) msg_list:', msg_list); // ?
ECMAScript2015(ES6) 부터는 블록(Block) 영역을 선택적으로 지원합니다.
let, const 키워드를 사용해 변수를 선언할 경우 블록 영역을 가지게 됩니다.
var fn_list = [];
// 반복문(블록 영역)
for ( var i=0, l=10; i<l; i++ ) {
fn_list.push(function(){
// 함수 영역
console.log(i);
});
console.log('반복문 내부 i:', i); // 0, 1, 2, 3, 4, 5, 6, 7, 8, 9
}
console.log('반복문 외부 i:', i); // ?
// 배열 데이터 순환 처리(콜백)
fn_list.forEach(function(f){
f(); // ?
});
반복문 내부에 함수를 정의 또는 할당하는 구문을 사용할 경우, 기대와 다른 동작에 의아해하곤 합니다.
함수의 실행 시점과 스코프 체이닝에 대해 이해를 해야만 왜 그렇게 동작 하는지 알 수 있습니다.
예제를 살펴보며 이야기를 나눠봅시다.
var fn_list = [];
// 반복문(블록 영역)
for ( var i=0, l=10; i<l; i++ ) {
// 함수 호출 없이 즉시 실행
// 클로저 생성
fn_list.push(function(i){
// 내부 함수 반환
return function(){
console.log(i); // ?
}
}(i));
}
// 배열 데이터 순환 처리(콜백)
fn_list.forEach(function(f){
f();
});
JavaScript는 임의의 조건이 갖춰지면 클로저(Closure)를 생성하는데,
이를 잘 활용하면 앞서 논했던 문제를 해결할 방법으로 사용 (난해 함이 문제지만...) 할 수 있습니다.
예제를 살펴보며 이야기를 나눠봅시다.
var fn_list = [];
// 반복문(블록 영역)
// var 대신 let 키워드로 교체
for ( let i=0, l=10; i<l; i++ ) {
fn_list.push(function(){
console.log(i); // ?
});
}
// 배열 데이터 순환 처리(콜백)
fn_list.forEach(function(f){
f();
});
반복문에 let 키워드를 사용해 블록 영역을 만들어 사용하면,
난해한 JavaScript 클로저를 사용하지 않고도 문제를 쉽게 해결할 수 있습니다.
// 함수 영역
function fire(condition) {
// 조건문(블록 영역)
if ( condition ) {
var message = 'Fire Ball!!';
console.log(message);
} else {
console.log(message);
}
}
// true 불리언 값을 전달한 경우
fire(true); // ?
// false 불리언 값을 전달한 경우
fire(false); // ?
JavaScript는 var, function 선언문을 사용할 경우, 브라우저가 해석하는 과정에서
영역 최상위로 끌어올리는 현상을 보입니다. 이러한 현상을 호이스트(Hoist)라고 부릅니다.
예제를 살펴보며 이야기를 나눠봅니다.
// 함수 영역
function fire(condition) {
// 호이스트(끌어올려짐)
var message; // 기본적으로 undefined 할당
// 조건문(블록 영역)
if ( condition ) {
message = 'Fire Ball!!';
console.log(message);
} else {
console.log(message);
}
}
// true 불리언 값을 전달한 경우
fire(true); // ?
// false 불리언 값을 전달한 경우
fire(false); // ?
브라우저 해석 과정에서 var, function 선언문은 영역 최상위로 끌어올려 코드를 다음과 같이 변경합니다.
결과를 살펴보며 이야기를 나눠봅시다.
// 함수 영역
function fire(condition) {
// 조건문(블록 영역)
if ( condition ) {
// var 대신 let 키워드로 교체
let message = 'Fire Ball!!';
console.log(message);
} else {
console.log(message);
}
}
// true 불리언 값을 전달한 경우
fire(true); // ?
// false 불리언 값을 전달한 경우
fire(false); // ?
let, const 키워드를 또한 영역 최상위로 끌어올려지지만, var 키워드 와는 다른 결과를 보입니다.
예제를 살펴보며 이야기를 나눠봅니다.
function discountPrices(prices, discount){
var discounted = [];
for ( var i=0, l=prices.length; i<l; i++ ) {
var discounted_price = prices[i] * (1 - discount);
var final_price = Math.round(discounted_price);
discounted.push( final_price );
}
console.log('i:', i); // ?
console.log('discounted_price:', discounted_price); // ?
console.log('final_price:', final_price); // ?
return discounted;
}
discountPrices([100, 300, 1200], 0.45); // ?
var 키워드를 사용할 경우 호이스트 결과를 유추해보세요.
결과를 보고 유추한 결과와 다름 없는지 확인해보세요.
function discountPrices(prices, discount){
var i;
var l;
var discounted;
var final_price;
var discounted_price;
discounted = [];
for ( i=0, l=prices.length; i<l; i++ ) {
discounted_price = prices[i] * (1 - discount);
final_price = Math.round(discounted_price);
discounted.push( final_price );
}
console.log('i:', i);
console.log('discounted_price:', discounted_price);
console.log('final_price:', final_price);
return discounted;
}
discountPrices([100, 300, 1200], 0.45); // ?
function discountPrices(prices, discount){
let discounted = [];
for ( let i=0, l=prices.length; i<l; i++ ) {
let discounted_price = prices[i] * (1 - discount);
let final_price = Math.round(discounted_price);
discounted.push( final_price );
}
console.log('i:', i); // ?
console.log('discounted_price:', discounted_price); // ?
console.log('final_price:', final_price); // ?
return discounted;
}
discountPrices([100, 300, 1200], 0.45); // ?
let 키워드를 사용할 경우 호이스트 결과를 유추해보세요.
결과를 보고 유추한 결과와 다름 없는지 확인해보세요.
function discountPrices(prices, discount){
let discounted;
discounted = [];
for ( let i=0, l=prices.length; i<l; i++ ) {
let discounted_price;
let final_price;
discounted_price = prices[i] * (1 - discount);
final_price = Math.round(discounted_price);
discounted.push( final_price );
}
console.log('i:', i); // ReferenceError
console.log('discounted_price:', discounted_price); // ReferenceError
console.log('final_price:', final_price); // ReferenceError
return discounted;
}
discountPrices([100, 300, 1200], 0.45); // ?
var, let, const 키워드를 비교해서 정리해봅시다.
var varVar;
console.log(varVar); // 초기 값: undefined 할당
varVar = 'var 변수'; // 값 교체 할당
let letVar;
console.log(letVar); // 초기 값: ReferenceError
letVar = 'let 변수'; // 값 할당
// [오류 발생] 선언 후, 값을 할당할 수 없음.
const constVar; // Uncaught SyntaxError: Missing initializer in const declaration
constVar = {type: '상수'};
var varVar = 'var 변수';
let letVar = 'let 변수';
const constVar = {type: '상수'};
// 중복 선언을 하더라도 문제 발생하지 않음.
var varVar = function(){};
// 동일한 이름이 중복 선언되면 오류 발생.
// Uncaught SyntaxError: Identifier 'letVar' has already been declared
let letVar = false;
// 상수에 초기 설정된 값을 다른 유형으로 변경하면 오류 발생.
// Uncaught TypeError: Assignment to constant variable.
constVar = [];
var 키워드를 사용해 전역에 선언할 경우, window 객체의 속성으로 접근 가능합니다.
반면 let, const 키워드를 사용할 경우는 window 객체의 속성으로 접근할 수 없습니다.
상수의 경우 값 자체를 다른 값으로 바꿀 수는 없지만,
객체/배열의 경우 값의 아이템을 추가,변경 할 수 있습니다.
var, let, const 키워드를 비교해서 정리해봅시다.
/**
* ES6를 사용하여 프로젝트를 진행한다면?
* var vs let vs const
* let https://goo.gl/kBquFB
* const https://goo.gl/TiqJRP
* ———————————————————————————————————————————————————————————
*
* var
* - 함수영역(function scope)
* - 초기 값 할당이 없으면: undefined
* - 가급적 사용하지 않는 것이 좋지만, 사용해야 한다면 Top Level에서만 사용.
* - 전역에서 선언 시, window 객체의 속성으로 접근 가능.
*
* let, const
* - 블록영역(block scope)
* - 초기 값 할당이 없으면: ReferenceError
* - 데이터 값 변경이 필요한 경우라면 let 사용 권장.
* - 전역에서 선언 해도, window 객체의 속성으로 접근 가능하지 않음.
*
* const
* - 초기 값 할당이 필수!
* - 값 유형 변경은 허용하지 않지만,
* 배열/객체 유형의 경우 새로운 아이템 추가,변경 가능.
* - 데이터 값 유형이 배열/객체일 경우 사용 권장.
*
*/
실전에서 let, const를 활용하는 예제를 실습해봅시다.
템플릿 리터럴을 활용하는 방법
템플릿 리터럴을 정리해봅시다.
/**
* ES6를 사용하여 프로젝트를 진행한다면?
* template literals
* 참고: https://goo.gl/MFz9j8
* ———————————————————————————————————————————————————————————
*
* 백틱 기호(`, backtick 또는 backquote)
* - 템플릿 구문을 읽기 쉽고, 작성이 용이하도록 만들어 줌.
* - 공백, 줄바꿈 허용.
* - 홑/쌍 따옴표를 자유롭게 사용 가능.
*
* 보간법(${}, string interpolation)
* - 포함된 JavaScript 식(Expression)을 처리하여 문자 데이터로 접합.
*
*/
실전에서 템플릿 리터럴을 활용하는 예제를 실습해봅시다.
확장된 문자 객체 능력 활용
// 플레이어 리스트(배열)
var players = 'messi ronaldo honaldo son'.split(' ');
// 함수: 문자를 포함하는 리스트 반환
function filterWordList(words, filter) {
var word_list = [];
for ( let i=0, l=words.length; i<l; ++i ) {
var word = words[i];
// 문자 포함 여부 검증
if ( word.indexOf('naldo') > -1 ) {
word_list.push(word);
}
}
return word_list;
}
// 함수 실행: 필터링 결과
var naldos = filterWordList(players, 'naldo');
// 결과 출력
console.log(naldos);
// 플레이어 리스트(배열)
const players = 'messi ronaldo honaldo son'.split(' ');
// 함수: 문자를 포함하는 리스트 반환
function filterWordList(words, filter) {
let word_list = [];
for ( let i=0, l=words.length; i<l; ++i ) {
let word = words[i];
// 문자 포함 여부 검증
if ( word.includes('naldo') ) {
word_list.push(word);
}
}
return word_list;
}
// 함수 실행: 필터링 결과
let naldos = filterWordList(players, 'naldo');
// 결과 출력
console.log(naldos);
텍스트가 포함되어있는지 여부를 불리언 값으로 반환합니다. (보다 명시적)
var kings_4 = '청룡 백호 현무 주작';
// 1. kings_4의 글자는 '백호'로 시작하는가?
kings_4.substr(0, 2) === '백호'; // false
// 2. '현무'는 kings_4 글자의 6 인덱스부터 시작하는가?
kings_4.substr(6, 2) === '현무'; // true
// 유틸리티 함수 업그레이드
function startsWith(word, find, start) {
start = start || 0;
return word.substr(start, find.length) === find;
}
startsWith(kings_4, '주작', 9); // true
어떤 문자열이 특정 문자로 시작하는지 확인하여 불리언 값으로 반환합니다.
let kings_4 = '청룡 백호 현무 주작';
// 1. kings_4의 글자는 '백호'로 시작하는가?
kings_4.startsWith('백호'); // false
// 2. '현무'는 kings_4 글자의 6 인덱스부터 시작하는가?
kings_4.startsWith('현무', 6); // true
var season = '봄 여름 가을 겨울';
// 1. season의 글자는 '겨울'로 끝나는가?
var index = season.length - 2;
season.substr(index, 2) === '겨울'; // true
// 유틸리티 함수
function endsWith(word, find, end) {
end = (end || word.length) - find.length;
var last_index = word.indexOf(find, end);
return last_index !== -1 && last_index === end;
}
endsWith(season, '겨울'); // true
// season의 글자는 '가을'이 7번째 인덱스(가을 다음 위치)에서 끝나는가?
endsWith(season, '가을', 7); // true
어떤 문자열이 특정 문자로 끝나는지 확인하여 불리언 값으로 반환합니다.
let season = '봄 여름 가을 겨울';
// 1. season의 글자는 '겨울'로 끝나는가?
season.endsWith('겨울'); // true
// season의 글자는 '가을'이 7번째 인덱스(가을 다음 위치)에서 끝나는가?
season.endsWith('가을', 7); // true
var repeat_word = '양심과 욕심 ';
function repeat(word, count) {
count = count || 0;
if (count === 0) { return ''; }
var combine = word;
while ( --count ) {
combine += word;
}
return combine;
}
repeat(repeat_word); // ''
repeat(repeat_word, 4); // '양심과 욕심 양심과 욕심 양심과 욕심 양심과 욕심 '
어떤 문자열을 특정한 개수만큼 반복하여 새로운 문자열을 반환합니다.
let repeat_word = '양심과 욕심 ';
repeat_word.repeat(); // ''
repeat_word.repeat(4); // '양심과 욕심 양심과 욕심 양심과 욕심 양심과 욕심 '
새롭게 확장된 문자열 객체의 능력을 정리해봅시다.
/**
* ES6를 사용하여 프로젝트를 진행한다면?
* string addtions
* ———————————————————————————————————————————————————————————
*
* .includes() 보다 명시적이고 의미적으로 사용 가능
* .startsWith() 임의의 텍스트로 시작하는지 여부 확인 가능
* .endsWidth() 임의의 텍스트로 끝나는지 여부 확인 가능
* .repeat() 필요한 경우, 특정 텍스트를 반복 횟수만큼 처리 가능
*
*/
화살표 함수 활용
// 데이터 유형 검증 유틸리티 함수
// 함수 선언(Declaration)
function isType(o) {
return Object.prototype.toString.call(o).toLowerCase().slice(8,-1);
}
// 함수 식(Expression)
var isType = function(o) {
return Object.prototype.toString.call(o).toLowerCase().slice(8,-1);
};
// 함수 식(Expression)
var isType = function(o) {
return Object.prototype.toString.call(o).toLowerCase().slice(8,-1);
};
// 화살표 함수 문법: 문(Statement)
let isType = (o) => {
return Object.prototype.toString.call(o).toLowerCase().slice(8,-1);
};
// 유사배열 데이터 -> 배열 유틸리티 함수
var makeArray = function(o) {
return Array.prototype.slice.call(o);
};
// 화살표 함수 문법: 식(Expression)
let makeArray = (o) => Array.prototype.slice.call(o);
// 앞서 살펴본 isType 함수 또한 식(Expression)으로 변경해보면 다음과 같습니다.
let isType = (o) => Object.prototype.toString.call(o).toLowerCase().slice(8,-1);
// 객체 합성 유틸리티 함수
var extend = function() {
var mixin = {};
makeArray(arguments).forEach(function(o) {
for ( var prop in o) {
if ( o.hasOwnProperty(prop) ) {
var v = o[prop];
if ( isType(v) !== 'object' ) {
mixin[prop] = v;
} else {
mixin[prop] = extend(mixin[prop] || {}, v);
}
}
}
});
return mixin;
};
// 객체 합성 유틸리티 함수
var extend = () => { // 매개변수가 0 또는 2개 이상일 경우, 괄호 () 사용 필수
var mixin = {};
makeArray(arguments).forEach(o => { // 매개변수가 1개일 경우, 괄호 () 생략 가능
for ( var prop in o) {
if ( o.hasOwnProperty(prop) ) {
var v = o[prop];
if ( isType(v) !== 'object' ) {
mixin[prop] = v;
} else {
mixin[prop] = extend(mixin[prop] || {}, v);
}
}
}
});
return mixin;
};
// 사용자 데이터
var users = [
{ name: '신인기', age: 21, job: '영화배우', email: 'inki@uri.io' },
{ name: '고민준', age: 36, job: '강사', email: 'mj.k@naver.com' },
{ name: '이지아', age: 28, job: '아나운서', email: 'jialee@daum.net' },
];
// 사용자 데이터 업데이트
// users 데이터를 순환 user 각 데이터 age 값 변경 후, users 덮어쓰기
users = users.map(function(user){
user.age++;
return user;
});
// 사용자 데이터 중 age 데이터만 뽑아 새로운 배열 데이터 ages 생성
var ages = users.map(function(user){
return user.age;
});
// 사용자 데이터
let users = [
{ name: '신인기', age: 21, job: '영화배우', email: 'inki@uri.io' },
{ name: '고민준', age: 36, job: '강사', email: 'mj.k@naver.com' },
{ name: '이지아', age: 28, job: '아나운서', email: 'jialee@daum.net' },
];
// 사용자 데이터 업데이트
// users 데이터를 순환 user 각 데이터 age 값 변경 후, users 덮어쓰기 → 문(Statement)
users = users.map( user => {
user.age++;
return user;
});
// 사용자 데이터 중 age 데이터만 뽑아 새로운 배열 데이터 ages 생성 → 식(Expression)
let ages = users.map( user => user.age );
// GitHub 사용자 원격저장소 데이터 중, 포크(Fork) 저장소 필터링 함수
function getGitHubUserForkRepos(userId) {
// fetch, IE8 폴리필(Poltfill): https://github.com/camsong/fetch-ie8
return fetch('https://api.github.com/users/' + userId + '/repos')
.then(function(response) {
return response.json();
})
.then(function(response) {
return response.data;
})
.then(function(repos) {
return repos.filter(function(repo) {
return repo.fork === true;
});
});
}
// GitHub 사용자 원격저장소 데이터 중, 포크(Fork) 저장소 필터링 함수
function getGitHubUserForkRepos(userId) {
// fetch, IE8 폴리필(Poltfill): https://github.com/camsong/fetch-ie8
return fetch(`https://api.github.com/users/${userId}/repos`)
.then(response => response.json())
.then(response => response.data)
.then(repos => repos.filter(repo => repo.fork === true));
}
// 객체 정의
var y9 = {
_name: 'yamoo9',
_students: [],
printStudents: function() {
// 객체 내부에서 사용된 함수 영역에서 객체에 접근할 수 있도록 _this에 참조.
var _this = this;
this._students.forEach(function(student) {
// 객체 내부 함수에서의 this 참조는 객체를 가리키지 않음.
console.log( _this._name + '은 ' + student + '학생을 알고 있습니다.' );
});
}
};
// 객체 정의
var y9 = {
_name: 'yamoo9',
_students: [],
printStudents: function() {
// forEach(fn[, thisArg]) 문법에서는 2번째 인자로 this를 대신할 인자를 전달 가능
this._students.forEach(function(student) {
console.log( this._name + '은 ' + student + '학생을 알고 있습니다.' );
}, this);
}
};
// 객체 정의
let y9 = {
_name: 'yamoo9',
_students: [],
// 객체 속성(메서드)에 화살표 함수를 사용하면 안된다.
// this 참조가 객체가 아닌, 상위 영역을 참조하기 때문이다.
printStudents: () => {
// 객체의 속성으로 화살표 함수를 사용하면
// this는 y9 객체를 참조하지 않는다.
// 고로 아래 코드는 제대로 작동하지 않는다.
// TypeError: Cannot read property 'forEach' of undefined
this._students.forEach(function(student) {
console.log(`${_this._name}은 ${student} 학생을 알고 있습니다.`);
});
}
};
// 객체 정의
let y9 = {
_name: 'yamoo9',
_students: [],
printStudents: function() {
// 객체 속성 내부에서 함수를 사용할 경우, 화살표 함수를 사용하면 유용하다.
// this 참조가 함수가 아닌 상위 영역, 즉 객체를 참조하기 때문이다.
this._students.forEach(student => {
console.log(`${this._name}은 ${student} 학생을 알고 있습니다.`);
});
}
};
// 제곱(square) 함수
function square() {
// arguments 객체 참조
var args = Array.prototype.slice.call(arguments);
var _square = function() {
var numbers = [];
// 참조된 args 객체 순환
args.forEach(function(arg){
numbers.push(arg * arg);
}
return numbers;
};
return _square();
}
// 제곱(square) 함수
function square() {
let _square = () => {
let numbers = [];
// 화살표 함수 내부 arguments 참조는 상위 영역 arguments
for (let arg of arguments) {
numbers.push(arg * arg);
}
return numbers;
};
return _square();
}
화살표 함수를 정리해봅시다.
/**
* ES6를 사용하여 프로젝트를 진행한다면?
* arrow function
* 참고: https://goo.gl/hdduQF
* ———————————————————————————————————————————————————————————
*
* 함수 표현식에서는 적극 활용
* 객체의 속성으로는 사용하지 말아야
* 객체 속성 내부에서는 적극 활용해야
* - this, arguments 상위 영역 활용
*
*/
실전에서 화살표 함수를 활용하는 예제를 실습해봅시다.
매개변수 그리고 전개 연산자
// 필수 인자 체크 함수
function isRequired(name) {
throw new Error(name + '매개변수는 전달인자 값을 필수로 요구합니다.');
}
// 사용자 전달 값 혹은 기본 매개변수 값 설정 함수
function defaultParam(param, defaults) {
return typeof param !== 'undefined' ? param : defaults;
}
// 지불 내역 계산 함수 (가격, 세금, 할인)
function calcuratePayment(price, tax, discount) {
if ( !price ) { isRequired('price'); }
tax = defaultParam(tax, 0.1);
discount = defaultParam(discount, 0);
return Math.floor( price * (1 + tax) * (1 - discount) );
}
함수 매개변수 기본 값을 설정할 수 있습니다.
// 지불 내역 계산 함수 (가격, 세금, 할인)
// 객체를 기본 값으로 사용할 경우
// 비구조 할당(Object Destructuring, ES6) 활용
function calcuratePayment( { price=isRequired('price'), tax=0.1, discount=0 } = {} ) {
return Math.floor( price * (1 + tax) * (1 - discount) );
}
calcuratePayment(); // ERROR
calcuratePayment({ price: 10000, discount: 0.165 }); // 9185
// 필수 인자 체크 함수
function isRequired(name) {
throw new Error(`${name} 매개변수는 전달인자 값을 필수로 요구합니다.`);
}
// 지불 내역 계산 함수 (가격, 세금, 할인)
function calcuratePayment( price=isRequired('price'), tax=0.1, discount=0 ) {
return Math.floor( price * (1 + tax) * (1 - discount) );
}
calcuratePayment(); // ERROR
calcuratePayment(10000, 0.1, 0.165); // 9185
// 전달된 인자의 합을 구하는 함수
function sum() {
// arguments는 배열 객체가 아니라, 유사 배열 객체이다.
for (var l=arguments.length, r=0, n; (n=arguments[--l]); ) {
r += n;
}
return r;
}
// 전달된 인자의 개수에 상관없이 사용 가능
sum(1, 3, 10); // 13
sum(29, 102, 7, 203, 10); // 351
// ES6 나머지 매개변수 & 화살표 함수(식) 활용
function sum(...nums) {
// 나머지 매개변수(Rest Parameter)는 배열 객체이다)
let r = 0;
nums.forEach(n => r += n);
return r;
}
sum(1, 3, 10); // 13
sum(29, 102, 7, 203, 10); // 351
함수의 나머지 매개변수를 한데 모아 배열 값으로 사용 가능합니다.
/// 응용편 ///
// 임의의 수에 나머지 값을 순차적으로 곱한 결과를 반환하는 함수
function n_multiply(r, ...nums) {
nums.forEach(n => r *= n);
return r;
}
// 첫번째 인자 값에 나머지 인자 값을 순차적으로 곱합
n_multiply(101, 1, 2, 3); // 606
// 전개 연산자를 사용하면 첫번째 인자를 뺀 나머지 값을 배열로 전달 가능하다.
n_multiply(29, ...[3, -1, 8, 2]); // -1392
// 정수 배열
var integer = [0, -10, 10];
// 배열 복제
var copy_integer = integer.map(function(int) {
return int;
});
// Array.prototype.slice() 메서드 활용 (https://goo.gl/tu9H3K)
// 보다 간단! (직관적이진 않음)
var copy_integer = integer.slice();
전개 연산자(...)는 함수 또는 배열 등에서 유용하게 활용됩니다.
// 정수 배열
var integer = [0, -10, 10];
// 배열 복제
var copy_integer = [...integer];
전개 연산자(...)는 함수 또는 배열 등에서 유용하게 활용됩니다.
// 정수 배열
var integer = [0, -10, 10];
// 소수 배열
var decimal = [0.8, 0.43, 0.7823];
// 순차 결합
var numbers = integer.slice().concat(decimal); // [0, -10, 10, 0.8, 0.43, 0.7823]
// 유틸리티 함수
function combineArray() {
for ( var r=[], i=0, a; (a=arguments[i++]); ) {
r = r.concat(a);
}
return r;
}
function reverseCombineArray() {
return combineArray.apply(undefined, arguments).reverse();
}
var numbers = combineArray(integer, decimal); // [0, -10, 10, 0.8, 0.43, 0.7823]
var r_numbers = reverseCombineArray(integer, decimal); // [0.7823, 0.43, 0.8, 10, -10, 0]
전개 연산자(...)는 함수 또는 배열 등에서 유용하게 활용됩니다.
// 정수 배열
var integer = [0, -10, 10];
// 소수 배열
var decimal = [0.8, 0.43, 0.7823];
// 전개 연산자(...)를 배열 앞에 붙여 사용하면 배열 원소를 전개함(열어 펼침).
var numbers = [...integer, ...decimal]; // [0, -10, 10, 0.8, 0.43, 0.7823]
var r_numbers = numbers.reverse(); // [0.7823, 0.43, 0.8, 10, -10, 0]
전개 연산자(...)는 함수 또는 배열 등에서 유용하게 활용됩니다.
// 정수
var integer = [3, 6, 9];
// 소수
var decimal = [0.9, 0.66];
// 중간 삽입 결합 (인덱스 2 위치에 삽입)
var numbers = integer.slice(), idx = 2;
decimal.forEach(function(int){ numbers.splice(idx++, 0, int) }); // [3, 6, 0.9, 0.66, 9]
// 유틸리티 함수
function insertCombineArray(o1, n, o2) {
var c = o1.slice();
o2.forEach(function (i) {
c.splice(n++, 0, i);
});
return c;
}
// 배열 인덱스 2 위치에 배열 삽입
var numbers = insertCombineArray(integer, 2, decimal); // [3, 6, 0.9, 0.66, 9]
전개 연산자(...)는 함수 또는 배열 등에서 유용하게 활용됩니다.
// 정수
var integer = [3, 6, 9];
// 소수
var decimal = [0.9, 0.66];
// 중간 삽입 결합 (인덱스 2 위치에 삽입)
var numbers = [3, 6, ...decimal, 9]
전개 연산자(...)는 함수 또는 배열 등에서 유용하게 활용됩니다.
// 멤버 데이터
var members = [
{
"gender": "male",
"name": "hudson lewis",
"email": "hudson.lewis@example.com",
"picture": "https://randomuser.me/api/portraits/thumb/men/65.jpg"
}, { ... }
];
// 새롭게 추가될 멤버 데이터
var new_members = [
{
"gender": "female",
"name": "gina reynolds",
"email": "gina.reynolds@example.com",
"picture": "https://randomuser.me/api/portraits/thumb/women/35.jpg"
}, { ... }
];
함수, 배열에 전개 연산자를 사용한 실전 예제를 다뤄봅시다.
// 커뮤니티 매니저 객체
var communityManager = {
_members: members,
// ES5: addMembers 메서드 정의
addMembers: function() {
var new_members = [].slice.call(arguments);
new_members.forEach(function(member) {
this._members.push(member);
}, this);
}
};
// ES5: 새로운 멤버들 추가
communityManager.addMembers.apply(communityManager, new_members);
함수, 배열에 전개 연산자를 사용한 실전 예제를 다뤄봅시다.
// 커뮤니티 매니저 객체
var communityManager = {
_members: members,
// ES6: addMembers 메서드 정의
addMembers: function(...members) {
this._members = [...this._members, ...members];
}
};
// ES6: 새로운 멤버들 추가
communityManager.addMembers(...new_members);
기본 매개변수와 나머지 매개변수. 그리고 전개 연산자를 정리해봅시다.
/**
* ES6를 사용하여 프로젝트를 진행한다면?
* default, rest parameters / spread operator
* default https://goo.gl/34EAkm
* rest https://goo.gl/MTHqGA
* spread https://goo.gl/KUNsTt
* ———————————————————————————————————————————————————————————
*
* 함수의 매개변수 기본 값 설정은 기존의 번거로움을 대폭 줄여준다.
* 전개 연산자(...)를 배열 또는 함수와 함께 사용하면 매우 유용하다.
* - 배열에 사용할 경우, 배열을 전개한다.
* - 함수의 매개변수에 전개 연산자를 사용할 경우, 나머지 매개변수는 배열으로 사용할 수 있다.
*
*/
실전에서 기본/나머지 매개변수 및 전개 연산자를 활용하는 예제를 실습해봅시다.
속기형 속성 설정 방법
var animations = ['원령 공주', '센과 치히로의 대모험', '명탐정 코난', '에반게리온'];
var movies = ['인디애나 존스', '살인자의 기억법', '범죄 도시'];
var music = [
{
song: '선물',
singer: '멜로망스'
},
{
song: '피카부 (Peek-A-Boo)',
singer: 'Red Velvet (레드벨벳)'
},
];
속기형 속성 작성법을 사용하면 객체의 속성 정의가 편리해집니다.
var favorites = {
animations: animations,
movies: movies,
music: music
};
속기형 속성 작성법을 사용하면 객체의 속성 정의가 편리해집니다.
var favorites = { animations, movies, music };
// 또는
var favorites = {
animations,
movies,
music
};
function isRequired(name) {
throw new Error(name + '전달인자는 필수입니다.');
}
function Mouse(name, weight, type) {
if (!name) { isRequired('name'); }
weight = weight || '100g';
type = type || 'Bluetooth';
return {
name: name,
weight: weight,
type: type
};
}
var magic_mouse_2 = new Mouse('Magic Mouse 2', '99g');
var mx_ergo = new Mouse('MX ERGO', '2g');
속기형 속성 작성법을 사용하면 객체의 속성 정의가 편리해집니다.
function isRequired(name) {
throw new Error(`${name} 전달인자는 필수입니다.`);
}
function Mouse( name=isRequired('name'), weight='100g', type='bluetooth' ) {
return { name, weigth, type };
}
const magic_mouse_2 = new Mouse('Magic Mouse 2', '99g');
const mx_ergo = new Mouse('MX ERGO', '2g');
속기형 속성에 관해 정리해봅시다.
/**
* ES6를 사용하여 프로젝트를 진행한다면?
* Shorthand Properties
* ———————————————————————————————————————————————————————————
*
* 객체의 속성, 값 이름이 동일할 경우 속기형 속성을 적극 활용하자.
*
*/
실전에서 속기형 속성을 활용하는 예제를 실습해봅시다.
향상된 객체 표기법 & 심볼 활용
var name = 'SM7';
var maker = 'Samsung';
var boost = function() {};
// car 객체 정의
var car = {
go: function() {},
stop: function() {},
boost: boost
};
// car 객체의 능력을 복사한 후,
// 자신만의 속성을 가진 객체 정의
var newbee = mixin(car, {
name: name,
maker: maker
});
console.log(newbee);
객체 표기법이 보다 향상되어 사용하기 유용해 졌습니다.
// 믹스인(객체 합성) 함수
function mixin() {
var objs = Array.prototype.slice.call(arguments);
var mixin_o = {};
objs.forEach(function(o){
for ( var p in o ) {
var v = o[p];
if ( o.hasOwnProperty(p) ) {
mixin_o[p] = v;
}
}
});
return mixin_o;
}
let name = 'SM7';
let maker = 'Samsung';
let boost = 'powerUp';
// car 객체 정의 (향상된 객체 표기법 활용)
const car = {
// 메서드
go(){},
};
객체 표기법이 보다 향상되어 사용하기 유용해 졌습니다.
let name = 'SM7';
let maker = 'Samsung';
let boost = 'powerUp';
// car 객체 정의 (향상된 객체 표기법 활용)
const car = {
// 메서드
go(){},
// 계산된 속성 (Computed Property)
['stop'](){},
[boost](){},
};
let name = 'SM7';
let maker = 'Samsung';
let boost = 'powerUp';
// car 객체 정의 (향상된 객체 표기법 활용)
const car = {
// 메서드
go(){},
// 계산된 속성 (Computed Property)
['stop'](){},
[boost](){},
};
// car 객체의 상속 받은 후,
// 자신만의 속성을 가진 객체 정의
const newbee = {
// 속기형 객체 속성 추가 방법
name,
maker,
}
let name = 'SM7';
let maker = 'Samsung';
let boost = 'powerUp';
// car 객체 정의 (향상된 객체 표기법 활용)
const car = {
// 메서드
go(){},
// 계산된 속성 (Computed Property)
['stop'](){},
[boost](){},
};
// car 객체의 상속 받은 후,
// 자신만의 속성을 가진 객체 정의
const newbee = {
// 프로토타입 객체 상속
__proto__: car,
// 속기형 객체 속성 추가 방법
name, maker,
}
let name='SM7', maker='Samsung', boost='powerUp';
// car 객체 정의 (향상된 객체 표기법 활용)
const car = {
// 메서드
go(){},
// 계산된 속성 (Computed Property)
['stop'](){},
[boost](){},
};
// car 객체의 상속 받은 후,
// 자신만의 속성을 가진 객체 정의
const newbee = {
// 프로토타입 객체 상속
__proto__: car,
// 속기형 객체 속성 추가 방법
name, maker,
// 동적 계산된 속성(Dynamic Cumputed Property)
// 'SM8SamsungPowerUp'
[`${name.replace('7','8')}${maker}${boost.slice(0,1).toUpperCase()+boost.slice(1)}`](){}
}
객체 표기법이 보다 향상되어 사용하기 유용해 졌습니다.
let name='SM7', maker='Samsung', boost='powerUp';
// car 객체 정의 (향상된 객체 표기법 활용)
const car = {
// 감춰진(private) 속성
// JavaScript 언어에서는 private를 지원하지 않아
// 이름 작성 시, _ 기호를 붙여 암시.
_wheel: 4,
// 게터(getter)
get wheel() {
return this._wheel;
},
// 세터(setter)
set wheel(new_wheel) {
this._wheel = new_wheel;
},
go(){},
['stop'](){},
[boost](){},
};
객체 표기법이 보다 향상되어 사용하기 유용해 졌습니다.
((global = window) => {
// 심볼(Symbol) 등록
// - 고유하고 수정 불가능한 데이터 타입이며 주로 객체 속성(object property)들의 식별자로 사용된다.
let _wheel = Symbol('wheel');
global.car = {
// 등록된 심볼을 속성으로 사용
[_wheel]: 4,
get wheel() {
return this[_wheel]; // 심볼 반환
},
set wheel(new_wheel) {
if ( typeof new_wheel !== 'number' ) {
throw new Error('전달 인자 유형은 숫자여야 합니다.');
}
// 계산된 값을 심볼에 할당
this[_wheel] = new_wheel > 4 ? new_wheel : 4;
},
};
})();
향상된 객체 표기법에 관해 정리해봅시다.
/**
* ES6를 사용하여 프로젝트를 진행한다면?
* Object Ehancements
* getter https://goo.gl/t3hvjJ
* setter https://goo.gl/PirNNt
* Symbol https://goo.gl/PNBLeH
* ———————————————————————————————————————————————————————————
*
* 객체 속성 및 메서드 표기법이 향상
* 계산된 (동적) 속성 표기법 활용 가능
* 객체 상속 및 활용 방법 향상
*
* getter, setter를 사용하여 계산된 속성 할당
* Symbol을 사용하여 접근 불가능한 식별자 활용
*
*/
실전에서 향상된 객체 표기법을 활용하는 예제를 실습해봅시다.
비구조화 할당
비구조화 할당(destructuring assignment) 구문은 배열 값 또는 객체 속성을
별개의 변수로 추출할 수 있게 하는 자바스크립트 식(expression)입니다.
var movie = {
name: '포레스트 검프',
director: '로버트 저메키스',
openning: '1994-10-15',
link: 'http://movie.naver.com/movie/bi/mi/basic.nhn?code=17159'
};
비구조화 할당(destructuring assignment) 구문은 배열 값 또는 객체 속성을
별개의 변수로 추출할 수 있게 하는 자바스크립트 식(expression)입니다.
// 객체의 속성 할당
var name = movie.name;
var director = movie.director;
var openning = movie.openning;
var link = movie.link;
console.log('name:', name);
console.log('director:', director);
console.log('openning:', openning);
console.log('link:', link);
// 비구조화 할당
let {name, director, openning, link} = movie;
console.log('name:', name);
console.log('director:', director);
console.log('openning:', openning);
console.log('link:', link);
비구조화 할당(destructuring assignment) 구문은 배열 값 또는 객체 속성을
별개의 변수로 추출할 수 있게 하는 자바스크립트 식(expression)입니다.
// 비구조화 할당
(({name, director, openning, link}) => {
console.log('name:', name); // '포레스트 검프',
console.log('director:', director); // '로버트 저메키스',
console.log('openning:', openning); // '1994-10-15',
console.log('link:', link); // 'http://movie.naver.com/...?code=17159'
})(window.movie);
// 비구조화 할당 (필요한 속성만 사용 가능)
(({director, openning}) => {
console.log('director:', director); // '로버트 저메키스'
console.log('openning:', openning); // '1994-10-15'
})(window.movie);
// title, art_director 변수에
// movie.name, movie.director 속성 값을 복사하고자 할 경우
var title = movie.name;
var art_director = movie.director;
console.log(title);
console.log(art_director);
비구조화 할당(destructuring assignment) 구문은 배열 값 또는 객체 속성을
별개의 변수로 추출할 수 있게 하는 자바스크립트 식(expression)입니다.
// title, art_director 변수에
// movie.name, movie.director 속성 값을 복사하고자 할 경우
let { name: title, director: art_director } = movie;
console.log(title);
console.log(art_director);
// title, art_director 변수에
// movie.name, movie.director 속성 값을 복사하고자 할 경우
(({name: title, director: art_director}) => {
console.log(title);
console.log(art_director);
})(window.movie);
var utensils = [
'그물국자',
'건지개',
'스패튤라',
'뒤집개',
'국자',
'포테이토 매셔',
];
비구조화 할당(destructuring assignment) 구문은 배열 값 또는 객체 속성을
별개의 변수로 추출할 수 있게 하는 자바스크립트 식(expression)입니다.
// 각 변수에 배열 utensils 원소 할당
var skimmer = utensils[0]; // 그물국자
var draining_spoon = utensils[1]; // 건지개
var spatula = utensils[2]; // 스패튤라
var turner = utensils[3]; // 뒤집개
var ladle = utensils[4]; // 국자
var potato_masher = utensils[5]; // 포테이토 매셔
// 배열 utensils 비구조화 할당
let [ skimmer, draining_spoon, spatula, turner ,ladle ,potato_masher ] = utensils;
비구조화 할당(destructuring assignment) 구문은 배열 값 또는 객체 속성을
별개의 변수로 추출할 수 있게 하는 자바스크립트 식(expression)입니다.
// 배열 utensils 비구조화 할당
(([ skimmer, draining_spoon, spatula, turner ,ladle ,potato_masher ]) => {
console.log(draining_spoon); // 건지개
})(window.utensils);
// 배열 utensils 비구조화 할당 (필요한 데이터만 할당)
(([ , draining_spoon, , , ,potato_masher ]) => {
console.log(draining_spoon); // 건지개
console.log(potato_masher); // 포테이토 매셔
})(window.utensils);
var people = [
{
"gender": "female",
"name": "gina reynolds",
"email": "gina.reynolds@example.com",
"picture": "https://randomuser.me/api/portraits/thumb/women/35.jpg"
}, {
"gender": "male",
"name": "leslie fisher",
"email": "leslie.fisher@example.com",
"picture": "https://randomuser.me/api/portraits/thumb/men/10.jpg"
},
{
"gender": "female",
"name": "brooke fuller",
"email": "brooke.fuller@example.com",
"picture": "https://randomuser.me/api/portraits/thumb/women/3.jpg"
},
{
"gender": "male",
"name": "کوروش کامروا",
"email": "کوروش.کامروا@example.com",
"picture": "https://randomuser.me/api/portraits/thumb/men/77.jpg"
},
{
"gender": "female",
"name": "judith gerlach",
"email": "judith.gerlach@example.com",
"picture": "https://randomuser.me/api/portraits/thumb/women/96.jpg"
},
{
"gender": "male",
"name": "hudson lewis",
"email": "hudson.lewis@example.com",
"picture": "https://randomuser.me/api/portraits/thumb/men/65.jpg"
},
{
"gender": "female",
"name": "alice french",
"email": "alice.french@example.com",
"picture": "https://randomuser.me/api/portraits/thumb/women/37.jpg"
}
];
비구조화 할당(destructuring assignment) 구문은 배열 값 또는 객체 속성을
별개의 변수로 추출할 수 있게 하는 자바스크립트 식(expression)입니다.
비구조화 할당(destructuring assignment) 구문은 배열 값 또는 객체 속성을
별개의 변수로 추출할 수 있게 하는 자바스크립트 식(expression)입니다.
// 콜백 함수 매개변수로 객체를 전달 받아
// 원하는 데이터 값을 지역 변수로 할당
people.forEach(function(person) {
var name = person.name;
var email = person.email;
console.log(name, email);
});
// 비구조화 할당 방식을 사용하여
// 콜백 함수 매개변수로 원하는 데이터만 받을 수 있음
people.forEach(({name, email}) => {
console.log(name, email);
});
비구조화 할당(destructuring assignment) 구문은 배열 값 또는 객체 속성을
별개의 변수로 추출할 수 있게 하는 자바스크립트 식(expression)입니다.
// JSON 객체(배열)의 순서에 해당 하는 변수 할당
var Brooke = people[2],
judith = people[people.length - 1];
// 이메일 출력 함수
function logEmail(o) {
// 전달받은 객체 중 email 속성 값 변수 할당
var email = o.email;
console.log(email);
}
logEmail(Brooke);
logEmail(Judith);
// 비구조화 할당 방식을 사용하여
// JSON 객체(배열)의 순서에 해당 하는 변수 설정
let [, ,Brooke, , Judith] = people;
// 이메일 출력 함수
function logEmail({email}) {
console.log(email);
}
// 이메일 출력(변수 전달)
logEmail(Brooke); // 'brooke.fuller@example.com'
logEmail(Judith); // 'judith.gerlach@example.com'
비구조화 할당에 관해 정리해봅시다.
/**
* ES6를 사용하여 프로젝트를 진행한다면?
* Destructuring Assignment
* 참고: https://goo.gl/5AjCug
* ———————————————————————————————————————————————————————————
*
* 객체 속성 또는 배열 값을 변수에 할당할 때, 비구조화 할당을 활용하면 매우 유용
*
* 객체
* - 속성(property or key) 이름과 비교하여 할당
*
* 배열
* - 원소의 순서(order)에 맞춰 할당
*
*/
실전에서 비구조화 할당을 활용하는 예제를 실습해봅시다.
클래스/상속 활용 및 데이터 보호 관리
(function(global){
'use strict';
// 비공개(Private) 멤버
var _origin = '에티오피아';
// 생성자(Constructor) 함수
function Coffee(bean) {
// 공개(Public) 멤버
this.bean = bean;
}
// 스태틱(Static) 메서드
Coffee.origin = function() { return _origin; };
// 프로토타입(Prototype) 객체
// 인스턴스(Instance) 메서드
Coffee.prototype.parch = function(time) {};
})(window);
(() => {
// 비공개 멤버
let _origin = '에티오피아';
// 클래스
class Coffee {
// 생성자
constructor(bean) {
// 공개 멤버
this.bean = bean;
}
// 스태틱 메서드
static origin() {
return _origin;
}
// 인스턴스 메서드
parch(time) {}
}
})();
프로토타입 기반의 객체 지향 프로그래밍 방법 대신, 클래스 기반의 객체 지향 프로그래밍 방법을 사용할 수 있습니다.
// 클래스 선언 이전에 사용하면 참조 오류 발생
// Uncaught ReferenceError: Bread is not defined
new Bread();
class Bread {}
ES6 사용자라면 알아두어야 할 클래스 특성
// 빵(Bread) 클래스 선언
const Bread = class {};
// 잼(Jam) 클래스 선언
const Jam = class {};
ES6 사용자라면 알아두어야 할 클래스 특성
ES6에서는 다양한 비공개 데이터 관리 방법이 존재합니다.
(() => {
class Coffee {
constructor(bean, type) {
// 공개 데이터
this.bean = bean;
// 비공개 데이터
// - 관례적 이름 규칙일 뿐, 데이터가 안전하게 보호되지 않는다.
this._type = type;
}
}
})();
ES6에서는 다양한 비공개 데이터 관리 방법이 존재합니다.
(() => {
class Coffee {
constructor(bean, type) {
// 비공개 데이터 관리
// - 완전한 데이터 비공개 관리가 가능하나, 메모리 누수가 발생한다.
Object.assign(this, {
getBean() {
return bean;
},
getType() {
return type;
}
});
}
}
})();
(() => {
// 심볼
let _bean = Symbol('bean');
class Coffee {
constructor(bean) {
// 비공개 데이터 관리
// - 기본적으로 데이터 안전이 보장되나, 완전히 보호 되지는 않음.
// - Reflect.ownKeys()로 확인이 가능하기 때문.
this[_bean] = bean;
}
get pea() {
return this[_bean];
}
set pea(new_bean) {
this[_bean] = new_bean;
}
}
})();
ES6에서는 다양한 비공개 데이터 관리 방법이 존재합니다.
(() => {
// 위크맵
let _bean = new WeakMap();
class Coffee {
constructor(bean) {
// 완벽한 보호가 가능함. 다만, 코드가 우아하지 않음.
_bean.set(this, bean);
}
get pea() {
return _bean.get(this);
}
set pea(new_pea) {
_bean.set(this, new_pea);
}
}
})();
ES6에서는 다양한 비공개 데이터 관리 방법이 존재합니다.
// Coffee 생성자 함수
function Coffee(bean) { this.bean = bean; }
// Coffee 프로토타입 객체 메서드
Coffee.prototype.parch = function(hour) { console.log(hour + '시간 만큼 '+this.bean+'을 볶다'); };
// Latte 생성자 함수 (Coffee 생성자 능력 상속)
function Latte(bean, milk) {
// super() 호출
Coffee.call(this, bean);
this.milk = milk;
}
// Latte 프로토타입 객체 ⇐ Coffee 프로토타입 객체 상속
Latte.prototype = Object.create(Coffee.prototype);
// Latte 생성자 참조 재정의
Latte.prototype.constructor = Latte;
// 메서드 오버라이드
Latte.prototype.parch = function(hour) {
Coffee.prototype.parch.call(this, hour/2);
console.log((hour/4)+'시간 만큼 '+this.milk+'를 넣고 끓인다.');
};
복잡한 프로토타입 기반 상속과 달리 ES6 클래스 기반 상속은 이해하기 쉽고 사용하기 편리합니다.
// Coffee 클래스
class Coffee {
constructor(bean) { this.bean = bean; }
parch(hour){ console.log(`${hour} 시간 만큼 ${this.bean}을 볶다`); }
}
// Latte 클래스 (Coffee 클래스 상속)
class Latte extends Coffee {
constructor(bean, milk) {
super(bean);
this.milk = milk;
}
// 메서드 오버라이드
parch(hour) {
super.parch(hour/2);
console.log(`${hour/4} 시간 만큼 ${this.milk}를 넣고 끓인다`);
}
}
Object.getPrototypeOf(Latte) === Coffee; // true
Latte.constructor === Coffee; // true
// Coffee 클래스
class Coffee {
constructor(bean) { this.bean = bean; }
parch(time){ console.log(`${time}만큼 ${this.bean}을 볶다`); }
}
// Latte 클래스 (Coffee 클래스 상속)
class Latte extends Coffee {
constructor(bean, milk) {
super(bean);
this.milk = milk;
}
// 메서드 오버라이드
parch(hour) {
super.parch(hour/2);
console.log(`${hour/4}시간 만큼 ${this.milk}를 넣고 끓인다`);
}
}
Object.getPrototypeOf(Latte) === Coffee; // true
Latte.__proto__ === Coffee; // true
// Espresso 객체(클래스 아님)
const Espresso = {
mix() { console.log('믹스(Mix)'); }
}
// CafeMocha 클래스
class CafeMocha {
constructor(bean, milk, chocolate) {}
}
// Object.setPrototypeOf()를 사용한 객체 상속
Object.setPrototypeOf(CafeMocha.prototype, Espresso);
// CafeMocha 객체 생성 후,
let cafemocha = new CafeMocha();
// Expresso 객체로 부터 상속받은 mix() 메서드 사용 가능
cafemocha.mix();
ES6에서는 클래스가 아닌 객체를 상속할 수 있습니다.
클래스 및 상속 방법 등에 대해 정리해봅시다.
/**
* ES6를 사용하여 프로젝트를 진행한다면?
* Classes
* 참고: https://goo.gl/gw9hr1
* ———————————————————————————————————————————————————————————
*
* 객체 지향 프로그래밍이 요구되는 경우, 클래스 문법 활용
* 비공개 데이터 관리 패턴을 숙지한 후, 적합한 방식 활용
* 클래스 상속 및 객체 상속 활용
*
*/
실전에서 클래스 문법을 활용하는 예제를 실습해봅시다.
모듈 활용
모듈은 재사용 가능한 코드 블록(Code Block)을 말합니다.
구현해야 할 기능을 캡슐화 하고, 공개 API를 제공하여 다른 코드에서 재사용 가능한 코드 블록입니다.
코드 추상화 : 외부 라이브러리 기능을 위임하여 사용할 때 , 복잡한 이해 없이도 사용 가능해야 합니다.
코드 캡슐화 : 코드 내부를 외부로부터 접근할 수 없도록 감출 수 있어야 합니다.
코드 재사용 : 동일한 코드를 반복하여 작성하지 않도록 재사용 가능해야 합니다.
의존성 관리 : 코드를 다시 작성하지 않고도 쉽게 의존성을 변경할 수 있어야 합니다.
웹의 탄생 시대부터 모듈이란 개념은 존재하지 않았습니다.
모듈과 비슷한 방법은 각각 나눠진 파일을 HTML 문서에서 순차적으로 로드 해야 합니다.
주의할 점은 의존 모듈 파일이 먼저 불러 와야 올바르게 작동합니다.
// 즉시 실행 함수식(IIFE) 패턴
// 캡슐화
(function(){
// 비공개
var private_var = '외부(함수 밖)에서 접근 불가';
})();
console.log(private_var); // ReferenceError: private_var is not defined
모듈 포멧은 모듈을 정의하기 위해 사용할 수 있는 문법입니다.
// 노출 모듈(Revealing Module) 패턴
// 캡슐화
// 재사용
var module = (function(){
// 비공개
var private_var = '외부에서 접근 불가, 하지만 publicMethod()로는 접근 가능';
// 외부로 공개하는 모듈
return {
publicMethod : function() { return private_var; }
};
})();
module.publicMethod(); // '외부에서 접근 불가, 하지만 publicMethod()로는 접근 가능'
console.log( module.private_var ); // undefined
모듈 포멧은 모듈을 정의하기 위해 사용할 수 있는 문법입니다.
모듈 포멧은 모듈을 정의하기 위해 사용할 수 있는 문법입니다.
// -----------------------------------
// Node.js 환경
// -----------------------------------
// Export
// ./Utils.js
module.exports = {
each: function(list, fn){
list = Array.prototype.slice.call(list);
list.forEach(fn);
}
};
// Import
// ./main.js
const utils = require('./Utils');
utils.each(document.querySelectorAll('.demo a'), function(link, index) {
console.log(index, link.textContent);
});
// Import
// ./main.js
const utils = require('./Utils');
utils.each(document.querySelectorAll('.demo a'), function(link, index) {
console.log(index, link.textContent);
});
모듈 포멧은 모듈을 정의하기 위해 사용할 수 있는 문법입니다.
// -----------------------------------
// require.js 환경
// -----------------------------------
// Export
define('jqTable', ['jquery'], function($, require, factory) {
function jqTable(){};
jqTable.register = function(){};
return jqTable;
});
// Import
define(['jqTable'], function(jqTable, require, factory) {
// 테이블 객체 생성
jqTable.register();
});
// Import
define(['jqTable'], function(jqTable, require, factory) {
// 테이블 객체 생성
jqTable.register();
});
모듈 포멧은 모듈을 정의하기 위해 사용할 수 있는 문법입니다.
// -----------------------------------
// 서버/클라이언트 환경에서 모두 사용 가능
// -----------------------------------
(function (root, factory) {
// AMD
if (typeof define === 'function' && define.amd) {
define(['module'], factory);
}
// CommonJS
else if (typeof module === 'object' && module.exports) {
module.exports = factory(require('module'));
}
// Browser
else {
root.returnExports = factory(root.module);
}
}(this, function (module) {
// 모듈
return {};
}));
모듈 포멧은 모듈을 정의하기 위해 사용할 수 있는 문법입니다.
(function (factory) {
// AMD
if (typeof define === 'function' && define.amd) {
define(['jquery'], factory);
}
// CommonJS
else if (typeof module === 'object' && module.exports) {
module.exports = function( root, jQuery ) {
if ( jQuery === undefined ) {
jQuery = ( typeof window !== 'undefined' ) ?
require('jquery') :
require('jquery')(root);
}
factory(jQuery);
return jQuery;
};
}
// Browser
else { factory(jQuery); }
}(function ($) {
$.fn.jqueryPlugin = function () { return this.addClass('assign jquery plugin'); };
}));
모듈 포멧은 모듈을 정의하기 위해 사용할 수 있는 문법입니다.
// -----------------------------------
// ES6를 지원하는 환경
// -----------------------------------
// Export
// ./Utils.js
let private_data = '비공개 데이터';
// JavaScript 모든 데이터 유형을 내보낼 수 있음.
export const utils = {
each(list, fn) { Array.from(list).each(fn); },
};
// Import
// ./main.js
import { utils } from './Utils';
const lis = document.querySelectorAll('ul.demo li');
const li_htmls = utils.each(lis, item => item.innerHTML);
// Import
// ./main.js
import { utils } from './Utils';
const lis = document.querySelectorAll('ul.demo li');
const li_htmls = utils.each(lis, item => item.innerHTML);
모듈 포멧은 모듈을 정의하기 위해 사용할 수 있는 문법입니다.
// -----------------------------------
// ES6를 지원하는 환경
// -----------------------------------
// Export
// ./Utils.js
const utils = {
map(list, fn) { return Array.from(list).map(fn) },
};
export default utils;
// Import
// ./main.js
import utils from './Utils';
const lis = document.querySelectorAll('ul.demo li');
const li_htmls = utils.map(lis, item => item.innerHTML);
// Import
// ./main.js
import utils from './Utils';
const lis = document.querySelectorAll('ul.demo li');
const li_htmls = utils.map(lis, item => item.innerHTML);
모듈 포멧은 모듈을 정의하기 위해 사용할 수 있는 문법입니다.
// -----------------------------------
// ES6를 지원하는 환경
// -----------------------------------
// Export
// ./Utils.js
export default {...};
export function randomNumber(n) { ... }
export class Utillity { ... };
// Import
// ./main.js
import utils, {randomNumber, Utillity} from './Utils';
const children = document.body.children;
utils.each(children, el => el.classList.add(`utils-${randomNumber(100)}`));
const el_classes = utils.map(children, el => el.getAttribute('class'));
new Utillity(); // Utillity 객체 생성
// Import
// ./main.js
import utils, {randomNumber, Utillity} from './Utils';
const children = document.body.children;
utils.each(children, el => el.classList.add(`utils-${randomNumber(100)}`));
const el_classes = utils.map(children, el => el.getAttribute('class'));
new Utillity(); // Utillity 객체 생성
모듈 포멧은 모듈을 정의하기 위해 사용할 수 있는 문법입니다.
// -----------------------------------
// ES6를 지원하는 환경
// -----------------------------------
// Export
// ./Utils.js
export default {...};
export function randomNumber(n) { ... }
export class Utillity {};
// Import
// ./main.js
import utils, {randomNumber as rn, Utillity as Util} from './Utils';
const children = document.body.children;
utils.each(children, el => el.classList.add(`utils-${rn(100)}`));
const el_classes = utils.map(children, el => el.getAttribute('class'));
new Util(); // Utillity 객체 생성
// Import
// ./main.js
import utils, {randomNumber as rn, Utillity as Util} from './Utils';
const children = document.body.children;
utils.each(children, el => el.classList.add(`utils-${rn(100)}`));
const el_classes = utils.map(children, el => el.getAttribute('class'));
new Util(); // Utillity 객체 생성
모듈 포멧은 모듈을 정의하기 위해 사용할 수 있는 문법입니다.
// -----------------------------------
// ES6를 지원하는 환경
// -----------------------------------
// Export
// ./Utils.js
export {...};
export function randomNumber(n) { ... }
export class Utillity {};
// Import
// ./main.js
import * as $ from './Utils';
const children = document.body.children;
$.utils.each(children, el => el.classList.add(`utils-${ $.randomNumber(100) }`));
const el_classes = $.utils.map(children, el => el.getAttribute('class'));
new $.Utillity(); // Utillity 객체 생성
// Import
// ./main.js
import * as $ from './Utils';
const children = document.body.children;
$.utils.each(children, el => el.classList.add(`utils-${ $.randomNumber(100) }`));
const el_classes = $.utils.map(children, el => el.getAttribute('class'));
new $.Utillity(); // Utillity 객체 생성
사용 환경 및 용도에 따라 적합한 모듈 포멧을 사용해야 합니다.
모듈 로더 및 번들러는 환경에 따라 선택 사용할 수 있습니다.
RequireJS : 자바스크립트 파일 모듈 로더 (클라이언트 환경)
SystemJS : ES6 모듈 문법을 사용한 로더 (서버/클라이언트 모든 환경)
모듈 로더는 클라이언트 환경에서 런타임(실행) 중에 모듈을 로드 합니다.
Browserify : CommonJS 방식의 모듈 번들러. (번들링 후에는 클라이언트 환경에서 사용 가능)
모듈 번들러는 서버 환경에서 빌드 과정에서 모듈을 로드하고 번들링 합니다.
rollup.js : 자바스크립트 모듈 번들러로 작은 코드를 라이브러리나 애플리케이션과 같이 더 크고 복잡한 코드로 컴파일합니다.
모듈에 대해 정리해봅시다.
/**
* ES6를 사용하여 프로젝트를 진행한다면?
* Modules
* import https://goo.gl/gQ6KPT
* export https://goo.gl/VsEGFf
* ———————————————————————————————————————————————————————————
*
* 모듈 단위 별, 독립 개발 지향
* 모듈 포멧에 대한 이해 [CommonJS, AMD, UMD, ES6 Modules]
* 주의! ES6 Modules는 현재 브라우저에서 제대로 지원이 안된다.
* 모듈 로더 또는 번들러를 사용하여 프로젝트에 반영해야 한다.
*
* 권장 방식
* - ES6 export, import
* - Webpack
*
*/
실전에서 ES6 모듈을 활용하는 예제를 실습해봅시다.
반복 가능한 객체를 순환하는 새로운 반복문 및 제너레이터 활용
// 배열
var sports_shoes = ['조깅화', '축구화', '농구화'];
// for문
for ( var i=0, l=sports_shoes.length; i<l; i++ ) {
if ( sports_shoes[i] === '축구화' ) { continue; }
console.log(sports_shoes[i]);
}
// for~in문 (주의! 느림, 배열이 아닌 객체 순환 용도)
for ( var i in sports_shoes ) {
if ( sports_shoes[i] === '축구화' ) { break; }
console.log(sports_shoes[i]);
}
// forEach문 (주의! 기본적으로 배열만 활용 가능)
sports_shoes.forEach(function(shoes, index) {
// 문법 오류: forEach문은 break, continue 사용 X
// if ( shoes === '축구화' ) { continue; }
console.log(sports_shoes[i]);
});
ES5에서 자주 사용되는 반복문은 for, for ~ in, forEach() 등이 있습니다.
// 반복 가능한 객체(배열, 유사 배열, 문자열, 맵, 세트 등)
let sports_shoes = ['조깅화', '축구화', '농구화'];
// 반복 가능한 객체 순환
for (let shoes of sports_shoes) {
console.log(shoes);
// '조깅화'
// '축구화'
// '농구화'
}
ES6부터 새롭게 지원하는 반복문으로 for ~ in, forEach() 등을 대체합니다.
// break, continue 사용 가능
for (let shoes of sports_shoes) {
if ( shoes === '축구화' ) { continue; }
console.log(shoes);
// '조깅화'
// '농구화'
}
// 반복 가능한 객체(배열, 유사배열 객체, 문자열, 맵, 세트 등)
let sports_shoes = ['조깅화', '축구화', '농구화'];
ES6부터 새롭게 지원하는 반복문으로 for, for ~ in, forEach() 등을 대체할 수 있습니다.
// [].entries() → Array Iterator {}
for (let [index, item] of sports_shoes.entries()) {
console.log(`index: ${index}, item: ${item}`);
// index: 0, item: 조깅화
// index: 1, item: 축구화
// index: 2, item: 농구화
}
// [...arguments].entries() → Array Iterator {}
function loopArguments() {
let args = [...arguments].entries();
for ( let [i, arg] of args ) {
console.log(`${i} => ${arg}`);
// 0 => first
// 1 => []
}
}
loopArguments('first', []);
// 전개 연산자(...)를 사용할 경우
function loopArguments(...args) {
for (let [i, arg] of args.entries()){
// ...
}
}
// 배열 객체
let sports_shoes = ['조깅화', '축구화', '농구화'];
ES6부터 새롭게 지원하는 반복문으로 for ~ in, forEach() 등을 대체합니다.
// 유사 배열 객체
let like_array_obj = { length: 3, 0: '조깅화', 1: '축구화', 2: '농구화' };
// 유사 배열 객체는 반복 가능한 객체(Iterator)가 아님.
// for ~ of 문을 사용할 수 없음.
for ( let v of like_array_obj ) {
// Uncaught TypeError: like_array_obj is not iterable
}
// 유사 배열 객체를 반복 가능한 객체(Iterator)로 변경해야 for ~ of문 사용 가능.
// [...] 구문은 사용하면 오류. Array.from() 메서드 사용해야 함.
for ( let v of Array.from(like_array_obj).entries() ) {
console.log(v);
// [0, "조깅화"]
// [1, "축구화"]
// [2, "농구화"]
}
// 배열 객체
let print_sports_shoesFn = [];
ES6부터 새롭게 지원하는 반복문으로 for ~ in, forEach() 등을 대체합니다.
// const, let 동일한 결과
for ( const shoes of ['조깅화', '축구화', '농구화'] ) {
print_sports_shoesFn.push( () => shoes );
}
console.log( print_sports_shoesFn.map(f => f()) ); // ["조깅화", "축구화", "농구화"]
console.log(shoes); // ReferenceError: shoes is not defined
// var 를 사용할 경우, 문제 발생.
for ( var shoes of ['조깅화', '축구화', '농구화'] ) {
print_sports_shoesFn.push( () => shoes );
}
console.log( print_sports_shoesFn.map(f => f()) ); // ["농구화", "농구화", "농구화"]
console.log(shoes); // 농구화
ES6부터 새롭게 추가된 프로토콜인 반복 가능한(Iterable) 객체와 연속된 값을 만드는 표준 방법 정의(Iterator)에 관한 개념 이해가 필요합니다.
반복 가능한 빌트인 객체는 Array, Array-Like, String, Typed Array, Set, Map, WeakSet, WeakMap 등이 있습니다.
이와 같은 객체는 for ~ of문을 통해 값들을 반복하여 처리하는 동작을 정의하거나 사용자 정의하는 것을 허용합니다.
반복 가능한 조건은 객체에 @@iterator 메소드가 구현되어 있어야 합니다.
즉, 객체가 [Symbol.iterator] 속성(메서드)을 가져야 하며 인자 없이 호출되고 Iterator 객체를 반환해야 합니다.
// 반복 가능한 객체
const iterable_obj = {
// 반복 가능한 객체의 조건
[Symbol.iterator]: function() {
// Iterator 객체 반환
}
};
ES6부터 새롭게 추가된 프로토콜인 반복 가능한(Iterable) 객체와 연속된 값을 만드는 표준 방법 정의(Iterator)에 관한 개념 이해가 필요합니다.
객체가 next 메서드를 통해 { value, done }을 반환합니다.
value는 JavaScript의 모든 데이터 유형이 가능하며 done은 Boolean 값을 반환합니다.
const iterable_obj = {
// 반복 가능한 객체의 조건
[Symbol.iterator]: function() {
// Iterator 객체 반환
return {
// next 메서드를 가짐 (value, done 속성을 가진 객체 반환)
next: function() {
return { value, done };
}
};
}
};
ES6부터 새롭게 추가된 프로토콜인 반복 가능한(Iterable) 객체와 연속된 값을 만드는 표준 방법 정의(Iterator)에 관한 개념 이해가 필요합니다.
// 반복 가능한(iterable) 빌트인 객체: Array
const array = [99, 9, 0];
// Iterator 객체 참조
const iterator = array[Symbol.iterator]();
// Iterator 객체의 next 메서드 사용
iterator.next(); // {value: 99, done: false}
iterator.next(); // {value: 9, done: false}
iterator.next(); // {value: 0, done: false}
iterator.next(); // {value: undefined, done: true}
ES6부터 새롭게 추가된 프로토콜인 반복 가능한(Iterable) 객체와 연속된 값을 만드는 표준 방법 정의(Iterator)에 관한 개념 이해가 필요합니다.
// 일반 객체를 반복 가능한 객체로 변경하기 위해서는
// 반복 가능한(iterable) 객체의 조건이 필요하다.
const o = {
propA: 'A',
propB: 'B',
// 반복 가능한 객체의 조건
[Symbol.iterator]() {
// 속성 값 참조를 위한 인덱스 변수
let i = 0;
// 객체의 속성(key) 집합 정렬 후 변수에 참조
let keys = Object.keys(this).sort();
return {
// next 메서드
next() {
// {value, done} 객체 반환
return { value: keys[i], done: i++ >= keys.length }
}
};
}
};
// Iterator 객체 참조
const o_iterator = o[Symbol.iterator]();
// Iterator 객체의 next 메서드 사용
o_iterator.next(); // { value: 'propA', done: false }
o_iterator.next(); // { value: 'propB', done: false }
o_iterator.next(); // { value: undefined, done: true }
Generator 객체는 제너레이터 함수로 부터 반환된 값이며 반복자와 반복자 프로토콜을 준수합니다.
// 제너레이터 함수
function* idMaker(){
let index = 0;
while(true) {
yield index++;
}
}
// 제너레이터 객체 참조
var gen = idMaker(); // "Generator { }"
// 제너레이터 객체의 next() 메서드 사용
console.log(gen.next().value); // 0
console.log(gen.next().value); // 1
console.log(gen.next().value); // 2
// ...
Generator 객체는 제너레이터 함수로 부터 반환된 값이며 반복자와 반복자 프로토콜을 준수합니다.
// 반복 가능한(iterable) 객체
const o = {
propA: 'A',
propB: 'B',
// 반복 가능한 객체의 조건
// 제너레이터 함수
[Symbol.iterator]: function* () {
// 객체의 속성(key) 집합 정렬 후 변수에 참조
let keys = Object.keys(this).sort();
// keys 반복 가능한 객체를 순환
for (let key of keys) {
// 제너레이터 객체의 next() 메서드를 사용할 때마다
// 제너레이터를 멈추고 값을 반환
yield key;
}
}
};
// Iterator 객체 참조
const o_iterator = o[Symbol.iterator]();
// Iterator 객체의 next 메서드 사용
o_iterator.next(); // { value: 'propA', done: false }
o_iterator.next(); // { value: 'propB', done: false }
o_iterator.next(); // { value: undefined, done: true }
Generator 객체는 제너레이터 함수로 부터 반환된 값이며 반복자와 반복자 프로토콜을 준수합니다.
// 피보나치 수열을 반환하는 제너레이터 함수
function* fibonacci(n=1) {
// current, next 변수 초기화
let current = 0;
let next = 1;
// 조건이 거짓일 때까지 반복
while(n--) {
// 제너레이터를 멈춘 후 반환하는 값
yield current;
// current, next 업데이트
[current, next] = [next, current + next];
}
}
// 피보나치 수열 제너레이터 참조
let fibo5 = fibonacci(5);
// next() 메서드의 반환 값 출력
console.log(fibo5.next().value); // 0
console.log(fibo5.next().value); // 1
console.log(fibo5.next().value); // 1
console.log(fibo5.next().value); // 2
console.log(fibo5.next().value); // 3
console.log(fibo5.next().value); // undefined
// 피보나치 수열을 값으로 하는 배열 참조 (제너레이터 함수, 비 구조화 할당 사용)
// [] 내부에서 전개연산자(...)를 사용하면 Iterator 객체를 순환 처리
let [...fibo14] = fibonacci(14);
// [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233]
for ~ of 문을 정리해봅시다.
/**
* ES6를 사용하여 프로젝트를 진행한다면?
* for ~ of
* Iterator
* Generator
*
* for~of https://goo.gl/QJS81J
* Iterator https://goo.gl/tCLY7a
* Generator https://goo.gl/oLJ8Fz
* ———————————————————————————————————————————————————————————
*
* for 문은 코드 작성에 수고가 많이 들고,
* for ~ in 문은 객체를 순환할 때나 사용하고,
* forEach 문은 코드가 간결하나 break, continue 사용할 수 없는데 반해
* for ~ of 문은 코드가 간결하고 break, continue 사용할 수 있는 장점을 지님.
*
* 반복 가능한(iterable) 객체와 반복자(iterator)를 통해 객체 속성 순환.
*
* 제너레이터(geterator), yield를 활용하면 강력한 반복 알고리즘 처리 가능.
*
*/
확장된 배열 객체 능력 활용
// DOM 객체 수집(Collection) = NodeList
// lis 변수에 참조된 값은 length 속성을 가진 유사 배열 객체
var lis = document.querySelectorAll('ul.demo li');
// 유틸리티 함수
function makeArray(o) {
return Array.prototype.slice.call(o);
}
// 유틸리티 함수 makeArray()를 사용하여 lis 유사 배열을 배열로 변경
makeArray(lis).forEach(function (li) {
console.log(li); // <li> 순환
});
// DOM 객체 수집(Collection) = NodeList
// lis 변수에 참조된 값은 length 속성을 가진 유사 배열 객체
var lis = document.querySelectorAll('ul.demo li');
// Array.from() 네이티브 Array 메서드를 사용하여 lis 유사 배열을 배열로 변경
Array.from(lis).forEach(li => console.log(li)); // <li> 순환
배열 생성자 메서드(Static Methods)를 사용해 배열 또는 배열과 유사한 객체를 손쉽게 활용할 수 있습니다.
// 전개 연산자(...)를 사용할 수도 있다.
[...lis].forEach(li => console.log(li)); // <li> 순환
// DOM 객체 수집(Collection) = NodeList
// links 변수에 참조된 값은 length 속성을 가진 유사 배열 객체
var links = document.querySelectorAll('ul.demo a');
// Array 객체의 .map() 메서드를 빌려 links에 사용
var links_content = Array.prototype.map.call(links, function (link) {
return link.textContent;
});
// DOM 객체 수집(Collection) = NodeList
// links 변수에 참조된 값은 length 속성을 가진 유사 배열 객체
var links = document.querySelectorAll('ul.demo a');
// Array.from() 메서드를 사용하여 links 객체를 배열로 변경 후, .map() 메서드 사용
var links_content = Array.from(links).map(link => link.textContent);
배열 생성자 메서드(Static Methods)를 사용해 배열 또는 배열과 유사한 객체를 손쉽게 활용할 수 있습니다.
// 0부터 100까지 채운 배열이 필요할 경우?
var array_101 = [];
for ( var i=0, l=100; i<=l; ++i ) {
array_101[i] = i;
}
console.log(array_101); // [0, 1, 2, ..., 100]
// 0부터 100까지 채운 배열이 필요할 경우?
// Array.from(arrayLike, mapFunc?, thisArg?)
const array_101 = Array.from(new Array(101), (x,i) => i); // [0, 1, 2, ..., 100]
배열 생성자 메서드(Static Methods)를 사용해 배열 또는 배열과 유사한 객체를 손쉽게 활용할 수 있습니다.
// new Array() 구문에 첫번째 인자로 숫자를 사용할 경우
// 기대와 다른 결과를 확인할 수 있다.
var dataList = new Array(3); // [undefined, undefined, undefined]
console.log(dataList.length); // 3
// 첫번째 인자로 소수점을 포함하는 숫자를 전달할 경우 오류가 발생한다.
// new Array() 에 첫번째로 전달된 숫자를 포함하는 아이템 개수로 설정하기 때문.
var dataList = new Array(2.1); // Uncaught RangeError: Invalid array length
// Array 리터럴을 사용할 경우
// 기대와 같은 결과를 확인할 수 있어
// 대부분의 경우 리터럴 사용을 권장한다.
var dataList = [3]; // [3]
console.log(dataList.length); // 1
// Array.of(...items) 메서드를 사용할 경우
// new Array() 구문에서 살펴본 기대 밖의 결과를 비켜갈 수 있다.
const data = Array.of(3); // [3]
console.log(data.length); // 1
// Array.of(item1, item2, ..., itemN)
// HTML 자식 요소(Children) 수집(배열)
let html_children = Array.of(document.body, document.head);
for (let [index, child] of html_children.entries()) {
console.log(child);
// <body></body>
// <head></head>
}
// Array.of(item1, item2, ..., itemN)
// HTML 자식 요소(Children) 수집(배열)
let html_children = Array.of(document.body, document.head);
for ( let child of html_children ) {
console.log(child);
// <body></body>
// <head></head>
}
배열 생성자 메서드(Static Methods)를 사용해 배열 또는 배열과 유사한 객체를 손쉽게 활용할 수 있습니다.
var numbers = [100, 105, 103, 109];
// for문
for ( var i=0, l=numbers.length; i<l; i++ ) {
console.log(i, numbers[i]);
// 0, 100
// 1, 105
// 2, 103
// 3, 109
}
// forEach문
numbers.forEach(function(n, i) {
console.log(i, n);
// 0, 100
// 1, 105
// 2, 103
// 3, 109
});
유용한 Array 객체 인스턴스 메서드가 새롭게 추가되었습니다.
let numbers = [100, 103, 108, 105];
// for ~ of문 + Array.prototype.keys
for ( let index of numbers.keys() ) {
console.log(index);
// 0
// 1
// 2
// 3
}
// for ~ of문 + Array.prototype.values
for ( let value of numbers.values() ) {
console.log(value);
// 100
// 103
// 108
// 105
}
// for ~ of문 + Array.prototype.entries
for ( let [index, value] of numbers.entries() ) {
console.log(index, value);
// 0, 100
// 1, 103
// 2, 108
// 3, 105
}
// for ~ of문 + Array.prototype.entries
for ( let [index, value] of numbers.entries() ) {
console.log(index, value);
// 0, 100
// 1, 103
// 2, 108
// 3, 105
}
let numbers = [100, 105, 103, 109];
// Array.prototype.keys
console.log( Array.from(numbers.keys()) ); // 0, 1, 2, 3
// Array.prototype.values
console.log( Array.from(numbers.values()) ); // 100, 105, 103, 109
// Array.prototype.entries
// Array.from() 대신 전개 연사자(...)를 사용해도 된다.
console.log( [...numbers.entries()] );
/*
[
[0, 100],
[1, 105],
[2, 103],
[3, 109]
]
*/
유용한 Array 객체 인스턴스 메서드가 새롭게 추가되었습니다.
var numbers = [100, 105, 103, 109];
// 배열 아이템을 찾는 유틸리티 함수
function findItemArray(array, cb) {
for ( var i=0, l=array.length; i<l; i++ ) {
if ( cb(array[i], i, array) ) { return array[i] }
}
}
// 유틸리티 함수를 사용해 조건에 부합하는 첫번째 아이템 반환
var item = findItemArray(numbers, function(item, index, array) {
return item > 100 && item < 105;
});
console.log(item); // 103
var numbers = [100, 105, 103, 109];
// Array.prototype.find 메서드 사용
var item = numbers.find(item => item > 100 && item < 105);
console.log(item); // 103
유용한 Array 객체 인스턴스 메서드가 새롭게 추가되었습니다.
var numbers = [100, 105, 103, 109];
// 배열 아이템 인덱스를 찾는 유틸리티 함수
function findItemIndexArray(array, cb) {
for ( var i=0, l=array.length; i<l; i++ ) {
if ( cb(array[i], i, array) ) { return i; }
}
return -1;
}
// 유틸리티 함수를 사용해 조건에 부합하는 첫번째 아이템 인덱스를 반환
var item = findItemIndexArray(numbers, function(item) {
return item > 105;
});
console.log(item); // 3
var numbers = [100, 105, 103, 109];
// Array.prototype.findIndex 메서드 사용
var item = numbers.findIndex(item => item > 105);
console.log(item); // 3
유용한 Array 객체 인스턴스 메서드가 새롭게 추가되었습니다.
// Array.prototype.indexOf
[false, 10, NaN, {}].indexOf(10); // 1
// Array.prototype.findIndex
[false, 10, NaN, {}].findIndex(x => x === 10); // 1
// Array.prototype.indexOf
// === 비교
// NaN === NaN // false
[false, 10, NaN, {}].indexOf(NaN); // -1
// Array.prototype.findIndex
// Object.is() 비교
// Object.is(NaN, NaN) // true
[false, 10, NaN, {}].findIndex(x => Object.is(x, NaN)); // 2
유용한 Array 객체 인스턴스 메서드가 새롭게 추가되었습니다.
var numbers = [100, 105, 103, 109];
// 배열 아이템이 포함 되었는지 확인하는 유틸리티 함수
function isIncludeItemArray(array, item) {
return array.indexOf(item) > -1;
}
// 유틸리티 함수를 사용해 아이템이 포함 되었는지 유무 확인
if ( !isIncludeItemArray(numbers, 107) ) {
numbers.push(107);
}
var numbers = [100, 105, 103, 109];
// Array.prototype.includes 메서드를 사용해 아이템이 포함 되었는지 유무 확인
if ( !numbers.includes(107) ) {
numbers.push(107);
}
유용한 Array 객체 인스턴스 메서드가 새롭게 추가되었습니다.
var numbers = [100, 105, 103, 109];
// 배열 아이템을 모두 동일하게 채우는 유틸리티 함수
function fillItemArray(array, item, start, end) {
start = start || 0;
end = end || array.length;
return array.map(function(t, i){
if ( i >= start && i < end ) {
return item;
} else {
return t;
}
});
}
// 유틸리티 함수를 사용해 배열 아이템을 모두 교체
fillItemArray(numbers, {}); // [{}, {}, {}, {}]
// 유틸리티 함수에 start, end 인자를 전달하면
// 조건에 부합하는 아이템만 교체
fillItemArray(numbers, {}, 1, 3); // [100, {}, {}, 109]
var numbers = [100, 105, 103, 109];
// Array.prototype.fill 메서드를 사용해 배열 아이템을 모두 교체
numbers.fill({}); // [{}, {}, {}, {}]
// Array.prototype.fill 메서드를 사용해 배열 아이템을 부분 교체
numbers.fill({}, 1, 3);
// Array.from() 메서드 활용
// 모두 교체
numbers = Array.from(numbers, x => ({})); // [{}, {}, {}, {}]
// 부분 교체
let start = 1, end = 3;
numbers = Array.from(numbers, (x,i) => {
if ( i > start && i < end ) {
return {};
} else {
return x;
}
});
유용한 Array 객체 인스턴스 메서드가 새롭게 추가되었습니다.
var numbers = [100, 105, 103, 109];
// Array.prototype.copyWithin(target, start=0, end=this.length)
// 0부터 (4-1)까지 아이템을 복사한 후, 1 위치부터 붙여넣음
numbers.copyWithin(1); // target: 1, start: 0, end: 4
// [100, 105, 103, 109] → [100, 100, 105, 103]
// 0부터 (4-1)까지 아이템을 복사한 후, -2(끝에서 2번째) 위치부터 붙여넣음
numbers.copyWithin(-2); // target: -2, start: 0, end: 4
// [100, 105, 103, 109] → [100, 105, 100, 105]
// 2부터 (4-1)까지 아이템을 복사한 후, 1 위치부터 붙여넣음
numbers.copyWithin(1, 2); // target: 1, start: 2, end: 4
// [100, 105, 103, 109] → [100, 103, 109, 109]
// 1부터 (2-1)까지 아이템을 복사한 후, 2 위치부터 붙여넣음
numbers.copyWithin(2, 1, 2); // target: 2, start: 1, end: 2
// [100, 105, 103, 109] → [100, 105, 105, 109]
// -2부터 -3까지 아이템을 복사한 후, -3(끝에서 3번째) 위치부터 붙여넣음
numbers.copyWithin(-3, -2); // target: -2, start: -2, end: 4
// [100, 105, 103, 109] → [100, 103, 109, 109]
// 0부터 (4-1)까지 아이템을 복사한 후, -2(끝에서 2번째) 위치부터 붙여넣음
numbers.copyWithin(-2); // target: -2, start: 0, end: 4
// [100, 105, 103, 109] → [100, 105, 100, 105]
// 2부터 (4-1)까지 아이템을 복사한 후, 1 위치부터 붙여넣음
numbers.copyWithin(1, 2); // target: 1, start: 2, end: 4
// [100, 105, 103, 109] → [100, 103, 109, 109]
// 1부터 (2-1)까지 아이템을 복사한 후, 2 위치부터 붙여넣음
numbers.copyWithin(2, 1, 2); // target: 2, start: 1, end: 2
// [100, 105, 103, 109] → [100, 105, 105, 109]
// -2부터 -3까지 아이템을 복사한 후, -3(끝에서 3번째) 위치부터 붙여넣음
numbers.copyWithin(-3, -2); // target: -2, start: -2, end: 4
// [100, 105, 103, 109] → [100, 103, 109, 109]
// 2부터 (4-1)까지 아이템을 복사한 후, 1 위치부터 붙여넣음
numbers.copyWithin(1, 2); // target: 1, start: 2, end: 4
// [100, 105, 103, 109] → [100, 103, 109, 109]
// 1부터 (2-1)까지 아이템을 복사한 후, 2 위치부터 붙여넣음
numbers.copyWithin(2, 1, 2); // target: 2, start: 1, end: 2
// [100, 105, 103, 109] → [100, 105, 105, 109]
// -2부터 -3까지 아이템을 복사한 후, -3(끝에서 3번째) 위치부터 붙여넣음
numbers.copyWithin(-3, -2); // target: -2, start: -2, end: 4
// [100, 105, 103, 109] → [100, 103, 109, 109]
// 1부터 (2-1)까지 아이템을 복사한 후, 2 위치부터 붙여넣음
numbers.copyWithin(2, 1, 2); // target: 2, start: 1, end: 2
// [100, 105, 103, 109] → [100, 105, 105, 109]
// -2부터 -3까지 아이템을 복사한 후, -3(끝에서 3번째) 위치부터 붙여넣음
numbers.copyWithin(-3, -2); // target: -2, start: -2, end: 4
// [100, 105, 103, 109] → [100, 103, 109, 109]
// -2부터 -3까지 아이템을 복사한 후, -3(끝에서 3번째) 위치부터 붙여넣음
numbers.copyWithin(-3, -2); // target: -3, start: -2, end: 4
// [100, 105, 103, 109] → [100, 103, 109, 109]
유용한 Array 객체 인스턴스 메서드가 새롭게 추가되었습니다.
배열 객체와 관련된 새로운 능력을 정리해봅시다.
/**
* ES6를 사용하여 프로젝트를 진행한다면?
* Array Additions
*
* Array.from https://goo.gl/WNGQDb
* Array.of https://goo.gl/D2KBsy
* Array.prototype.keys https://goo.gl/bVcAC9
* Array.prototype.values https://goo.gl/cLuXnW
* Array.prototype.entries https://goo.gl/WHDSGo
* Array.prototype.find https://goo.gl/24Hnf9
* Array.prototype.findIndex https://goo.gl/KnqK13
* Array.prototype.includes https://goo.gl/9o8Vf9
* Array.prototype.fill https://goo.gl/NNJp5k
* Array.prototype.copyWithin https://goo.gl/dhggqg
* ———————————————————————————————————————————————————————————
*
* 배열은 데이터 관리에 자주 사용되는 객체로 새롭게 추가된
* 스태틱 메서드, 프로토타입 인스턴스 메서드를 적극 활용
*
*/
세트, 맵 활용
// 배열(Array)
const features = ['modules','arrow function','let, const','rest parameter','modules'];
console.log(features.length, features[0]); // 5, 'modules'
// 세트(Set)
// new Set([iterable]);
const features_set = new Set(features);
console.log(features_set.size, features_set[0]); // 4, undefined
Set 객체는 값 콜렉션(Collections)으로 삽입된 순서대로 요소들을 반복(iterate) 할 수 있습니다.
Set 객체의 속성 `.size`는 아이템 개수를 반환합니다.
Array 객체와 달리 Set 객체는 동일한 아이템을 포함하지 않습니다.
즉, 저장한 데이터는 유일무이(唯一無二)한 데이터입니다.
Array 객체에 포함된 'modules' 문자열 원시 값은 동일한 값으로 단 하나의 값만 저장됩니다.
Set 객체를 사용하면 Array 객체의 아이템 중, 중복되는 것을 제거하는데 용이합니다.
Array 객체와 달리 Set[index] 방법으로는 아이템에 접근할 수 없습니다.
// 세트(Set) : new Set([iterable]);
const features_set = new Set(features);
console.log(features_set.size, features_set[0]); // 4, undefined
// Set.prototype 객체
console.dir(Set.prototype);
// .constructor
// .size
// .add()
// .has()
// .delete()
// .clear()
// .forEach()
// .entries()
// .keys()
// .values()
Set 객체는 값 콜렉션(Collections)으로 삽입된 순서대로 요소들을 반복(iterate) 할 수 있습니다.
// Set 객체 생성
const phones = new Set(); // Set(0) {}
Set 객체는 값 콜렉션(Collections)으로 삽입된 순서대로 요소들을 반복(iterate) 할 수 있습니다.
// 아이템 추가
phones.add('iPhoneX'); // Set(1) {"iPhoneX"}
phones.add('Gallaxy Note 8'); // Set(2) {"iPhoneX", "Gallaxy Note 8"}
phones.add('V30'); // Set(3) {"iPhoneX", "Gallaxy Note 8", "V30"}
// 저장된 아이템 개수 출력
console.log(phones.size); // 3
// 아이템 소유 여부 확인
phones.has('V30'); // true
phones.has('Windows Phone'); // false
// 'Mi5' 아이템이 phones 세트에 저장되어 있지 않다면?
if ( !phones.has('Mi5') ) {
// phones 세트에 'Mi5' 아이템을 저장
phones.add('Mi5'); // // Set(4) {"iPhoneX", "Gallaxy Note 8", "V30", "Mi5"}
console.log(phones.size); // 4
}
// phones 세트
phones; // Set(4) {"iPhoneX", "Gallaxy Note 8", "V30", "Mi5"}
Set 객체는 값 콜렉션(Collections)으로 삽입된 순서대로 요소들을 반복(iterate) 할 수 있습니다.
// 아이템 제거
phones.delete('iPhoneX'); // true
phones.delete('Blackberry'); // false
// 저장된 아이템 개수 출력
console.log(phones.size); // 3
// 저장된 아이템 모두 제거
phones.clear(); // Set(0) {}
// phones 세트
phones; // Set(4) {"iPhoneX", "Gallaxy Note 8", "V30", "Mi5"}
Set 객체는 값 콜렉션(Collections)으로 삽입된 순서대로 요소들을 반복(iterate) 할 수 있습니다.
// 아이템 순환
phones.forEach(phone => console.log(phone)); // 'iPhoneX', 'Gallaxy Note 8', 'V30', 'Mi5'
// 전달인자 검토
phones.forEach((...args) {
console.log(args);
// (3) ["iPhoneX", "iPhoneX", Set(4)]
// (3) ["Gallaxy Note 8", "Gallaxy Note 8", Set(4)]
// (3) ["V30", "G30", Set(4)]
// (3) ["Mi5", "Mi5", Set(4)]
});
// phones 세트
phones; // Set(4) {"iPhoneX", "Gallaxy Note 8", "V30", "Mi5"}
Set 객체는 값 콜렉션(Collections)으로 삽입된 순서대로 요소들을 반복(iterate) 할 수 있습니다.
// for문으로 Set 객체를 순환 하면?
for ( let i=0, l=phones.size; i<l; ++i ) {
console.log(phones[i]); // undefined
}
// for ~ in문으로 Set 객체를 순환 하면?
for (let key in phones) {
console.log(key, phones[key]); // undefined
}
// for ~ of 문으로 Set 객체를 순환 하면?
for ( let phone of phones ) {
console.log(phone); // 'iPhoneX', 'Gallaxy Note 8', 'G30', 'Mi5'
}
// phones 세트
phones; // Set(4) {"iPhoneX", "Gallaxy Note 8", "V30", "Mi5"}
Set 객체는 값 콜렉션(Collections)으로 삽입된 순서대로 요소들을 반복(iterate) 할 수 있습니다.
// 매핑(Mapping)
// 세트 → 배열 변환 후, .map() 메서드 활용
// Array.from() 또는 [...세트] 사용
let upgradePhones = new Set( [...phones].map(phone => `upgrade ${phone}`) );
// 필터링(Filtering)
// 아이템 글자 개수가 10개 이상인 것만 필터링
let filteredPhones = new Set( [...phones].filter(phone => phone.length > 10) );
Set 객체는 값 콜렉션(Collections)으로 삽입된 순서대로 요소들을 반복(iterate) 할 수 있습니다.
// 합집합(union, ∪) 유틸리티 함수
function unionSet(setA, setB) {
return new Set([...setA, ...setB]);
}
// 교집합(intersection, ∩) 유틸리티 함수
function intersectSet(setA, setB) {
return new Set( [...setA].filter(item => setB.has(item)) );
}
// 차집합(diffrence, \) 유틸리티 함수
function diffSet(setA, setB) {
return new Set( [...setA].filter(item => !setB.has(item)) );
}
// 상위집합(superset, ⊃) 유틸리티 함수
function isSuperset(setA, setB) {
for ( let item of setB ) {
if ( !setA.has(item) ) { return false; }
}
return true;
}
Set 객체는 값 콜렉션(Collections)으로 삽입된 순서대로 요소들을 반복(iterate) 할 수 있습니다.
// 합집합(union, ∪) 메서드
Set.prototype.union = function(x) {
return new Set([...this, ...x]);
};
// 교집합(intersection, ∩) 메서드
Set.prototype.intersection = function(x) {
return new Set( [...this].filter(y => x.has(y)) );
};
// 차집합(diffrence, \) 메서드
Set.prototype.diffrenece = function(x) {
return new Set( [...this].filter(y => !x.has(y)) );
}
// 상위집합(superset, ⊃) 메서드
Set.prototype.isSuperset = function(x) {
for ( let y of x ) {
if (!this.has(y)) { return false; }
}
return true;
}
// 객체(Object)
let capitals = {
korea_ref : '서울',
china : '북경',
usa : '워싱턴 D.C'
};
console.log(capitals); // {korea_ref: "서울", china: "북경", usa: "워싱턴 D.C"}
Map 객체는 속성(Key)/값(Value) 쌍으로 구성된 객체입니다.
// 맵(Map)
// new Map([iterable]);
let capitals_map = new Map();
capitals_map
.set('한국', '서울')
.set('중국', '북경')
.set('미국', '워싱턴 D.C');
console.log(capitals_map); // {"한국" => "서울", "중국" => "북경", "미국" => "워싱턴 D.C"}
Map 객체는 속성(key)/값(value) 쌍으로 구성된 객체입니다.
키(key)로 저장하고, 불러오고, 삭제하거나, 저장된 값(value)을 확인할 수 있다는 점에서 객체는 맵과 유사합니다.
문자, 심볼만 키(key)로 사용할 수 있는 객체에 반해, 맵은 어떤 값도 키로 사용할 수 있습니다.
그리고 객체는 저장된 데이터(키: 값)의 개수를 알 수 없지만, 맵은 .size 속성을 통해 알 수 있습니다.
데이터 콜렉션(Collection)을 다룰 때 주로 사용하면 좋습니다.
키 값을 문자, 심볼이 아닌 것을 사용해야 하거나 데이터가 순환(Iterate) 되어야 할 경우 유용합니다.
그 외의 경우는 객체를 사용하는 것이 좋습니다.
// Map.prototype 객체
console.dir(Map.prototype);
// .constructor
// .size
// .set()
// .get()
// .has()
// .delete()
// .clear()
// .forEach()
// .entries()
// .keys()
// .values()
Map 객체는 속성(key)/값(value) 쌍으로 구성된 객체입니다.
// Map 객체 생성
let capitals = new Map(); // Map(0) {}
// 아이템(키,값) 추가
capitals.set('한국', '서울'); // Map(1) {"한국"=>"서울"}
capitals.set('중국', '북경'); // Map(2) {"한국"=>"서울", "중국"=>"북경"}
capitals.set('미국', '워싱턴 D.C'); // Map(3) {"한국"=>"서울", "중국"=>"북경", "미국"=>"워싱턴 D.C"}
// 저장된 아이템 개수 출력
console.log(capitals.size); // 3
// 저장된 아이템 출력
capitals.get('한국'); // '서울'
capitals.get('일본'); // undefined
// 저장된 키 값에 '일본'이 없다면?
if ( !capitals.has('일본') ) {
// '일본' => '동경' 아이템 추가
capitals.set('일본', '동경');
console.log(capitals.size); // 4
}
Map 객체는 속성(key)/값(value) 쌍으로 구성된 객체입니다.
Map 객체는 속성(key)/값(value) 쌍으로 구성된 객체입니다.
// capitals 맵
capitals; // Map(4) {"한국" => "서울", ...}
// 아이템 제거
capitals.delete('일본'); // true
capitals.delete('러시아'); // false
// 저장된 아이템 개수 출력
console.log(capitals.size); // 3
// 저장된 아이템 모두 제거
capitals.clear(); // Map(0) {}
Map 객체는 속성(key)/값(value) 쌍으로 구성된 객체입니다.
// 아이템 순환
capitals.forEach((city, nat, collection) => console.log(city, nat, collection));
// 전달인자 검토
capitals.forEach((...args) {
console.log(args);
// 서울 한국 Map(4) {"한국" => "서울", ...}
// 북경 중국 Map(4) {"한국" => "서울", ...}
// 워싱턴 D.C 미국 Map(4) {"한국" => "서울", ...}
// 동경 일본 Map(4) {"한국" => "서울", ...}
});
// capitals 맵
capitals; // Map(4) {"한국" => "서울", ...}
Map 객체는 속성(key)/값(value) 쌍으로 구성된 객체입니다.
// for문으로 Map 객체를 순환 하면?
for ( let i=0, l=capitals.size; i<l; ++i ) {
console.log(capitals[i]); // undefined
}
// for ~ in문으로 Map 객체를 순환 하면?
for (let key in capitals) {
console.log(key, capitals[key]); // undefined
}
// for ~ of 문으로 Map 객체를 순환 하면?
for ( let [nat, city] of capitals ) {
console.log(nat, city);
}
// capitals 맵
capitals; // Map(4) {"한국" => "서울", ...}
Map 객체는 속성(key)/값(value) 쌍으로 구성된 객체입니다.
// capitals 맵
capitals; // Map(4) {"한국" => "서울", ...}
// .values()
for ( let city of capitals.values() ) {
console.log(city); // '서울', '북경', '워싱턴 D.C', '동경'
}
// .keys()
for ( let nat of capitals.keys() ) {
console.log(nat); // '한국', '중국', '미국', '일본'
}
// .entries()
for ( let [key, value] of capitals.entries() ) {
console.log(key, value);
}
Map 객체는 속성(key)/값(value) 쌍으로 구성된 객체입니다.
// capitals 맵
// 배열([]) 내부에 키/값 배열([key, value])을 포함.
let capitals = new Map([
['한국', '서울'],
['중국', '북경'],
['미국', '워싱턴 D.C'],
['일본', '동경']
]);
// 맵 → 배열
console.log( [...capitals] );
/* 결과:
[
['한국', '서울'],
['중국', '북경'],
['미국', '워싱턴 D.C'],
['일본', '동경']
]
*/
Map 객체는 속성(key)/값(value) 쌍으로 구성된 객체입니다.
// 매핑(Mapping)
// 맵 → 배열 변환 후, .map() 메서드 활용
// Array.from() 또는 [...세트] 사용
// 매개변수 [], 반환 값도 []
capitals = new Map( [...capitals].map(([nat, city]) => [nat, `${nat}의 수도 ${city}.`]) );
// 필터링(Filtering)
// 중국을 제외한 나머지 국가 및 수도 데이터 필터링
capitals = new Map([...capitals].filter(([nat, city]) => nat !== '중국'));
// capitals 맵
capitals; // Map(4) {"한국" => "서울", ...}
Map 객체는 속성(key)/값(value) 쌍으로 구성된 객체입니다.
// 맵 병합 유틸리티 함수
function combineMap(mapA, mapB) {
return new Map( [...mapA, ...mapB] );
}
// 맵 → 배열 유틸리티 함수
function convertMap2Array(map) {
return [...map];
}
// Map.prototype 확장
Map.prototype.combine = function(x) {
return new Map([...this, ...x]);
};
Map.prototype.convertArray = function() {
return [...this];
};
Map 객체는 속성(key)/값(value) 쌍으로 구성된 객체입니다.
// Map → JSON 유틸리티 함수
function map2json(map) {
return JSON.stringify([...map]);
}
// JSON → Map 유틸리티 함수
function json2map(jsonStr) {
return new Map(JSON.parse(jsonStr));
}
let map = new Map().set(true, 7).set({foo: 3}, ['abc']);
map = map2json(map); // '[[true,7],[{"foo":3},["abc"]]]'
map = json2map(map); // Map(2) {true => 7, Object {foo: 3} => ['abc']}
Map 객체는 속성(key)/값(value) 쌍으로 구성된 객체입니다.
// String Map → Object 유틸리티 함수
function strMap2obj(strMap) {
let o = Object.create(null);
for ( let [k,v] of strMap ) {
o[k] = v;
}
return o;
}
// Object → String Map 유틸리티 함수
function obj2strMap(o) {
let m = new Map();
for ( let k of Object.keys(o) ) {
m.set(k, o[k]);
}
return m;
}
let strMap = new Map().set(true, 'yes').set(false, 'no');
let o = strMap2obj(strMap); // { true: 'yes', false: 'no' }
strMap = obj2strMap(o); // Map(2) { true => 'yes', false => 'no' }
Map 객체는 속성(key)/값(value) 쌍으로 구성된 객체입니다.
// strMap → JSON 유틸리티 함수
function strMap2json(strMap) {
return JSON.stringify(strMap2obj(strMap));
}
// JSON → strMap 유틸리티 함수
function json2strMap(jsonStr) {
return obj2strMap(JSON.parse(jsonStr));
}
let strMap = new Map().set('yes', true).set({'no': false});
let json = strMap2json(strMap); // '{"yes":true,"no":false}'
strMap = json2strMap(json); // Map(2) {'yes' => true, 'no' => false}
세트 및 맵을 정리해봅시다.
/**
* ES6를 사용하여 프로젝트를 진행한다면?
* Set, Map
*
* Set https://goo.gl/rLszbu
* Map https://goo.gl/DUs6c8
* ———————————————————————————————————————————————————————————
*
* Set은 Array와 유사하나, 중복된 데이터를 허용하지 않아 데이터 관리에 유용
* Map은 Object와 유사하나, 키로 어떤 값이든 사용 가능하며 데이터를 순환할 경우 유용
* 즉, Set, Map은 데이터 관리에 매우 유용하니 적극 활용
*
*/
위크 세트, 위크 맵 활용
// 데이터(객체)
let arr = [1, 3, 5, 7],
obj = {key: 'value'};
// Set 객체 생성
let set = new Set();
// WeakSet 객체 생성
let wset = new WeakSet();
// 아이템 추가
set.add(arr).add(obj);
wset.add(arr).add(obj);
// 아이템 사이즈
console.log(set.size); // 2
console.log(wset.size); // undefined
// 객체가 아닌 데이터 추가
set.add(true);
wset.add(true); // 오류 발생: Invalid value used in weak set
WeakSet 객체는 Set 객체와 유사합니다만, Set 객체와 달리 객체만 수집할 수 있고 약한 참조가 이루어져 메모리 누수를 예방할 수 있습니다.
// 아이템 소유 여부 확인
set.has(obj); // true
wset.has(obj); // true
// 아이템 제거
set.delete(arr); // true
wset.delete(arr); // true
// 세트 순환
set.forEach(item => console.log(item)); // 참조된 데이터에 접근 및 사용 가능
wset.forEach(item => console.log(item)); // 오류 발생: wset.forEach is not a function
// 메모리 참조
let set = new Set();
let wset = new WeakSet();
(() => {
let o1 = {a: 1}; // 메모리
let o2 = {a: 2}; // 가비지 컬렉터에 의해 메모리 삭제
set.add(o1);
wset.add(o2);
});
// WeakSet 어떻게 사용하면 좋을까?
// 참고: https://goo.gl/lrwPDV
// WeakSet 객체 생성
let ownClass = new WeakSet();
// 클래스 OffCanvasMenu 정의
class OffCanvasMenu {
constructor() {
// 클래스 자신을 ownClass에 추가
ownClass.add(this);
// ...
}
toggle() {
// OffCanvasMenu 객체가 아닌,
// 다른 객체가 toggle() 메서드를 사용하려 할 경우 오류 출력
if ( !ownClass.has(this) ) {
throw new TypeError('toggle() 메서드는 OffCanvasMenu 객체만 사용 가능합니다!');
}
}
}
// 데이터(객체)
let arr = [1, 3, 5, 7],
obj = {key: 'value'};
// Map 객체 생성
let map = new Map();
// WeakMap 객체 생성
let wmap = new WeakMap();
// 아이템 추가
map.set(arr, 'Array').set(obj, 'Object');
wmap.set(arr, 'Array').set(obj, 'Object');
// 아이템 사이즈
console.log(map.size); // 2
console.log(wmap.size); // undefined
// 객체가 아닌 데이터 추가
map.set(true, 'yes');
wmap.set(true, 'yes'); // 오류 발생: Invalid value used as weak map key
WeakMap 객체는 Map 객체와 유사하지만, Map 객체와 달리 객체만 수집할 수 있고 약한 참조가 이루어져 메모리 누수를 예방할 수 있습니다.
// 아이템 소유 여부 확인
map.has(obj); // true
wmap.has(obj); // true
// 아이템 제거
map.delete(arr); // true
wmap.delete(arr); // true
// 세트 순환
map.forEach(item => console.log(item)); // 참조된 데이터에 접근 및 사용 가능
wmap.forEach(item => console.log(item)); // 오류 발생: wmap.forEach is not a function
// 메모리 참조
let map = new Map();
let wmap = new WeakMap();
(() => {
let o1 = {a: 1}; // 메모리
let o2 = {a: 2}; // 가비지 컬렉터에 의해 메모리 삭제
map.set(o1, '가비지 컬렉터에 의해 제거되지 않음');
wmap.set(o2, '가비지 컬렉터에 의해 제거됨');
});
// WeakMap 어떻게 사용하면 좋을까?
// 비공개 속성을 관리하기 위한 WeakMap 객체 생성
let _ = new WeakMap();
// 클래스 OffCanvasMenu 정의
class OffCanvasMenu {
constructor(el, options) {
// WeakMap 객체를 사용해 비공개 속성 설정
_.set(this, {el, options});
// ...
}
toggle() {
// 비공개 속성에 접근 가능한 $ 변수 참조
let $ = _.get(this);
// 비공개 속성 el에 접근하여 조작
$.el.classList.toggle('is-active');
}
}
WeakSet, WeakMap을 정리해봅시다.
/**
* ES6를 사용하여 프로젝트를 진행한다면?
* WeakSet, WeakMap
*
* WeakSet https://goo.gl/5viDqn
* WeakMap https://goo.gl/VWqA2B
* ———————————————————————————————————————————————————————————
*
* Set, Map과 유사하지만, 다음과 같은 특징을 가짐
*
* 특징
* - 객체 유형만 저장 가능 (원시데이터 불가능)
* - 열거(Enumerable) 불가능
* - 약한 참조 (메모리 누수 방지)
*
*/
프로미스 활용
Promise 객체는 비동기 처리를 위한 목적으로 사용되며, 당장은 아니더라도
나중에 처리될 것으로 기대되는 연산을 수행할 때 사용합니다.
// CASE 1. VanillaJS + VelocityJS
window.addEventListener('DOMContentLoaded', function(){
var insert_btn = document.querySelector('.insert-button');
insert_btn.onclick = function(e){
window.Velocity(this, {'padding': '+=0.45em'}, {
complete: function(){
console.log('애니메이션 종료');
}
})
};
});
// CASE 2. jQuery
$(document).ready(function(){
$('.insert_btn').on('click', function(e){
$(this).animate({'padding': '+=0.45em'}, function(){
console.log('애니메이션 종료');
});
});
});
// CASE 3. jQuery Ajax
$.ajax({
url: 'https://randomuser.me/api/',
method: 'GET',
dataType: 'json',
// 통신이 성공하면 실행하는 콜백함수
success: function(data) {
console.log(data);
},
// 통신이 실패하면 실행하는 콜백함수
error: function() {
console.error('통신 실패');
},
});
Promise 객체는 비동기 처리를 위한 목적으로 사용되며, 당장은 아니더라도
나중에 처리될 것으로 기대되는 연산을 수행할 때 사용합니다.
// 동기(Sync) 프로그래밍: 거듭 제곱 함수
// 어떤 작업을 요청한 후 그 작업이 완료되기까지 기다렸다가
// 응답을 받아 처리하는 것을 말합니다.
function exponentiation(x, n) {
n = n || 2;
var o = [];
while(n--) { o.push(x); }
return o.reduce(function(a,b){return a*b;});
}
// 동기 처리 중.
var expo_six = exponentiation(6, 4);
// 동기 처리가 끝나면 실행
console.log(expo_six); // 1296
// 비동기(Async) 프로그래밍: 거듭 제곱 함수
// 어떤 작업을 요청한 후 다른 작업을 수행하다가 이벤트가 발생하면
// 그에 대한 응답을 받아 처리하는 것을 말합니다.
function exponentiationAsync(x, n, cb) {
n = n || 2;
var o = [];
while(n--) { o.push(x); }
x = o.reduce(function(a,b){return a*b;});
window.setTimeout(cb, 1000, x);
}
var expo_six = 0;
// 비동기 처리 중...
exponentiationAsync(6, 4, function(x){
expo_six = x; // 1초 뒤에 계산된 값이 할당됨.
});
// 기다리지 않고 바로 수행
console.log(expo_six); // 0
Promise 객체는 비동기 처리를 위한 목적으로 사용되며, 당장은 아니더라도
나중에 처리될 것으로 기대되는 연산을 수행할 때 사용합니다.
// 비동기(Async) 프로그래밍: 거듭 제곱 함수
function exponentiationAsync(x, n, cb) {
n = n || 2;
var o = [];
while(n--) { o.push(x); }
x = o.reduce(function(a,b){return a*b;});
window.setTimeout((cb || function(){}), 1000, x);
}
// 2의 3승(거듭제곱) 값을 시작으로 1초 마다
// 전달 받은 값을 3승(거듭제곱)하여 비동기 처리
// 비동기 프로그래밍 코드 결과: 콜백 지옥!!!!
exponentiationAsync(2, 3, function(x){
exponentiationAsync(x, 3, function(x2){
exponentiationAsync(x2, 3, function(x3){
exponentiationAsync(x3, 3, function(x4){
console.log(x4); // 4초 뒤에 2.4178516392292583e+24 출력
});
});
});
});
// 2의 3승(거듭제곱) 값을 시작으로 1초 마다
// 전달 받은 값을 3승(거듭제곱)하여 비동기 처리
exponentiationAsync(2, 3, function(x){ // 콜
exponentiationAsync(x, 3, function(x2){ // 백
exponentiationAsync(x2, 3, function(x3){ // 지
exponentiationAsync(x3, 3, function(x4){ // 옥
console.log(x4);
});
});
});
});
// 4초 뒤에 출력되는 결과 값: 2.4178516392292583e+24
Promise 객체는 비동기 처리를 위한 목적으로 사용되며, 당장은 아니더라도
나중에 처리될 것으로 기대되는 연산을 수행할 때 사용합니다.
// Promise를 사용하여 exponentiationPromise 유틸리티 함수 개선
function exponentiationPromise(x, n=2, time=1000) {
return new Promise((resolve, reject) => {
window.setTimeout(() => {
try {
let o = Array.from(new Array(n), () => x);
resolve( o.reduce((a,b) => a*b) );
} catch(e) {
reject(e);
}
}, time);
});
}
// Promise를 사용한 비동기(Async) 프로그래밍
exponentiationPromise(2, 3)
.then(x => exponentiationPromise(x, 3))
.then(x2 => exponentiationPromise(x2, 3))
.then(x3 => exponentiationPromise(x3, 3))
.then(x4 => console.log(x4));
// 4초 뒤에 출력되는 결과 값: 2.4178516392292583e+24
Promise 객체는 비동기 처리를 위한 목적으로 사용되며, 당장은 아니더라도
나중에 처리될 것으로 기대되는 연산을 수행할 때 사용합니다.
팬딩(pending) : 초기 상태. 실행(fulfilled) 또는 거절(rejected) 되기 이전 상태를 말합니다.
실행(fulfilled) : 동작이 성공한 상태를 말합니다.
거절(rejected) : 동작이 실패한 상태를 말합니다.
Promise
팬딩(pending)
실행(fulfill)
거절(reject)
확정(settled)
.then(onFulfillment)
.then(onRejction) .catch(onRejection)
비동기 동작 처리
오류 처리
Promise
팬딩(pending)
.then()
.catch()
반환(return)
반환(return)
Promise 객체는 비동기 처리를 위한 목적으로 사용되며, 당장은 아니더라도
나중에 처리될 것으로 기대되는 연산을 수행할 때 사용합니다.
// Promise 객체 생성: 인자로 excutor 함수를 전달 받음.
// excutor 함수는 resolve, reject를 매개변수로 가지며 바로 실행된다.
// 비동기 동작을 수행/완료하면 동작이 실행(fulfilled, 성공) 상태이면 resolve 함수를 실행한다.
// 동작이 거절(rejected, 실패) 상태이면 reject 함수를 실행한다.
let is_success = true;
const promise = new Promise((resolve, reject) => {
if ( is_success ) {
resolve();
} else {
reject();
}
});
promise
.then(() => console.log('비동기 동작 성공 상태일 경우, 실행되는 함수'))
.catch(() => console.error('비동기 동작 실패 상태일 경우, 실행되는 함수'));
// Ajax 유틸리티 함수
const AJAX = (url, method='GET') => {
return new Promise((resolve, reject) => {
let xhr = new XMLHttpRequest();
xhr.open(method, url);
xhr.addEventListener('readystatechange', function(){
if ( this.readyState === 4 && this.status === 200 ) {
resolve(this.response);
}
});
xhr.addEventListener('error', function(e){
reject(e);
});
xhr.send(null);
});
};
AJAX('https://jsonplaceholder.typicode.com/albums')
.then(response => JSON.parse(response))
.then(data => console.log(data.filter((item, index) => index < 10)));
fetch()는 네트워크를 통해 비동기 적으로 리소스를 가져 오는 쉽고 논리적인 방법을 제공하는 전역 함수입니다. (promise 객체 반환)
// Promise 객체 생성: 인자로 excutor 함수를 전달 받음.
// excutor 함수는 resolve, reject를 매개변수로 가지며 바로 실행된다.
// 비동기 동작을 수행/완료하면 동작이 실행(fulfilled, 성공) 상태이면 resolve 함수를 실행한다.
// 동작이 거절(rejected, 실패) 상태이면 reject 함수를 실행한다.
let is_success = true;
const promise = new Promise((resolve, reject) => {
if ( is_success ) {
resolve();
} else {
reject();
}
});
promise
.then(() => console.log('비동기 동작 성공 상태일 경우, 실행되는 함수'))
.catch(() => console.error('비동기 동작 실패 상태일 경우, 실행되는 함수'));
// window.fetch()
fetch('https://jsonplaceholder.typicode.com/albums')
.then(response => response.json())
.then(data => console.log(data.filter((item, index) => index < 10)))
.catch(error => console.error(error));
Promise 클래스의 메서드를 살펴봅시다.
// Promise 객체 생성: 인자로 excutor 함수를 전달 받음.
// excutor 함수는 resolve, reject를 매개변수로 가지며 바로 실행된다.
// 비동기 동작을 수행/완료하면 동작이 실행(fulfilled, 성공) 상태이면 resolve 함수를 실행한다.
// 동작이 거절(rejected, 실패) 상태이면 reject 함수를 실행한다.
let is_success = true;
const promise = new Promise((resolve, reject) => {
if ( is_success ) {
resolve();
} else {
reject();
}
});
promise
.then(() => console.log('비동기 동작 성공 상태일 경우, 실행되는 함수'))
.catch(() => console.error('비동기 동작 실패 상태일 경우, 실행되는 함수'));
// Promise 객체 참조
let a = new Promise((rs, rj) => {
// 실행
window.setTimeout(rs, 1000, 'A');
});
let b = new Promise((rs, rj) => {
// 실행
window.setTimeout(rs, 2000, 'B');
});
// Promise.all() 메서드 활용
// 모든 promise의 상태가 실행(fulfilled) 되거나 첫 거절(rejection)이 발생할 경우 동작.
// 병렬 비동기 프로그래밍
Promise.all([a,b])
.then(values => console.log(values)); // 2초가 지난 후... ['A', 'B']
Promise 클래스의 메서드를 살펴봅시다.
// Promise 객체 생성: 인자로 excutor 함수를 전달 받음.
// excutor 함수는 resolve, reject를 매개변수로 가지며 바로 실행된다.
// 비동기 동작을 수행/완료하면 동작이 실행(fulfilled, 성공) 상태이면 resolve 함수를 실행한다.
// 동작이 거절(rejected, 실패) 상태이면 reject 함수를 실행한다.
let is_success = true;
const promise = new Promise((resolve, reject) => {
if ( is_success ) {
resolve();
} else {
reject();
}
});
promise
.then(() => console.log('비동기 동작 성공 상태일 경우, 실행되는 함수'))
.catch(() => console.error('비동기 동작 실패 상태일 경우, 실행되는 함수'));
// Promise 객체 참조
let a = new Promise((rs, rj) => {
// 실행
window.setTimeout(rs, 1000, 'A');
});
let b = new Promise((rs, rj) => {
// 거절
window.setTimeout(rj, 2000, 'B');
});
// Promise.all() 메서드 활용
Promise.all([a,b])
.then(values => console.log(values))
.catch(e => console.error(`${e} 오류 발생!`)); // B 오류 발생!
Promise 클래스의 메서드를 살펴봅시다.
// Promise 객체 생성: 인자로 excutor 함수를 전달 받음.
// excutor 함수는 resolve, reject를 매개변수로 가지며 바로 실행된다.
// 비동기 동작을 수행/완료하면 동작이 실행(fulfilled, 성공) 상태이면 resolve 함수를 실행한다.
// 동작이 거절(rejected, 실패) 상태이면 reject 함수를 실행한다.
let is_success = true;
const promise = new Promise((resolve, reject) => {
if ( is_success ) {
resolve();
} else {
reject();
}
});
promise
.then(() => console.log('비동기 동작 성공 상태일 경우, 실행되는 함수'))
.catch(() => console.error('비동기 동작 실패 상태일 경우, 실행되는 함수'));
// Promise 객체 참조
let a = new Promise((rs, rj) => {
// 실행 (먼저 종료)
window.setTimeout(rs, 1000, 'A');
});
let b = new Promise((rs, rj) => {
// 거절
window.setTimeout(rj, 2000, 'B');
});
// Promise.race() 메서드 활용
// 결과가 실행이든, 거절이든 먼저 종료된 쪽의 결과 반환
Promise.race([a,b])
.then(values => console.log(values)) // 'A' 출력
.catch(e => console.error(`${e} 오류 발생!`));
Promise 클래스의 메서드를 살펴봅시다.
// Promise 객체 생성: 인자로 excutor 함수를 전달 받음.
// excutor 함수는 resolve, reject를 매개변수로 가지며 바로 실행된다.
// 비동기 동작을 수행/완료하면 동작이 실행(fulfilled, 성공) 상태이면 resolve 함수를 실행한다.
// 동작이 거절(rejected, 실패) 상태이면 reject 함수를 실행한다.
let is_success = true;
const promise = new Promise((resolve, reject) => {
if ( is_success ) {
resolve();
} else {
reject();
}
});
promise
.then(() => console.log('비동기 동작 성공 상태일 경우, 실행되는 함수'))
.catch(() => console.error('비동기 동작 실패 상태일 경우, 실행되는 함수'));
// Promise 객체 참조
let a = new Promise((rs, rj) => {
// 실행
window.setTimeout(rs, 1000, 'A');
});
let b = new Promise((rs, rj) => {
// 거절 (먼저 종료)
window.setTimeout(rj, 300, 'B');
});
// Promise.race() 메서드 활용
// 결과가 실행이든, 거절이든 먼저 종료된 쪽의 결과 반환
Promise.race([a,b])
.then(values => console.log(values))
.catch(e => console.error(`${e} 오류 발생!`)); // 'B 오류 발생!' 출력
Promise 클래스의 메서드를 살펴봅시다.
// Promise 객체 생성: 인자로 excutor 함수를 전달 받음.
// excutor 함수는 resolve, reject를 매개변수로 가지며 바로 실행된다.
// 비동기 동작을 수행/완료하면 동작이 실행(fulfilled, 성공) 상태이면 resolve 함수를 실행한다.
// 동작이 거절(rejected, 실패) 상태이면 reject 함수를 실행한다.
let is_success = true;
const promise = new Promise((resolve, reject) => {
if ( is_success ) {
resolve();
} else {
reject();
}
});
promise
.then(() => console.log('비동기 동작 성공 상태일 경우, 실행되는 함수'))
.catch(() => console.error('비동기 동작 실패 상태일 경우, 실행되는 함수'));
// 실행(fulfilled)된 Promise 객체를 반환하는 메소드.
let a = Promise.resolve('fulfilled');
// 거절(rejected)된 Promise를 객체를 반환하는 메소드.
let b = Promise.reject('rejected');
// 전달된 Promise 객체 중, 먼저 처리된 결과를 실행.
Promise.race([a,b])
.then(values => console.log(values)) // fulfilled 출력
.catch(e => console.error(`${e} 오류 발생!`));
Promise를 정리해봅시다.
/**
* ES6를 사용하여 프로젝트를 진행한다면?
* Promise, Fetch API
*
* Promise https://goo.gl/Cstuig
* Fetch https://goo.gl/wbb1zc
* ———————————————————————————————————————————————————————————
*
* Callback 함수 패턴이 아닌, Promise 패턴을 사용하여 비동기 프로그래밍
*
*/
레퍼런스