모던 자바스크립트
EcmaScript 2015 (ES6)

EcmaScript

TC 39

Ecma

ECMA-262

Mark Andreessen

Netscape

Brendan Eich

1995년 4월부터 Netscape Communications Corporation에서 일을 시작한 아이크는 브라우저 내부에 Scheme
프로그래밍 언어를 추가하려고 합류하였으나,  넷스케이프 상사는 그에게 Java와 유사한 언어를 브라우저에서 사용 가능
하도록 요구하였다. 결과적으로 그는 Scheme의 기능성, Self의 객체 지향성, Java 문법을 따와 언어를 만들어 낸다.

첫 번째 버전은 Navigator 2.0 베타 릴리스 일정을 수용하기 위해 10일 만에 완료 되었으며 Mocha로 불렸지만,
1995년 9월에 LiveScript로 이름이 바뀌었고 이후 같은 달에 마케팅을 이유로 JavaScript로 바꾸었다.

JavaScript

EcmaScript

Ecma

TC39

Technical

Committee

Compatibility

var vs let vs const

변수를 선언하는 3가지 방법. 각 차이와 용도

variable declaration

변수 선언

Initialization

Scope

변수 초기화

프로그램 내부에서 접근 가능한 영역(범위) 설정

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); // ?

블록 영역 vs 함수 영역

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); // ?

블록 영역 & let, const

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 & 블록 영역 & 함수 실행 시점

반복문에 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

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 & hoist

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 & hoist

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 vs let vs const

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 vs let, const

var 키워드를 사용해 전역에 선언할 경우, window 객체의 속성으로 접근 가능합니다.
반면 let, const 키워드를 사용할 경우는 window 객체의 속성으로 접근할 수 없습니다.

const

상수의 경우 값 자체를 다른 값으로 바꿀 수는 없지만,
객체/배열의 경우 값의 아이템을 추가,변경 할 수 있습니다.

conclusion

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
 *  - 초기 값 할당이 필수!
 *  - 값 유형 변경은 허용하지 않지만,
 *    배열/객체 유형의 경우 새로운 아이템 추가,변경 가능.
 *  - 데이터 값 유형이 배열/객체일 경우 사용 권장.
 *
 */

Action

실전에서 let, const를 활용하는 예제를 실습해봅시다.

Template Literals

템플릿 리터럴을 활용하는 방법

ES5

ES6

ES5

ES6

conclusion

템플릿 리터럴을 정리해봅시다.

/**
 * ES6를 사용하여 프로젝트를 진행한다면?
 * template literals
 * 참고: https://goo.gl/MFz9j8
 * ———————————————————————————————————————————————————————————
 *
 * 백틱 기호(`, backtick 또는 backquote)
 *  - 템플릿 구문을 읽기 쉽고, 작성이 용이하도록 만들어 줌.
 *  - 공백, 줄바꿈 허용.
 *  - 홑/쌍 따옴표를 자유롭게 사용 가능.
 *
 * 보간법(${}, string interpolation)
 *  - 포함된 JavaScript 식(Expression)을 처리하여 문자 데이터로 접합.
 *
 */

Action

실전에서 템플릿 리터럴을 활용하는 예제를 실습해봅시다.

String Additions

확장된 문자 객체 능력 활용

ES5

// 플레이어 리스트(배열)
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);

ES6

.includes()

텍스트가 포함되어있는지 여부를 불리언 값으로 반환합니다. (보다 명시적)

ES5

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

ES6

.startsWith()

어떤 문자열이 특정 문자로 시작하는지 확인하여 불리언 값으로 반환합니다.

let kings_4 = '청룡 백호 현무 주작';

// 1. kings_4의 글자는 '백호'로 시작하는가?
kings_4.startsWith('백호'); // false

// 2. '현무'는 kings_4 글자의 6 인덱스부터 시작하는가?
kings_4.startsWith('현무', 6); // true

ES5

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

ES6

.endsWith()

어떤 문자열이 특정 문자로 끝나는지 확인하여 불리언 값으로 반환합니다.

let season = '봄 여름 가을 겨울';

// 1. season의 글자는 '겨울'로 끝나는가?
season.endsWith('겨울'); // true

// season의 글자는 '가을'이 7번째 인덱스(가을 다음 위치)에서 끝나는가?
season.endsWith('가을', 7); // true

ES5

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); // '양심과 욕심 양심과 욕심 양심과 욕심 양심과 욕심 '

ES6

.repeat()

어떤 문자열을 특정한 개수만큼 반복하여 새로운 문자열을 반환합니다.

let repeat_word = '양심과 욕심 ';

repeat_word.repeat();  // ''
repeat_word.repeat(4); // '양심과 욕심 양심과 욕심 양심과 욕심 양심과 욕심 '

conclusion

새롭게 확장된 문자열 객체의 능력을 정리해봅시다.

/**
 * ES6를 사용하여 프로젝트를 진행한다면?
 * string addtions
 * ———————————————————————————————————————————————————————————
 *
 * .includes()   보다 명시적이고 의미적으로 사용 가능
 * .startsWith() 임의의 텍스트로 시작하는지 여부 확인 가능
 * .endsWidth()  임의의 텍스트로 끝나는지 여부 확인 가능
 * .repeat()     필요한 경우, 특정 텍스트를 반복 횟수만큼 처리 가능
 *
 */

Arrow Function

화살표 함수 활용

ES5

// 데이터 유형 검증 유틸리티 함수

// 함수 선언(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);
};

ES6

ES5

// 유사배열 데이터 -> 배열 유틸리티 함수
var makeArray = function(o) {
  return Array.prototype.slice.call(o);
};

ES6

// 화살표 함수 문법: 식(Expression)
let makeArray = (o) => Array.prototype.slice.call(o);
// 앞서 살펴본 isType 함수 또한 식(Expression)으로 변경해보면 다음과 같습니다.
let isType = (o) => Object.prototype.toString.call(o).toLowerCase().slice(8,-1);

ES5

// 객체 합성 유틸리티 함수
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;
};

ES6

// 객체 합성 유틸리티 함수
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;
};

ES5

// 사용자 데이터
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;
});

ES6

// 사용자 데이터
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 );

ES5

// 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;
      });
    });
}

ES6

// 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));
}

ES5

// 객체 정의
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);
  }
};

ES6

// 객체 정의
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} 학생을 알고 있습니다.`);
    });
  }
};

ES5

// 제곱(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

conclusion

화살표 함수를 정리해봅시다.

/**
 * ES6를 사용하여 프로젝트를 진행한다면?
 * arrow function
 * 참고: https://goo.gl/hdduQF
 * ———————————————————————————————————————————————————————————
 *
 * 함수 표현식에서는 적극 활용
 * 객체의 속성으로는 사용하지 말아야
 * 객체 속성 내부에서는 적극 활용해야
 *  - this, arguments 상위 영역 활용
 *
 */

Action

실전에서 화살표 함수를 활용하는 예제를 실습해봅시다.

Parameters & Spread Operator

매개변수 그리고 전개 연산자

ES5

// 필수 인자 체크 함수
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) );
}

ES6

Default Parameters

함수 매개변수 기본 값을 설정할 수 있습니다.

// 지불 내역 계산 함수 (가격, 세금, 할인) 
// 객체를 기본 값으로 사용할 경우
// 비구조 할당(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

ES5

// 전달된 인자의 합을 구하는 함수
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

ES6

Rest Parameters

함수의 나머지 매개변수를 한데 모아 배열 값으로 사용 가능합니다.

/// 응용편 ///
// 임의의 수에 나머지 값을 순차적으로 곱한 결과를 반환하는 함수
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

ES5: 배열 복제

// 정수 배열
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();

Spread Operator

전개 연산자(...)는 함수 또는 배열 등에서 유용하게 활용됩니다.

ES6: 배열 복제

// 정수 배열
var integer = [0, -10, 10];

// 배열 복제
var copy_integer = [...integer];

Spread Operator

전개 연산자(...)는 함수 또는 배열 등에서 유용하게 활용됩니다.

ES5: 배열 (역)순차 결합

// 정수 배열
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]

Spread Operator

전개 연산자(...)는 함수 또는 배열 등에서 유용하게 활용됩니다.

ES6: 배열 (역)순차 결합

// 정수 배열
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]

Spread Operator

전개 연산자(...)는 함수 또는 배열 등에서 유용하게 활용됩니다.

ES5: 배열 중간 삽입 결합

// 정수
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]

Spread Operator

전개 연산자(...)는 함수 또는 배열 등에서 유용하게 활용됩니다.

ES6: 배열 중간 삽입 결합

// 정수
var integer = [3, 6, 9];

// 소수
var decimal = [0.9, 0.66];

// 중간 삽입 결합 (인덱스 2 위치에 삽입)
var numbers = [3, 6, ...decimal, 9]

Spread Operator

전개 연산자(...)는 함수 또는 배열 등에서 유용하게 활용됩니다.

멤버 객체

// 멤버 데이터
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"
  }, { ... }
];

Practical Example

함수, 배열에 전개 연산자를 사용한 실전 예제를 다뤄봅시다.

커뮤니티 매니저

// 커뮤니티 매니저 객체
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);

Practical Example

함수, 배열에 전개 연산자를 사용한 실전 예제를 다뤄봅시다.

// 커뮤니티 매니저 객체
var communityManager = {
  _members: members,
  // ES6: addMembers 메서드 정의
  addMembers: function(...members) {
    this._members = [...this._members, ...members];
  }
};


// ES6: 새로운 멤버들 추가
communityManager.addMembers(...new_members);

conclusion

기본 매개변수와 나머지 매개변수. 그리고 전개 연산자를 정리해봅시다.

/**
 * ES6를 사용하여 프로젝트를 진행한다면?
 * default, rest parameters / spread operator
 * default https://goo.gl/34EAkm
 * rest    https://goo.gl/MTHqGA
 * spread  https://goo.gl/KUNsTt
 * ———————————————————————————————————————————————————————————
 *
 * 함수의 매개변수 기본 값 설정은 기존의 번거로움을 대폭 줄여준다.
 * 전개 연산자(...)를 배열 또는 함수와 함께 사용하면 매우 유용하다.
 * - 배열에 사용할 경우, 배열을 전개한다.
 * - 함수의 매개변수에 전개 연산자를 사용할 경우, 나머지 매개변수는 배열으로 사용할 수 있다.
 *
 */

Action

실전에서 기본/나머지 매개변수 및 전개 연산자를 활용하는 예제를 실습해봅시다.

Shorthand Properties

속기형 속성 설정 방법

데이터

var animations = ['원령 공주', '센과 치히로의 대모험', '명탐정 코난', '에반게리온'];

var movies     = ['인디애나 존스', '살인자의 기억법', '범죄 도시'];

var music      = [
  {
    song: '선물',
    singer: '멜로망스'
  },
  {
    song: '피카부 (Peek-A-Boo)',
    singer: 'Red Velvet (레드벨벳)'
  },
];

Shorthand Properties

속기형 속성 작성법을 사용하면 객체의 속성 정의가 편리해집니다.

ES5

var favorites = {
  animations: animations,
  movies: movies,
  music: music
};

Shorthand Properties

속기형 속성 작성법을 사용하면 객체의 속성 정의가 편리해집니다.

ES6

var favorites = { animations, movies, music };
// 또는 

var favorites = { 
  animations, 
  movies, 
  music 
};

ES5

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');

Shorthand Properties

속기형 속성 작성법을 사용하면 객체의 속성 정의가 편리해집니다.

ES6

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');

conclusion

속기형 속성에 관해 정리해봅시다.

/**
 * ES6를 사용하여 프로젝트를 진행한다면?
 * Shorthand Properties
 * ———————————————————————————————————————————————————————————
 *
 * 객체의 속성, 값 이름이 동일할 경우 속기형 속성을 적극 활용하자.
 *
 */

Action

실전에서 속기형 속성을 활용하는 예제를 실습해봅시다.

Object Enhancements & Symbol

향상된 객체 표기법 & 심볼 활용

ES5

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);

Object Ehancements

객체 표기법이 보다 향상되어 사용하기 유용해 졌습니다.

// 믹스인(객체 합성) 함수
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;
}

ES6

let name  = 'SM7';
let maker = 'Samsung';
let boost = 'powerUp';

// car 객체 정의 (향상된 객체 표기법 활용)
const car = {
  // 메서드
  go(){},
};

Object Ehancements

객체 표기법이 보다 향상되어 사용하기 유용해 졌습니다.

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)}`](){}
}

ES6 : 게터 / 세터

Object Ehancements

객체 표기법이 보다 향상되어 사용하기 유용해 졌습니다.

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](){},
};

ES6 : 심볼

Object Ehancements

객체 표기법이 보다 향상되어 사용하기 유용해 졌습니다.

((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;
    },
  };

})();

conclusion

향상된 객체 표기법에 관해 정리해봅시다.

/**
 * ES6를 사용하여 프로젝트를 진행한다면?
 * Object Ehancements
 * getter https://goo.gl/t3hvjJ
 * setter https://goo.gl/PirNNt
 * Symbol https://goo.gl/PNBLeH
 * ———————————————————————————————————————————————————————————
 *
 * 객체 속성 및 메서드 표기법이 향상
 * 계산된 (동적) 속성 표기법 활용 가능
 * 객체 상속 및 활용 방법 향상
 *
 * getter, setter를 사용하여 계산된 속성 할당
 * Symbol을 사용하여 접근 불가능한 식별자 활용
 *
 */

Action

실전에서 향상된 객체 표기법을 활용하는 예제를 실습해봅시다.

Destructuring Assignment

비구조화 할당

Destructuring Assignment

비구조화 할당(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

비구조화 할당(destructuring assignment) 구문은 배열 값 또는 객체 속성을
별개의 변수로 추출할 수 있게 하는 자바스크립트 식(expression)입니다.

ES5

// 객체의 속성 할당
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);

ES6

Destructuring Assignment

비구조화 할당(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);

ES5

// 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

비구조화 할당(destructuring assignment) 구문은 배열 값 또는 객체 속성을
별개의 변수로 추출할 수 있게 하는 자바스크립트 식(expression)입니다.

ES6

// 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

비구조화 할당(destructuring assignment) 구문은 배열 값 또는 객체 속성을
별개의 변수로 추출할 수 있게 하는 자바스크립트 식(expression)입니다.

ES5

// 각 변수에 배열 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;

ES6

Destructuring Assignment

비구조화 할당(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);

JSON 데이터

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

비구조화 할당(destructuring assignment) 구문은 배열 값 또는 객체 속성을
별개의 변수로 추출할 수 있게 하는 자바스크립트 식(expression)입니다.

Destructuring Assignment

비구조화 할당(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);
});

ES5

ES6

ES5

Destructuring Assignment

비구조화 할당(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

conclusion

비구조화 할당에 관해 정리해봅시다.

/**
 * ES6를 사용하여 프로젝트를 진행한다면?
 * Destructuring Assignment
 * 참고: https://goo.gl/5AjCug
 * ———————————————————————————————————————————————————————————
 *
 * 객체 속성 또는 배열 값을 변수에 할당할 때, 비구조화 할당을 활용하면 매우 유용
 * 
 * 객체
 *  - 속성(property or key) 이름과 비교하여 할당
 * 
 * 배열
 *  - 원소의 순서(order)에 맞춰 할당
 *
 */

Action

실전에서 비구조화 할당을 활용하는 예제를 실습해봅시다.

Classes & Private Data

클래스/상속 활용 및 데이터 보호 관리

(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) {}

  }
})();

ES6 : 클래스

Classes

프로토타입 기반의 객체 지향 프로그래밍 방법 대신, 클래스 기반의 객체 지향 프로그래밍 방법을 사용할 수 있습니다.

ES6 : 호이스트 되지 않음.


// 클래스 선언 이전에 사용하면 참조 오류 발생
// Uncaught ReferenceError: Bread is not defined
new Bread();

class Bread {}

Classes

ES6 사용자라면 알아두어야 할 클래스 특성

ES6 : 클래스 식(expression) 사용가능.


// 빵(Bread) 클래스 선언
const Bread = class {};

// 잼(Jam) 클래스 선언
const Jam = class {};

Classes

ES6 사용자라면 알아두어야 할 클래스 특성

ES6 : 관례적인 이름 규칙 활용

Private Data

ES6에서는 다양한 비공개 데이터 관리 방법이 존재합니다.

(() => {

  class Coffee {

    constructor(bean, type) {

      // 공개 데이터
      this.bean = bean;

      // 비공개 데이터
      // - 관례적 이름 규칙일 뿐, 데이터가 안전하게 보호되지 않는다.
      this._type = type;

    }

  }

})();

ES6 : Object.assign() 활용

Private Data

ES6에서는 다양한 비공개 데이터 관리 방법이 존재합니다.

(() => {

  class Coffee {

    constructor(bean, type) {

      // 비공개 데이터 관리
      // - 완전한 데이터 비공개 관리가 가능하나, 메모리 누수가 발생한다.
      Object.assign(this, {
        getBean() {
          return bean;
        },
        getType() {
          return type;
        }
      });

    }

  }

})();

ES6 : 심볼 + 게터/세터 활용

Private Data

(() => {

  // 심볼
  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에서는 다양한 비공개 데이터 관리 방법이 존재합니다.

ES6 : 위크맵 활용

Private Data

(() => {

  // 위크맵
  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에서는 다양한 비공개 데이터 관리 방법이 존재합니다.

ES5 : 프로토타입 기반 상속

// 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 클래스 기반 상속은 이해하기 쉽고 사용하기 편리합니다.

Sub Classing

// 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

Inheritance Object

// 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에서는 클래스가 아닌 객체를 상속할 수 있습니다.

conclusion

클래스 및 상속 방법 등에 대해 정리해봅시다.

/**
 * ES6를 사용하여 프로젝트를 진행한다면?
 * Classes
 * 참고: https://goo.gl/gw9hr1
 * ———————————————————————————————————————————————————————————
 *
 * 객체 지향 프로그래밍이 요구되는 경우, 클래스 문법 활용
 * 비공개 데이터 관리 패턴을 숙지한 후, 적합한 방식 활용
 * 클래스 상속 및 객체 상속 활용
 *
 */

Action

실전에서 클래스 문법을 활용하는 예제를 실습해봅시다.

Modules

모듈 활용

모듈이란?

Modules

모듈은 재사용 가능한 코드 블록(Code Block)을 말합니다.

구현해야 할 기능캡슐화 하고, 공개 API를 제공하여 다른 코드에서 재사용 가능한 코드 블록입니다.

모듈의 조건

코드 추상화 : 외부 라이브러리 기능을 위임하여 사용할 때 , 복잡한 이해 없이도 사용 가능해야 합니다.

코드 캡슐화 : 코드 내부를  외부로부터 접근할 수 없도록 감출 수 있어야 합니다.

코드 재사용 : 동일한 코드를 반복하여 작성하지 않도록 재사용 가능해야 합니다.

의존성 관리 : 코드를 다시 작성하지 않고도 쉽게 의존성을 변경할 수 있어야 합니다.

클라이언트 환경에서의 모듈?

웹의 탄생 시대부터 모듈이란 개념은 존재하지 않았습니다.
모듈과 비슷한 방법은 각각 나눠진 파일을 HTML 문서에서 순차적으로 로드 해야 합니다.
주의할 점은 의존 모듈 파일이 먼저 불러 와야 올바르게 작동합니다.

IIFE

// 즉시 실행 함수식(IIFE) 패턴
// 캡슐화
(function(){

  // 비공개
  var private_var = '외부(함수 밖)에서 접근 불가';
  
})();


console.log(private_var); // ReferenceError: private_var is not defined

Module Format

모듈 포멧은 모듈을 정의하기 위해 사용할 수 있는 문법입니다.

IIFE + Revealing

// 노출 모듈(Revealing Module) 패턴
// 캡슐화
// 재사용
var module = (function(){
  
  // 비공개
  var private_var = '외부에서 접근 불가, 하지만 publicMethod()로는 접근 가능';
  // 외부로 공개하는 모듈
  return {
    publicMethod : function() { return private_var; }
  };

})();


module.publicMethod(); // '외부에서 접근 불가, 하지만 publicMethod()로는 접근 가능'
console.log( module.private_var ); // undefined

Module Format

모듈 포멧은 모듈을 정의하기 위해 사용할 수 있는 문법입니다.

CommonJS

Module Format

모듈 포멧은 모듈을 정의하기 위해 사용할 수 있는 문법입니다.

// -----------------------------------
// 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);
});

AMD

Module Format

모듈 포멧은 모듈을 정의하기 위해 사용할 수 있는 문법입니다.

// -----------------------------------
// 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();

});

UMD

Module Format

모듈 포멧은 모듈을 정의하기 위해 사용할 수 있는 문법입니다.

// -----------------------------------
// 서버/클라이언트 환경에서 모두 사용 가능
// -----------------------------------
(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 {};
}));

UMD : jQuery 플러그인

Module Format

모듈 포멧은 모듈을 정의하기 위해 사용할 수 있는 문법입니다.

(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, import

Module Format

모듈 포멧은 모듈을 정의하기 위해 사용할 수 있는 문법입니다.

// -----------------------------------
// 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 : default

Module Format

모듈 포멧은 모듈을 정의하기 위해 사용할 수 있는 문법입니다.

// -----------------------------------
// 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 : default & others

Module Format

모듈 포멧은 모듈을 정의하기 위해 사용할 수 있는 문법입니다.

// -----------------------------------
// 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 : alias

Module Format

모듈 포멧은 모듈을 정의하기 위해 사용할 수 있는 문법입니다.

// -----------------------------------
// 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 : *

Module Format

모듈 포멧은 모듈을 정의하기 위해 사용할 수 있는 문법입니다.

// -----------------------------------
// 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 객체 생성

Module Format

사용 환경 및 용도에 따라 적합한 모듈 포멧을 사용해야 합니다.

Loaders

Module Loaders / Bundlers

모듈 로더 및 번들러는 환경에 따라 선택 사용할 수 있습니다.

RequireJS : 자바스크립트 파일 모듈 로더 (클라이언트 환경)

SystemJS : ES6 모듈 문법을 사용한 로더 (서버/클라이언트 모든 환경)

모듈 로더는 클라이언트 환경에서 런타임(실행) 중에 모듈을 로드 합니다.

Bundlers

Browserify : CommonJS 방식의 모듈 번들러. (번들링 후에는 클라이언트 환경에서 사용 가능)

Webpack : 자바스크립트 파일 뿐만 아니라, 다양한 파일 포멧을 번들링 하는데 사용됩니다.

모듈 번들러는 서버 환경에서 빌드 과정에서 모듈을 로드하고 번들링 합니다.

rollup.js : 자바스크립트 모듈 번들러로 작은 코드를 라이브러리나 애플리케이션과 같이 더 크고 복잡한 코드로 컴파일합니다.

conclusion

모듈에 대해 정리해봅시다.

/**
 * ES6를 사용하여 프로젝트를 진행한다면?
 * Modules
 * import https://goo.gl/gQ6KPT
 * export https://goo.gl/VsEGFf
 * ———————————————————————————————————————————————————————————
 *
 * 모듈 단위 별, 독립 개발 지향
 * 모듈 포멧에 대한 이해 [CommonJS, AMD, UMD, ES6 Modules]
 * 주의! ES6 Modules는 현재 브라우저에서 제대로 지원이 안된다.
 * 모듈 로더 또는 번들러를 사용하여 프로젝트에 반영해야 한다.
 * 
 * 권장 방식
 *  - ES6 export, import
 *  - Webpack
 *
 */

Action

실전에서 ES6 모듈을 활용하는 예제를 실습해봅시다.

for ~ of & Iterator & Generator

반복 가능한 객체를 순환하는 새로운 반복문 및 제너레이터 활용

ES5

// 배열
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]);
});

for / for ~ in / forEach

ES5에서 자주 사용되는 반복문은 for, for ~ in, forEach() 등이 있습니다.

ES6

// 반복 가능한 객체(배열, 유사 배열, 문자열, 맵, 세트 등)
let sports_shoes = ['조깅화', '축구화', '농구화'];

// 반복 가능한 객체 순환
for (let shoes of sports_shoes) {
  console.log(shoes);
  // '조깅화'
  // '축구화'
  // '농구화'
}

for ~ of

ES6부터 새롭게 지원하는 반복문으로 for ~ in, forEach() 등을 대체합니다.

// break, continue 사용 가능
for (let shoes of sports_shoes) {
  if ( shoes === '축구화' ) { continue; }
  console.log(shoes);
  // '조깅화'
  // '농구화'
}

ES6

// 반복 가능한 객체(배열, 유사배열 객체, 문자열, 맵, 세트 등)
let sports_shoes = ['조깅화', '축구화', '농구화'];

for ~ of

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()){
    // ...
  }
}

for ~ of 사용 시, 주의할 점!

// 배열 객체
let sports_shoes = ['조깅화', '축구화', '농구화'];

for ~ of

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, "농구화"]
}

for ~ of문을 var, let, const와 사용할 때 주의할 점!

// 배열 객체
let print_sports_shoesFn = [];

for ~ of

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

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 : Iterator 프로토콜

Iterator

ES6부터 새롭게 추가된 프로토콜인 반복 가능한(Iterable) 객체와 연속된 값을 만드는 표준 방법 정의(Iterator)에 관한 개념 이해가 필요합니다.

Iterator 객체

객체가 next 메서드를 통해 { value, done }을 반환합니다.
value는 JavaScript의 모든 데이터 유형이 가능하며 done은 Boolean 값을 반환합니다.

next 메서드 & 반환 객체

const iterable_obj = {
  // 반복 가능한 객체의 조건
  [Symbol.iterator]: function() {
    // Iterator 객체 반환
    return {
      // next 메서드를 가짐 (value, done 속성을 가진 객체 반환)
      next: function() {
        return  { value, done };
      }
    };
  }
};

ES6 : 빌트인 Iterator 객체

Iterator

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 : 사용자 정의 Iterator 객체

Iterator

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 }

ES6 : Generator & yield

Generator

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
// ...

ES6 : 사용자 정의 Iterator + Generator

Generator

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 }

ES6 : 피보나치 수열 제너레이터

Generator

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]

#

conclusion

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를 활용하면 강력한 반복 알고리즘 처리 가능.
 *
 */

Array Additions

확장된 배열 객체 능력 활용

ES5

// 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> 순환

ES6 : Array.from

Array Static Methods

배열 생성자 메서드(Static Methods)를 사용해 배열 또는 배열과 유사한 객체를 손쉽게 활용할 수 있습니다.

// 전개 연산자(...)를 사용할 수도 있다.
[...lis].forEach(li => console.log(li)); // <li> 순환

ES5

// 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);

ES6 : Array.from

Array Static Methods

배열 생성자 메서드(Static Methods)를 사용해 배열 또는 배열과 유사한 객체를 손쉽게 활용할 수 있습니다.

ES5

// 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]

ES6 : Array.from

Array Static Methods

배열 생성자 메서드(Static Methods)를 사용해 배열 또는 배열과 유사한 객체를 손쉽게 활용할 수 있습니다.

ES5

// 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

ES6 : Array.of

// 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>
}

Array Static Methods

배열 생성자 메서드(Static Methods)를 사용해 배열 또는 배열과 유사한 객체를 손쉽게 활용할 수 있습니다.

ES5

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
});

ES6 : keys & values & entries

Array.prototype Methods

유용한 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
}

ES6 : keys & values & entries

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.prototype Methods

유용한 Array 객체 인스턴스 메서드가 새롭게 추가되었습니다.

ES5

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

ES6 : find

var numbers = [100, 105, 103, 109];

// Array.prototype.find 메서드 사용
var item = numbers.find(item => item > 100 && item < 105);

console.log(item); // 103

Array.prototype Methods

유용한 Array 객체 인스턴스 메서드가 새롭게 추가되었습니다.

ES5

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

ES6 : findIndex

var numbers = [100, 105, 103, 109];

// Array.prototype.findIndex 메서드 사용
var item = numbers.findIndex(item => item > 105);

console.log(item); // 3

Array.prototype Methods

유용한 Array 객체 인스턴스 메서드가 새롭게 추가되었습니다.

indexOf vs findIndex

// 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.prototype Methods

유용한 Array 객체 인스턴스 메서드가 새롭게 추가되었습니다.

ES5

var numbers = [100, 105, 103, 109];

// 배열 아이템이 포함 되었는지 확인하는 유틸리티 함수
function isIncludeItemArray(array, item) {
  return array.indexOf(item) > -1;
}

// 유틸리티 함수를 사용해 아이템이 포함 되었는지 유무 확인
if ( !isIncludeItemArray(numbers, 107) ) {
  numbers.push(107);
}

ES6 : includes

var numbers = [100, 105, 103, 109];

// Array.prototype.includes 메서드를 사용해 아이템이 포함 되었는지 유무 확인
if ( !numbers.includes(107) ) {
  numbers.push(107);
}

Array.prototype Methods

유용한 Array 객체 인스턴스 메서드가 새롭게 추가되었습니다.

ES5

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]

ES6 : fill

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.prototype Methods

유용한 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]

ES6 : copyWithin

// 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.prototype Methods

유용한 Array 객체 인스턴스 메서드가 새롭게 추가되었습니다.

conclusion

배열 객체와 관련된 새로운 능력을 정리해봅시다.

/**
 * 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
 * ———————————————————————————————————————————————————————————
 *
 * 배열은 데이터 관리에 자주 사용되는 객체로 새롭게 추가된
 * 스태틱 메서드, 프로토타입 인스턴스 메서드를 적극 활용
 *
 */

Set & Map

세트, 맵 활용

Array vs Set

// 배열(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

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

// Set.prototype 객체
console.dir(Set.prototype);

// .constructor
// .size
// .add()
// .has()
// .delete()
// .clear()
// .forEach()
// .entries()
// .keys()
// .values()

Set

Set 객체는 값 콜렉션(Collections)으로 삽입된 순서대로 요소들을 반복(iterate) 할 수 있습니다.

.size & .add() & .has()

// Set 객체 생성
const phones = new Set(); // Set(0) {}

Set

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
}

.delete() & .clear()

// phones 세트
phones; // Set(4) {"iPhoneX", "Gallaxy Note 8", "V30", "Mi5"}

Set

Set 객체는 값 콜렉션(Collections)으로 삽입된 순서대로 요소들을 반복(iterate) 할 수 있습니다.

// 아이템 제거
phones.delete('iPhoneX'); // true
phones.delete('Blackberry'); // false
// 저장된 아이템 개수 출력
console.log(phones.size); // 3
// 저장된 아이템 모두 제거
phones.clear(); // Set(0) {}

.forEach()

// phones 세트
phones; // Set(4) {"iPhoneX", "Gallaxy Note 8", "V30", "Mi5"}

Set

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)]
});

for ~ of

// phones 세트
phones; // Set(4) {"iPhoneX", "Gallaxy Note 8", "V30", "Mi5"}

Set

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' 
} 

mapping & filtering

// phones 세트
phones; // Set(4) {"iPhoneX", "Gallaxy Note 8", "V30", "Mi5"}

Set

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) );

집합 : union & intersection & difference & superset & subset

Set

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.prototype 확장

Set

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 vs Map

// 객체(Object)
let capitals = {
  korea_ref : '서울',
  china     : '북경',
  usa       : '워싱턴 D.C'
};

console.log(capitals); // {korea_ref: "서울", china: "북경", usa: "워싱턴 D.C"}

Map

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"}

Object vs Map

Map

Map 객체는 속성(key)/값(value) 쌍으로 구성된 객체입니다.

키(key)로 저장하고, 불러오고, 삭제하거나, 저장된 값(value)을 확인할 수 있다는 점에서 객체는 맵과 유사합니다.

유사한 점

문자, 심볼만 키(key)로 사용할 수 있는 객체에 반해, 맵은 어떤 값도 키로 사용할 수 있습니다.
그리고 객체는 저장된 데이터(키: 값)의 개수를 알 수 없지만, 맵은 .size 속성을 통해 알 수 있습니다.

다른 점

데이터 콜렉션(Collection)을 다룰 때 주로 사용하면 좋습니다.
키 값을 문자, 심볼이 아닌 것을 사용해야 하거나 데이터가 순환(Iterate) 되어야 할 경우 유용합니다.
그 외의 경우는 객체를 사용하는 것이 좋습니다.

언제 사용하면 좋을까?

Map.prototype

// Map.prototype 객체
console.dir(Map.prototype);

// .constructor
// .size
// .set()
// .get()
// .has()
// .delete()
// .clear()
// .forEach()
// .entries()
// .keys()
// .values()

Map

Map 객체는 속성(key)/값(value) 쌍으로 구성된 객체입니다.

.size & .set() & .get() & .has()

// 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

Map 객체는 속성(key)/값(value) 쌍으로 구성된 객체입니다.

.delete & .clear()

Map

Map 객체는 속성(key)/값(value) 쌍으로 구성된 객체입니다.

// capitals 맵
capitals; // Map(4) {"한국" => "서울", ...}
// 아이템 제거
capitals.delete('일본'); // true
capitals.delete('러시아'); // false
// 저장된 아이템 개수 출력
console.log(capitals.size); // 3
// 저장된 아이템 모두 제거
capitals.clear(); // Map(0) {}

Map

Map 객체는 속성(key)/값(value) 쌍으로 구성된 객체입니다.

.forEach()

// 아이템 순환
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

Map 객체는 속성(key)/값(value) 쌍으로 구성된 객체입니다.

for ~ of

// 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) {"한국" => "서울", ...}

.keys() & .values() & .entries()

Map

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);
}

[key: value] Array

Map

Map 객체는 속성(key)/값(value) 쌍으로 구성된 객체입니다.

// capitals 맵
// 배열([]) 내부에 키/값 배열([key, value])을 포함.
let capitals = new Map([
  ['한국', '서울'],
  ['중국', '북경'],
  ['미국', '워싱턴 D.C'],
  ['일본', '동경']
]);
// 맵 → 배열
console.log( [...capitals] );

/* 결과:
[
  ['한국', '서울'],
  ['중국', '북경'],
  ['미국', '워싱턴 D.C'],
  ['일본', '동경']
] 
*/

Map

Map 객체는 속성(key)/값(value) 쌍으로 구성된 객체입니다.

mapping & filtering

// 매핑(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

Map 객체는 속성(key)/값(value) 쌍으로 구성된 객체입니다.

combineMap & convertArray & Map.prototype 확장

// 맵 병합 유틸리티 함수
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

Map 객체는 속성(key)/값(value) 쌍으로 구성된 객체입니다.

map2json & json2map 유틸리티 함수

// 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

Map 객체는 속성(key)/값(value) 쌍으로 구성된 객체입니다.

strMap2obj & obj2strMap 유틸리티 함수

// 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

Map 객체는 속성(key)/값(value) 쌍으로 구성된 객체입니다.

strMap2json & json2strMap 유틸리티 함수

// 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}

conclusion

세트 및 맵을 정리해봅시다.

/**
 * ES6를 사용하여 프로젝트를 진행한다면?
 * Set, Map
 *
 * Set     https://goo.gl/rLszbu
 * Map     https://goo.gl/DUs6c8
 * ———————————————————————————————————————————————————————————
 *
 * Set은 Array와 유사하나, 중복된 데이터를 허용하지 않아 데이터 관리에 유용
 * Map은 Object와 유사하나, 키로 어떤 값이든 사용 가능하며 데이터를 순환할 경우 유용
 * 즉, Set, Map은 데이터 관리에 매우 유용하니 적극 활용
 *
 */

위크 세트, 위크 맵 활용

WeakSet & WeakMap

Set vs WeakSet

// 데이터(객체)
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

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 객체만 사용 가능합니다!');
    } 
  }
}

Map vs WeakMap

// 데이터(객체)
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

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');
  }
}

conclusion

WeakSet, WeakMap을 정리해봅시다.

/**
 * ES6를 사용하여 프로젝트를 진행한다면?
 * WeakSet, WeakMap
 *
 * WeakSet https://goo.gl/5viDqn
 * WeakMap https://goo.gl/VWqA2B
 * ———————————————————————————————————————————————————————————
 *
 * Set, Map과 유사하지만, 다음과 같은 특징을 가짐
 *
 * 특징
 * - 객체 유형만 저장 가능 (원시데이터 불가능)
 * - 열거(Enumerable) 불가능
 * - 약한 참조 (메모리 누수 방지)
 *
 */

Promise

프로미스 활용

Promise

Promise 객체는 비동기 처리를 위한 목적으로 사용되며, 당장은 아니더라도
나중에 처리될 것으로 기대되는 연산을 수행할 때 사용합니다.

ES5 : 콜백(Callback)

// 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

Promise 객체는 비동기 처리를 위한 목적으로 사용되며, 당장은 아니더라도
나중에 처리될 것으로 기대되는 연산을 수행할 때 사용합니다.

ES5 : 동기 vs 비동기

// 동기(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

Promise 객체는 비동기 처리를 위한 목적으로 사용되며, 당장은 아니더라도
나중에 처리될 것으로 기대되는 연산을 수행할 때 사용합니다.

ES5 : 콜백지옥

// 비동기(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 객체는 비동기 처리를 위한 목적으로 사용되며, 당장은 아니더라도
나중에 처리될 것으로 기대되는 연산을 수행할 때 사용합니다.

ES6 : 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

Promise 객체는 비동기 처리를 위한 목적으로 사용되며, 당장은 아니더라도
나중에 처리될 것으로 기대되는 연산을 수행할 때 사용합니다.

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 객체는 비동기 처리를 위한 목적으로 사용되며, 당장은 아니더라도
나중에 처리될 것으로 기대되는 연산을 수행할 때 사용합니다.

ES6

// 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

fetch()는 네트워크를 통해 비동기 적으로 리소스를 가져 오는 쉽고 논리적인 방법을 제공하는 전역 함수입니다. (promise 객체 반환)

ES6 : fetch

// 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 Static Methods

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 Static Methods

Promise 클래스의 메서드를 살펴봅시다.

Promise.all()

// 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 Static Methods

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 Static Methods

Promise 클래스의 메서드를 살펴봅시다.

Promise.race()

// 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 Static Methods

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} 오류 발생!`));

conclusion

Promise를 정리해봅시다.

/**
 * ES6를 사용하여 프로젝트를 진행한다면?
 * Promise, Fetch API
 *
 * Promise https://goo.gl/Cstuig
 * Fetch   https://goo.gl/wbb1zc
 * ———————————————————————————————————————————————————————————
 *
 * Callback 함수 패턴이 아닌, Promise 패턴을 사용하여 비동기 프로그래밍
 *
 */

Reference

레퍼런스