본문 바로가기

JavaScript

[#. JavaScript] ES6 정리하기

반응형

 

  • arrows 
  • classes 
  • enhanced object literals 
  • template strings 
  • destructuring 
  • default + rest + spread 
  • let + const 
  • iterators, for..of 
  • generators 
  • unicode 
  • modules 
  • map + set + weakmap + weakset 
  • proxies, reflect API
  • symbols 
  • subclassable built-ins
  • promises
  • math + number + string + array + object APIs
  • binary and octal literals
  • tail calls

 

 

 

 

 

① 화살표 함수(Arrow Function)

 

`=>` 화살표를 사용한다

 

 

Arrow Function에는 this, prototype, arguments가 없다

 

 

⑴ this가 없다 => this를 차단한다

 

일반 함수와 달리 화살표 함수에는 고유한 this가 없으며 인자를 바인딩할 수 없다

렉시컬 스코프 (Lexical Scope)란, 함수를 어디서 선언하였는지에 따라 상위 스코프를 결정하는 것을 뜻한다

화살표 함수는 자신의 this를 가질 수 없기 때문에 자신의 상위의 렉시컬 범위(Lexical Scope)에서 가장 가까운 this를 검색해서 사용한다

 

 

var name = "arrow func"
let arr = {
  name: "reg func",
  thisInArrow: () => {
    console.log(this.name); 
  },
  thisInRegular() {
    console.log(this.name); 
  }
};

arr.thisInArrow();	// arrow func
arr.thisInRegular();	// reg func

 

 

 

 

아래 코드에서 콜백함수(setTimeout) 내부의 this는 전역 객체 window를 가리킨다

myVar 프로퍼티는 window가 아니라 obj의 프로퍼티이기 때문에 오류가 발생한다

 

let obj = {
    myVar: 'foo',

    myFunc: function() { 
        console.log(this.myVar);   

        setTimeout(function() {
            console.log(this.myVar);
        }, 1000)
    }
}
obj.myFunc();
  
// foo 출력
// 1초 뒤 undefined 출력

 

 

아래처럼 화살표 함수를 사용하게 되면 this가 상위 Scope에서 this를 찾아서 사용한다

이때 this는 상위 Scope인 myFunc()에서 먼저 myVar을 찾고 없으면 obj에서 myVar을 찾게 된다

 

 

let obj = {
    myVar: 'foo',

    myFunc: function() { 
        console.log(this.myVar);   

        setTimeout(() => {
        console.log(this.myVar);
        }, 1000)
    }
}
obj.myFunc();
  
// foo 출력
// 1초 뒤 foo 출력

 

* 기본적으로 this는 전역 객체를 가리킨다

Node환경에서는 Global 객체를, Browser에서는 Window 객체를 가리킨다

 

 

 

 

⑵ prototype이 없다

 

var Foo = () => {}
var foo = new Foo();	// Foo is not a constructor

function Foo2() {};
var foo2 = new Foo2();
console.log(foo2);	// Foo2 {}

 

 

 

⑶ arguments가 없다

 

매개변수를 지정하지 않아도 arguments 프로퍼티가 함수에 자동으로 생성되어 사용 가능했었으나, arguments가 없어졌다

대신 args가 생겼다

 

const myFunction1 = () => {
	console.log(arguments);		
}

const myFunction2 = (...args) => {
	console.log(args);		
}

myFunction1(1, 2, 3, 4)	// arguments is not defined
myFunction2(1, 2, 3, 4)	// [1, 2, 3, 4]

 

 

 

⑷ Hoisting 되지 않는다

 

호이스팅(hoisting)

변수의 선언과 초기화를 분리한 후, 선언만 코드의 최상단으로 옮기는 것

코드를 실행하기 전에 함수와 변수는 실행 컨텍스트를 위해 메모리에 저장된다

함수는 전체 함수에 대한 참조와 함께 저장되고, var 키워드가 있는 변수는 undefined, let 및 const 키워드가 있는 변수는 초기화되지 않은 상태로 메모리에 저장된다

 

sum(1, 2)

function sum(a, b) {
  return a + b
}

// 3 출력

sum1(1, 2)

var sum1 = (a, b) => {
  return a + b
}

// sum1 is not a function

 

 

 

 

Arrow Function의 장점

코드가 간결하다 

내부에 사용되는 this는 자연스럽게 상위에 있는 Scope, 즉 고차 함수(또는 내부 함수를 포함하고 있는 함수)의 this가 된다

Arrow Function은 같은 맥락에 있어야 하는 함수가 다른 this를 가리키게 되는 문제점을 차단해 준다

원래 같으면 어떤 this를 사용하는 것인지 binding을 해줘야 했지만 가까운 외부 this를 쓰고 싶다면 화살표 함수를 사용하면 된다

 

 

 

 

 

 

② 클래스(Classes)

 

Fields, Methods로 구성된다

fields만 있는 경우도 있는데 data class라고 한다

 

재사용성을 위해 사용한다

함수에서 함수 선언과 함수 표현식처럼 동일하게 클래스 선언(Class declarations)과 클래스 표현(Class expressions)으로 클래스를 만들 수 있다 

구조체(템플릿, 틀)를 만들기 위해 한 번만 선언해서 사용한다

ex) 완성품을 만들기 위한 상가 = Class, 부품들을 파는 곳 = Class에 만든 method

새로운 인스턴스를 생성하면 object가 된다 이때는 메모리에 올라가지 않지만 data를 넣으면 메모리에 올라간다

 

class: template

object: instance of a class

 

prototype을 베이스로 한 syntactic sugar이다

 

 

 

클래스 선언(Class declarations)

 

class Rectangle {
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }
}

 

함수 선언과 달리 클래스 선언은 Hoisting(호이스팅)이 일어나지 않는다

 

const p = new Rectangle4(); 

class Rectangle4 {}

// Uncaught ReferenceError: Rectangle4 is not defined

 

 

클래스 표현(Class expressions)

 

let Rectangle2 = class {
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }
};
console.log(Rectangle2.name);	// Rectangle2


let Rectangle3 = class Rectangle2 {
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }
};
console.log(Rectangle3.name);	// Rectangle2

 

 

 

extends

상속을 구현하여 하위 클래스 생성

 

super

하위 클래스와 프로토타입 클래스에 같은 이름의 메소드가 있을 경우에 기본적으로 프로토타입 클래스의 메소드는 호출이 되지 않고 하위클래스의 메소드가 호출된다

이 때 super 키워드를 사용해서 프로토타입 클래스의 메소드를 호출할 수 있다

 

static

정적메소드로 프로토타입에 연결되지 않는다

클래스의 인스턴스에서는 호출할 수 없다

 

 

 

 

Class의 장점

 

class는 함수 선언과 달리 호이스팅이 일어나지 않는다

호이스팅은 함수 선언 유무와 상관 없이 선언 전에도 함수 사용이 가능 한 것이다

함수를 호출하고 선언하는 경우, 호이스팅이 일어나면 문제없이 실행이 되지만, 그렇지 않으면 선언되지 않은 함수라는 에러가 뜬다

호이스팅으로 인해 코드가 꼬일 수 있는 단점을 class는 해결해 줄 수 있다

상속을 이용하여 코드를 간단하게 구현 가능하다

 

 

 

 

⑴ Class 선언

 

 

class Person {
    // constructor
    constructor(name, age) {
        // fields
        this.name = name;
        this.age = age;
    }

    // methods
    speak() {
        console.log(`${this.name}: hello!`);
    }
}

const dev = new Person("dev", 20);
console.log(dev.name);
console.log(dev.age);
dev.speak();

 

 

⑵ Getter, Setter

 

 

class User {
    constructor(firstName, lastName, age) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.age = age; // this.age => getter 호출, age => setter 호출
    }

    get age() {
        return this.age;
    }

    set age(value) {
        this.age = value;
    }
}

const user1 = new User("Bob", "Sally", -1);
console.log(user1.age);	// -1

 

=> stack exceed 가 일어나기 때문에 getter, setter에 있는 age 변수를 다르게 바꿔준다

 

    get age() {
        return this._age;
    }

    set age(value) {
        this._age = value < 0 ? 0 : value;
    }

 

 

 

⑶ Public, Private fields

 

class Experiment {
    publicField = 2;
    #privateField = 0;
}

const experiment = new Experiment();
console.log(experiment.publicField); // 2
console.log(experiment.privateField); // undefined

 

 

 

⑷ static

 

 

class Article {
    static publisher = "dev news";
    constructor(articleNumber) {
        this.articleNumber = articleNumber;
    }

    static printPublisher() {
        console.log(Article.publisher);
    }
}

const article1 = new Article(1);
const article2 = new Article(2);
console.log(article1.publisher); // undefined
Article.printPublisher(); // dev news

 

static 메서드는 object가 아니라 class 자체에 붙어있다

그래서 들어오는 데이터에 상관없이 공통적으로 사용하는 경우에 static으로 지정해 준다 => 메모리 사용 줄여줌

그러면 클래스 뒤에 바로 붙여서 사용 가능하다

 

 

 

⑸ 상속

 

 

class Shape {
    constructor(width, height, color) {
        this.width = width;
        this.height = height;
        this.color = color;
    }

    draw() {
        console.log(`drawing ${this.color} color of`);
    }

    getArea() {
        return this.width * this.height;
    }
}

class Rectangle extends Shape {}
class Triangle extends Shape {
    draw() {
        // 부모의 method도 실행
        super.draw();
        console.log("semo");
    }
    // overriding
    getArea() {
        return (this.width * this.height) / 2;
    }
}

const rectangle = new Rectangle(20, 20, 'white');
rectangle.draw(); // drawing white color of
console.log(rectangle.getArea()); // 400
const triangle = new Triangle(20, 20, 'blue');
triangle.draw(); // drawing blue color of
console.log(triangle.getArea()); // 200

 

 

 

⑹ instanceOf 

 

console.log(rectangle instanceof Rectangle); // true
console.log(triangle instanceof Rectangle); // false
console.log(triangle instanceof Triangle); // true
console.log(triangle instanceof Shape); // true
console.log(triangle instanceof Object); // true

 

 

 

 

 

③ 향상된 객체 리터럴(Enhanced Object Literal)

 

JS에서 사용되던 객체 정의 방식을 개선한 문법이다

자주 사용하던 문법을 한층 더 간결하게 작성할 수 있도록 도와준다

 

 

⑴ 객체에서 key, value 값이 같을 경우 한 번만 작성해도 된다

 

var a = "A", b = 44, c = {};
var test = {
  a,
  b,
  c
};

console.log(test.a);	// A
console.log(test.b);	// 44
console.log(test.c);	// {}

 

 

⑵ 메서드 작성 시 function 생략 가능

 

var test = {
  /*
  javascript: function(){
  	console.log('자바스크립트');
  }
  */
  javascript() {
  	console.log('자바스크립트');
  }
}

test.javascript();

 

 

 

 

 

④ 템플릿 문자열(Template Strings)

 

``(백틱)을 이용한다

내장된 표현식을 허용하는 문자열 리터럴이다

${} 안에 표현식을 넣어 값이나 간단한 식 등 자바스크립트 문법을 문자열 안에 사용할 수 있다

 

 

* 리터럴

데이터(값) 그 자체를 뜻한다

 

 

var a = 10;
var b = 90;

var str = "저는 " + (a + b) + "살입니다";
console.log(str);   //저는 100살입니다

var tps = `저는 ${a+b}살입니다`;
console.log(tps);	//저는 100살입니다

 

위 처럼 + 연산자로 문자열을 연결해 주는 것보다 가독성이 더 좋다

 

 

 

 

⑤ 구조 분해 할당(Destructuring)

 

구조 분해 할당 구문은 배열이나 객체의 속성을 해체하여 그 값을 개별 변수에 담을 수 있게 하는 JavaScript 표현식이다

객체와 배열은 자바스크립트에서 가장 많이 쓰이는 자료 구조다

객체나 배열을 변수로 분해할 수 있게 해주는 특별한 문법인 구조 분해 할당(destructuring assignment) 을 사용할 수 있다

 

 

 

var foo = ["one", "two", "three"];

var [one, two, three] = foo;
console.log(one); 	// "one"
console.log(two);	// "two"
console.log(three);	// "three"

 

 

⑴ 선언에서 분리한 할당

 

var a, b;

[a, b] = [1, 2];
console.log(a); // 1
console.log(b); // 2

 

 

⑵ 기본값

 

var a, b;

[a, b] = [1];
console.log(a); 	// 1
console.log(b); 	// undefined

[a=5, b=7] = [1];
console.log(a); 	// 1
console.log(b); 	// 7

 

 

⑶ 변수 값 교환하기

 

var a = 1;
var b = 3;

[a, b] = [b, a];

console.log(a); // 3
console.log(b); // 1

 

 

⑷ 변수에 배열의 나머지 배열 할당하기

 

var [a, ...b] = [1, 2, 3];
console.log(a); // 1
console.log(b); // [2, 3]

 

 

 

 

 

⑤ Default + Rest + Spread

 

⑴ Default parameters

 

JS에서 함수의 매개변수는 undefined가 기본이다

Default parameters를 통해 함수 선언 시 매개변수 자체에 기본값을 설정해 주어 코드를 한층 간결하게 작성할 수 있게 되었다

 

 

// 매개변수 b = 1이 바로 Default parameters b에 기본값 2을 할당해 준다
const multiply = (a, b = 2) => a * b

multiply(5);	// 10

 

 

앞쪽 매개변수(name, greeting)는 뒤쪽 매개변수(message)의 기본값으로 사용할 수 있다

 

 

function greet(name, greeting, message = greeting + ' ' + name) {
  return [name, greeting, message]
}

greet('shab', 'Hi');	// ["shab", "Hi", "Hi shab"]
greet('shab', 'Hi', 'Happy Birthday!');	// ["shab", "Hi", "Happy Birthday!"]

 

 

 

 

⑵ Rest parameters

 

마지막 파라미터만 rest 파라미터가 될 수 있다

 

const restTest = (a, b, ...rest) => {
	console.log(a);	// 1
	console.log(b);	// 2
	console.log(rest);	// [3, 4, 5, 6]
}

restTest(1, 2, 3, 4, 5, 6);

 

특정 매개변수는 정해져 있고 나머지는 몇개의 전달인자가 들어올지 모르는 상황에서 유용하다

 

 

 

⑶ 전개 구문(Spread Operator = Spread Syntax)

 

... 을 통해 사용한다

특정 객체 또는 배열의 값을 다른 객체, 배열로 복제하거나 옮길 때 사용한다

 

 

 

❶ 배열 병합

 

var arr1 = [1,2,3]; 
var arr2 = [4,5,6]; 

var arr = [...arr1, ...arr2]; 
console.log(arr); // [ 1, 2, 3, 4, 5, 6 ]

 

 

❷ 배열 복사

 

var arr1 = ['A','B']; 
var arr2 = [...arr1]; 

arr2.push('D'); 

console.log(arr2);	// ['A', 'B', 'D']
console.log(arr1);	// ['A', 'B']

 

 

 

 

⑥ Let + Const

 

let

변수 재선언이 되지 않고, 재할당이 가능하다

 

 

const

변수 재선언이 되지 않고, 재할당이 불가능하다

 

 

JS는 ES6에서 도입된 let, const를 포함하여 모든 선언(var, let, const, function, function*, class)을 호이스팅한다

var로 선언된 변수는 선언 단계와 초기화 단계가 한번에 이루어진다

let, const로 선언된 변수는 선언 단계와 초기화 단계가 분리되어 진행된다 스코프에 변수를 등록(선언 단계)하지만 초기화 단계는 변수 선언문에 도달했을 때 이루어진다 초기화 이전에 변수에 접근하려고 하면 참조 에러(ReferenceError)가 발생한다

 

 

 

 

Let, Const의 장점

 

오염을 방지하고, 불필요한 호이스팅을 피한다

 

 

 

 

⑦ Iterators, For..Of

 

⑴ Iterator

 

“순회 가능한”, “순회시킬 때 사용하는 객체”

JS에서 기본적으로 iterable하게 구현되어 있는 객체는 문자열(String), 배열(Array), Map, Set 등이다

iterable하면 주어진 순서대로 데이터를 표현할 수 있다 => 순서 보장

iterable한 객체에는 Symbol.iterator라는 프로퍼티가 있고, 이 프로퍼티에는 iterator를 리턴하는 함수가 할당되어 있다

그래서 이 함수를 실행하면 iterator 객체가 나온다

 

const array = [1, 2, 3, 4, 5];

// array의 Symbol.iterator 프로퍼티에 할당돼 있는 함수를 실행해 iterator 객체를 리턴
const iterator = array[Symbol.iterator](); 

iterator.next();
// Object { value: 1, done: false }

iterator.next();
// Object { value: 2, done: false }

iterator.next();
// Object { value: 3, done: false }

iterator.next();
// Object { value: 4, done: false }

iterator.next();
// Object { value: 5, done: false }

iterator.next();
// Object { value: undefined, done: true }

 

iterator 객체에는 next라는 메서드가 있다

이 메서드를 실행하면 현재 커서의 위치에 따라 다음 요소의 값 리턴하면서(value) 순회이 완료됐는지 알려준다

 

iterable하면 for ... of문, ... 전개 문법(Spread Operator), function* 선언, 구조 분해 할당(Destructuring)을 사용할 수 있다

 

 

 

⑵ for ... of

 

for…of 문은 내부적으로 이터레이터의 next 메소드를 호출하여 이터러블을 순회한다

next 메소드가 반환한 이터레이터 result 객체의 value 프로퍼티 값을 for…of 문의 변수에 할당한다

그리고 이터레이터 result 객체의 done 프로퍼티 값이 false이면 이터러블의 순회를 계속하고 true이면 이터러블의 순회를 중단한다

 

const array = [1, 2, 3, 4, 5];

for(data of array) {
    console.log(data);
}

// 1
// 2
// 3
// 4
// 5

 

 

 

* Iterator vs Iterable

 

Iterable은 반복 가능한 객체를 말한다
for ... of에서 이 값을 사용할 수 있다
Iterator는 반복기 자체를 의미한다 next() 메서드를 가지고 있고 value, done을 반환한다

 

 

 

 

 

⑧ Generators

 

Generator는 이 제너레이터 함수의 반환으로 iterable 프로토콜과 iterator 프로토콜을 따르는 객체이다

이 때, 제너레이터의 이터러블에서 반환하는 이터레이터는 자기 자신이다

Generator는 Iterator를 반환하는 함수이다

Generator 함수는 function 키워드 다음에 별표 (*)가 추가되고 새로운 yield 키워드를 사용한다

 

 

function *createIterator() {
    console.log("11");
    yield 1;
    console.log("22");
    yield 2;
    yield 3;
    yield 4;
}
// generator는 일반 함수처럼 호출되지만 iterator를 반환
let iterator = createIterator();

// 11 출력
console.log(iterator.next().value);	// 1
// 22 출력
console.log(iterator.next().value); // 2

 

 

Generator 함수는 각 yield 문 다음에 실행을 멈춘다

위 코드에서 yield 1을 실행한 후에 함수는 Iterator의 next() 메서드가 호출될 때까지 다른 것을 실행하지 않고, next()가 호출 되는 시점에 yield 2가 실행된다

Generator 함수를 호출한다고 해서 해당 함수 안의 코드가 바로 시작되지는 않는다

generator.next() 를 호출해야만 코드가 실행되며, yield를 한 값을 반환하고 코드의 흐름을 멈춘다

코드의 흐름이 멈추고 나서 generator.next() 를 다시 호출하면 흐름이 이어서 다시 시작된다

=> 코드의 흐름 제어 가능

 

 

 

 

⑨ 유니코드(Unicode)

 

유니코드 코드포인트 이스케이프라는 새로운 형식의 이스케이프 시퀀스를 도입했다

새로운 문법은 아래처럼 사용하는데, {중괄호} 안에 6자리까지 쓸 수 있어서 모든 유니코드 코드포인트를 넣을 수 있다

따라서 5자리 짜리 코드포인트를 가진 이모지도 정상적으로 출력이 된다

새로운 ES6 문법을 사용하면 어떤 유니코드 기호도 간단하게 표시할 수 있게 된다

 

console.log('\u{1F4A9}');	// 💩

 

 

 

 

 

모듈(Module)

 

애플리케이션을 구성하는 개별적 요소로서, 재사용 가능한 코드 조각이다

모듈에 export나 import같은 지시자를 적용하면, 다른 모듈을 현재 파일에 불러와서 그 안에 있는 함수나 변수를 호출하는 등 서로 공유할 수 있다

 

export = 모듈 내보내기 (외부 모듈에서 해당 변수나 함수에 접근 가능) 

import = 외부 모듈 불러오기

 

 

 

 

Ⓐ map + set + weakmap + weakset

 

 

⑴ Map

 

Map 객체는 Object와 상당히 유사하다

그리고 Map이 없었던 시절에는 Object가 Map 대신에 쓰였다

Object와 달리 넣은 순서가 기억되고, 키가 문자열이 아니어도 된다

 

var map = new Map([['dev', 'shab']]); 
map.set('today', 'attend'); 
map.get('dev'); // 'shab'
map.size;   // 2
map.has('hero');    // false
map.has('today');    // true
map.entries();  // {'dev' => 'shab', 'today' => 'attend'}
map.keys(); // {'dev', 'today'}
map.values();   // {'shab', 'attend'}
map.delete('today');    // true
map.clear();

 

 

 

* Map과의 차이점

  • 넣은 순서대로 반복
  • 키가 문자열뿐만 아니라 어떤 값이어도 상관없음(객체도 가능)
  • size를 항상 체크 가능
  • 편리한 메소드 제공
  • 사용할 수 있는 메서드가 Object가 사용할 수 있는 메서드보다 적다
  • Memory Reference가 없다

 

 

 

⑵ WeakMap

 

객체는 키를 약하게 참조하는 키/값 쌍 컬렉션이다

Key는 객체여야 하지만, Value는 아무 값이나 사용할 수 있다

Map과 달리 entries, keys, values 메서드를 사용할 수 없다

 

var weakMapObj = { example: 'any' };
var weakMap = new WeakMap();
weakMap.set(weakMapObj, 'zero');
weakMap.get(weakMapObj); // 'zero'

// key: {example: 'any'}
// value: "zero"

 

 

⑶ Set

 

Set은 Array와 비슷하다

Set이 없었을 때는 Array가 Set처럼 쓰였다

Set은 Array와는 달리 값이 중복될 수 없다

 

 

var set = new Set(['dev']);
set.add('shab');    // { 'dev', 'shab' }
set.has('dev'); // true
set.has('devy');    // false
set.size;   // 2
set.entries(); // {'dev' => 'dev', 'shab' => 'shab'}
set.keys(); // {'dev', 'shab'}
set.values(); // {'dev', 'shab'}
set.delete('shab'); // true
set.clear();

 

 

* Array와의 차이점

 

  • 중복 불가능
  • 중간 값 확인 불가능
  • 편리한 메소드 제공

 

 

⑷ WeakSet

 

WeakSet은 객체만을 값으로 받는다

Set과 달리 entries, keys, values 메서드를 사용할 수 없다

 

var weakSetObj = { example: 'any' };
var weakSet = new WeakSet();
weakSet.add(weakSetObj);

// WeakSet{{...}}
// 0: Object
// value: {example: 'any'}

 

 

 

 

 

Ⓑ Proxy, Reflect

 

 

⑴ 프록시(Proxy)

 

Proxy의 사전적 의미는 "대행", "대리", "위임"

프록시(Proxy)는 객체에 수행되는 동작들 (예를 들면 속성값 조회, 변경) 을 가로챌 수 있게 해주고, 커스터마이징도 할 수 있게 해주는 일종의 객체 감싸미다

프록시 객체(Proxy object)는 대상 객체(Target object) 대신 사용된다

대상 객체를 직접 사용하는 대신, 프록시 객체가 사용되며 각 작업을 대상 객체로 전달하고 결과를 다시 코드로 돌려준다

프록시 객체는 JS의 기본적인 명령에 대한 동작을 사용자 정의가 가능하도록 한다

객체 자체가 처리하는 특정 명령을 재정의할 수 있게 되는 것이다

이런 명령의 종류는 속성 검색, 접근, 할당, 열거, 함수 호출 등이 대표적이다

 

 

프록시는 세 요소로 구성된다

 

  • 핸들러(handler): trap 메서드들을 담고 있는 객체
    타겟에 대한 동작을 감지하여 그에 대응하는 트랩 메서드가 존재할 경우 해당 트랩 메서드를 호출한다
  • 트랩(trap): handler 안에 존재하는 메서드
    타겟 객체에 대한 동작을 가로채며, 사용자 정의 로직을 넣을 수 있다
  • 타겟(target): proxy로 감쌀 대상 객체

 

프록시를 어디에 사용하는지?

 

  • 로깅/관찰
  • 접근제한(권한- 읽기전용, 접근금지)
  • 유효성 검증.

 

 

 

var target = {a: 1};
 
var handler = {
  get: function(target, key) {
    // any custom logic
    console.log('GET: ' + key);
    return target[key];
  },
 
  deleteProperty: function(target, key) {
    // any custom logic
    console.log('DELETE: ' + key);
    return delete target[key];
  }
};
 
var proxiedTarget = new Proxy(target, handler);
 
console.log(proxiedTarget.a);
// GET: a
// 1
 
console.log(proxiedTarget.b);

delete proxiedTarget.a;	// = delete proxiedTarget['a']
// DELETE: a

 

 

 

⑵ 리플렉트(Reflect)

 

Proxy와 같이 JavaScript 명령을 가로챌 수 있는 메서드를 제공하는 내장 객체이다.

 

var target = {a: 1};
 
var handler = {
  get: function(target, key) {
    // any custom logic
    console.log('GET: ' + key);
    return Reflect.get(target, key);
  },
 
  deleteProperty: function(target, key) {
    // any custom logic
    console.log('DELETE: ' + key);
    return Reflect.deleteProperty(target, key);
  }
};
 
var proxiedTarget = new Proxy(target, handler);
 
console.log(proxiedTarget.a);
// GET: a
// 1
 
console.log(proxiedTarget.b);
// GET: b
// undefined
 
delete proxiedTarget.a;

 

 

 

 

* Proxy - Reflect 매치(1:1)

 

 

프록시 Trap 동작 설명 기본 동작 오버라이드 대상
get 대상 속성의 값을 반환 Reflect.get() 프로퍼티 값을 읽음
set 속성에 값을 할당 Reflect.set() 프로퍼티 값을 기록
has in 연산자 Reflect.has() in 연산자
deleteProperty delete 연산자 Reflect.deleteProperty() delete 연산자
getPrototypeOf 지정된 객체의 프로토타입을 반환 Reflect.getPrototypeOf() Object.getPrototypeOf()
setPrototypeOf 객체의 프로토타입을 지정 Reflect.setPrototypeOf() Object.setPrototypeOf()
isExtensible 객체가 확장 가능한지(객체에 새 속성을 추가할 수 있는지 여부)를 결정 Reflect.isExtensible() Object.isExtensible()
preventExtensions 새로운 속성이 이제까지 객체에 추가되는 것을 방지 Reflect.preventExtensions() Object.preventExtensions()
getOwnPropertyDescriptor 주어진 속성이 대상 객체에 존재하면, 그 속성의 서술자를 반환 Reflect.getOwnPropertyDescriptor() Object.getOwnPropertyDescriptor()
defineProperty 객체에 새로운 속성을 직접 정의하거나 이미 존재하는 속성을 수정한 후, 해당 객체를 반환 Reflect.defineProperty() Object.defineProperty()
ownKeys 대상 객체의 자체 키(상속하지 않은 키) 목록을 배열로 반환 Reflect.ownKeys() Object.keys,
Object.getOwnPropertyNames(),
Object.getOwnPropertySymbols()
apply 대상 함수를 주어진 매개변수로 호출 Reflect.apply() 함수 호출
construct 함수로 사용하는 new 연산자 Reflect.construct() new를 이용한 함수 호출

 

 

 

Proxy는 트랩을 이용해서 객체(target)의 기본 동작을 intercept 가로채서 다른 동작을 수행하게 한다
보통 로깅, 접근제한 등을 위해 사용하는데 어떤 기본 동작을 했을 때 로그를 찍기 위해 사용한다
기본 동작들은 Reflect에 정의되어 있으며 트랩과 1:1로 매칭된다
기본 동작을 가로채서 로깅이나 어떤 동작을 한 후에 Reflect가 제공하는 메서드를 통해 기본 동작도 수행하게 하는 것이다

 

 

 

 

 

 

 

Ⓒ Symbol

 

Symbol은 고유한 데이터이다

ES6에서 등장한 타입이다

Private에 대한 니즈에서 탄생했다
기본적인 for문이나 열거대상에서 제외되기 때문에 symbol에 대한 출력은 건너뛴다
외부에서 쉽게 접근해서 값을 덮어씌우거나 key값이 꼬이는 것을 방지한다

 

const a = Symbol("id");
const b = Symbol("id");

console.log(a == b); // false

const c = b;
console.log(b === c);	// true, c는 b를 참조한 거기 때문에

 

 

 

 

Ⓓ Math, Number, String, Array, Object API

 

core Math 라이브러리, Array 생성 helper, String helper, 복사를 위한 Object.assign 등 많은 라이브러리들이 추가되었다

 

// Number
Number.EPSILON;	// 2.220446049250313e-16, 가장 작은 수
Number.isInteger(4);	// true, 값이 정수인지 확인
Number.isNaN(NaN);	// true, 값이 NaN인지 확인

// Math
Math.acosh(3);  // 1.7627471740390859, 쌍곡선 아크 코사인 값을 반환
Math.hypot(3, 4);   // 9+16=25의 제곱근 => 5, 주어진 인수의 제곱의 합의 제곱근을 반환
Math.imul(Math.pow(2, 32) - 1, Math.pow(2, 32) - 2);    // 2, 2개의 파라미터의 32bit 곱셈 결과를 반환

// String
"abcde".includes("cd"); // true, 해당하는 문자열이 포함되는지 판별
"abc".repeat(3);    // "abcabcabc", 문자열을 주어진 횟수만큼 반복해 붙인 새로운 문자열을 반환

// Array
Array.from("foo");  // ["f", "o", "o"], 유사 배열 객체나 반복 가능한 객체를 얕게 복사해서 새로운 Array 객체를 생성
Array.of(1, 2, 3);  // [1, 2, 3], 새로운 Array 인스턴스를 생성
[0, 0, 0].fill(7, 0);   // [7, 7, 7], 배열의 시작 인덱스부터 끝 인덱스의 이전까지 값 하나로 채운다
[1, 2, 3].find(x => x == 3);    // 3, 주어진 판별 함수를 만족하는 첫 번째 요소의 값을 반환
[1, 2, 3].findIndex(x => x == 2);   // 1, 주어진 판별 함수를 만족하는 배열의 첫 번째 요소에 대한 인덱스를 반환

// Iterator
[1, 2, 3, 4, 5].copyWithin(3, 0);   // [1, 2, 3, 1, 2], 배열의 일부를 얕게 복사한 뒤, 동일한 배열의 다른 위치에 덮어쓰고 그 배열을 반환
["a", "b", "c"].entries();  // 배열의 각 인덱스에 대한 키/값 쌍을 가지는 새로운 Array Iterator 객체를 반환
// var iterator = ["a", "b", "c"].entries();
// iterator.next(); // done: false, value: Array(2) => 0: 0, 1: "a"
["a", "b", "c"].keys(); // 배열의 각 인덱스를 키 값으로 가지는 새로운 Array Iterator 객체를 반환
// var iterator = ["a", "b", "c"].keys();
// iterator.next(); done: false, value: 0
["a", "b", "c"].values();   // 배열의 각 인덱스에 대한 값을 가지는 새로운 Array Iterator 객체를 반환
// var iterator = ["a", "b", "c"].values();
// iterator.next(); done: false, value: "a"

// Object
Object.assign({a: 1}, {a: 4, b: 2});   // {a: 4, b: 2}, 열거할 수 있는 하나 이상의 출처 객체로부터 대상 객체로 속성을 복사할 때 사용, 대상 객체를 반환

 

 

 

 

 

* Object.assign vs Spread Operator 차이

 

 

 

Object.assign

 

var target = { a: 1, b: 2 };
var sources = { b: 3, c: 6 };

var newObj = Object.assign(target, sources);

console.log(target);	// { a: 1, b: 3, c: 6 }
console.log(newObj);	// { a: 1, b: 3, c: 6 }

 

 

Spread Operator 

 

var target = { a: 1, b: 2 };
var sources = { b: 3, c: 6 };

var newObj = {...target, ...sources}

console.log(target);	// { a: 1, b: 2 }
console.log(newObj);	// { a: 1, b: 3, c: 6 }

 

 

Object.assign은 객체 원본을 조작하고 그 후에 새로운 객체를 반환한다

Spread Operator는 객체는 원래의 값을 유지하고 새로운 객체를 반환한다

 

 

 

 

 

Ⓔ binary and octal literals

 

2진법(b), 8진법(o) numeric 리터럴 형식이 추가되었다

 

⑴ Binary, 2진수

 

ES6에서는 접두사 0b를 사용해서 2진수를 나타낸다

 

let bn = 0b111;
console.log(bn);	// 7

 

 

 

⑵ Octal, 8진수

 

ES6에서는 접두사 0o를 8진수를 나타낸다

 

var oc = 0o51;
console.log(oc);	// 41

 

 

 

 

Ⓕ 꼬리 호출(Tail Calls)

 

함수의 마지막 지점에서 다른 함수를 호출하는 패턴이다

일반적으로 함수 호출할 때, 함수 호출과 관련된 데이터에 스택 공간을 할당한다

이 데이터에는 반환 주소, 이전 스택 포인터, 함수에 대한 전달인자, 로컬 값에 대한 공간(스택 프레임)등이 있다

꼬리 위치에 있는 함수를 호출하면 호출 함수의 스택 공간을 재사용할 수 있다

=> 실행 스택을 새로 만들지 않고 기존 스택을 재사용하기 때문에 메모리 점유가 발생하지 않는다 재활용성

 

function factorial(n) {
  function cal(n, result) {
    return n === 0 ? result : cal(n - 1, n * result);
  }

  return cal(n, 1);
}

 

 

 

하지만 아래에서 확인해 보면 Safari에서만 지원한다고 한다

현재 많이 사용하는 경우가 없다고 한다

 

https://kangax.github.io/compat-table/es6/

 

ECMAScript 6 compatibility table

Sort by Engine types Features Flagged features Show obsolete platforms Show unstable platforms <!-- --> V8 SpiderMonkey JavaScriptCore Chakra Carakan KJS Other ⬤ Minor difference (1 point) ⬤ Small feature (2 points) ⬤ Medium feature (4 points) ⬤ La

kangax.github.io

 

 

 

 

Ⓖ 프로미스(Promise)

 

비동기 작업이 맞이할 미래의 완료 또는 실패와 그 결과 값을 나타낸다

비동기를 간편하게 처리할 수 있도록 도와주는 Object이다

정상적으로 기능을 수행했다면 성공 메시지와 결과값을 전달해 주고 에러를 전달해 준다

네트워크 요청이나 파일 읽어오기 등 heavy한 처리를 한 후에 어떤 동작을 수행할 것인지 컨트롤하기 위해 사용한다

데이터가 준비되는대로 then()을 수행할 수 있도록 약속한다

 

state

pending => fulfilled or  rejected

 

Produder vs Consumer

 

⑴ Produder

 

Promise() 생성하기

새로운 Promise()가 만들어질 때는 executor라는 함수가 자동으로 바로 실행된다

 

const promise = new Promise((resolve, reject) => {
    // doing some heavy work (network, read files)
    console.log("doing something");
    setTimeout(() => {
        resolve('dev');
    }, 2000);
});

 

 

⑵ Consumers

 

Promise() 사용하기

then, catch, finally

 

❶ then

 

Promise()를 return 하기 때문에 catch를 호출할 수 있다

 

promise
    .then(value => {
        console.log(value);	// 2초 뒤 dev 출력
    })

 

 

❷ catch

 

const promise = new Promise((resolve, reject) => {
    // doing some heavy work (network, read files)
    console.log("doing something");
    setTimeout(() => {
        // resolve('dev');
        reject(new Error("no network"));	// 에러 전달
    }, 2000);
});

 

promise
    .then(value => {
        console.log(value);
    })
    .catch(err => {
        console.log(err);
    })

 

 

 

❸ finally

 

마지막에 호출된다

성공, 실패에 상관없이 무조건 수행된다

 

promise
    .then(value => {
        console.log(value);
    })
    .catch(err => {
        console.log(err);
    })
    .finally(() => {
        console.log("finally");
    })

 

 

⑶ Promise Chaining

 

 

const fetchNumber = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve(1)
    }, 1000);
});

fetchNumber
    .then(num => num * 2)
    .then(num => num * 3)
    .then(num => {
        return new Promise((resolve, reject) => {
            setTimeout(() => resolve(num - 1), 1000);
        });
    })
    .then(num => console.log(num));	// 5

 

 

⑸ Error Handling

 

const getHen = () => 
    new Promise((resolve, reject) => {
        setTimeout(() => resolve("🐔"), 1000);
    });

const getEgg = hen => 
    new Promise((resolve, reject) => {
        setTimeout(() => resolve(`${hen} => 🥚`), 1000);
    })

const cook = egg => 
    new Promise((resolve, reject) => {
        setTimeout(() => resolve(`${egg} => 🥞`), 1000);
    })

getHen()
    .then(hen => getEgg(hen))	
    .then(egg => cook(egg))
    .then(meal => console.log(meal));	// 🐔 => 🥚 => 🥞
    
// 코드 축약해서 사용 가능

getHen()
    .then(getEgg)
    .then(cook)
    .then(console.log);	// 🐔 => 🥚 => 🥞

 

 

getEgg()에서 에러 발생할 경우

 

const getHen = () => 
    new Promise((resolve, reject) => {
        setTimeout(() => resolve("🐔"), 1000);
    });

const getEgg = hen => 
    new Promise((resolve, reject) => {
        setTimeout(() => reject(new Error(`error${hen} => 🥚`)), 1000);
    })

const cook = egg => 
    new Promise((resolve, reject) => {
        setTimeout(() => resolve(`${egg} => 🥞`), 1000);
    })

 

 

catch() 사용하지 않았을 경우

 

getHen()
    .then(getEgg)
    .then(cook)
    .then(console.log);	// Uncaught (in promise) Error: Error 🐔 => 🥚

 

 

catch() 사용했을 경우

 

getHen()
    .then(getEgg)
    .catch(error => {
        return '🧀';
    })
    .then(cook)
    .then(console.log);	// 🧀 => 🥚

 

 

 

 

* async await

 

기존에 존재하는 Promise 위에 간편하게 사용할 수 있도록 API를 제공하는 것을 syntactic sugar라고 한다

async await = syntactic sugar

 

 

⑴ async

 

async를 사용하게 되면 자동으로 함수 안에 있는 코드 블럭들이 Promise()로 바뀐다

then() 사용 가능하다

 

async function fetchUser() {
    // do network request in 10 secs
    return "dev";
}

const user = fetchUser();
user.then(console.log);
console.log(user);

 

 

⑵ await

 

async가 붙은 함수 안에서만 사용 가능하다

chaining보다 동기적인 코드를 쓰는 것처럼 보이기 때문에 파악이 더 쉽다

 

function delay(ms) {
    return new Promise(resolve => setTimeout(resolve, ms)); 
} 

async function getTomato() {
    await delay(1000);
    return "🍅";
}


async function getBanana() {
    await delay(1000);
    return "🍌";
}

function pickFruits() {
    return getTomato()
    .then(tomato => {
        return getBanana()
        .then(banana => `${tomato} + ${banana}`)
    });
}

pickFruits().then(console.log);	// 🍅 + 🍌

 

 

 

=> chaining이 너무 깊어지면 콜백 지옥에 빠지게 된다

 

 

function delay(ms) {
    return new Promise(resolve => setTimeout(resolve, ms)); 
} 

async function getTomato() {
    await delay(1000);
    return "🍅";
}

async function getBanana() {
    await delay(1000);
    return "🍌";
}

// async await을 이용해서 간단하게 작성 가능
async function pickFruits() {
    const tomato = await getTomato();
    const banana = await getBanana();
    return `${tomato} + ${banana}`;
}

pickFruits().then(console.log);

 

위와 같은 경우 getTomato() 1초, getBanana() 1초, 즉 2초를 기다리게 된다

병렬처리를 위한 Promise API를 사용해 보자 

 

 

⑶ useful Promise APIs

 

 

❶ Promise.all()

 

메서드는 순회 가능한 객체에 주어진 모든 프로미스가 이행한 후, 혹은 프로미스가 주어지지 않았을 때 이행하는 Promise를 반환한다

배열을 전달하게 되면 모든 Promise들을 병렬적으로 다 받은 후 배열을 반환한다

 

function pickAllFruits() {
    return Promise.all([getTomato(), getBanana()])
    .then(fruits => fruits.join(' + '));
}

pickAllFruits().then(console.log);	// 🍅 + 🍌

 

 

❷ Promise.race()

 

먼저 완료된 것의 결과값을 전달한다

 

function delay(ms) {
    return new Promise(resolve => setTimeout(resolve, ms)); 
} 

async function getTomato() {
    await delay(1000);
    return "🍅";
}

async function getBanana() {
    await delay(2000);
    return "🍌";
}

function pickOnlyOne() {
    return Promise.race([getTomato(), getBanana()]);
}

pickOnlyOne().then(console.log);	// 🍅

 

 

 

 

 

Ⓗ subclassable built-ins

 

기존에 존재하는 Array 등의 기능을 상속 받고 거기에 서브 클래스(자식)에서 기능을 확장해서 더 다양하게 사용하기 위한 것이다

 

// Pseudo-code of Array
class Array {
    constructor(...args) { /* ... */ }
    static [Symbol.create]() {
        // Install special [[DefineOwnProperty]]
        // to magically update 'length'
    }
}
// User code of Array subclass
class MyArray extends Array {
    constructor(...args) { super(...args); }
}

// Two-phase 'new':
// 1) Call @@create to allocate object
// 2) Invoke constructor on new instance
var arr = new MyArray();
arr[1] = 12;
arr.length == 2

 

 

 

 

* Array.prototype vs Array.from(), Array.of() (prototype이 안 붙어있음) 차이

 

instance 메서드 vs static 메서드

 

var arr = [1, 2, 3];	// 1
var newArray = new Array(1, 2, 3);	// 2

 

1, 2는 달라 보이지만 둘다 new Array()을 통해 생성된 배열이다 => 인스턴스

1은 단지 JS에서 자동으로 인식해서 생성해 주었기 때문이다

이를 prototype이라고 한다 

이런 prototype은 인스턴스 메서드를 사용 가능하지만, 정적 메서드는 사용할 수 없다

정적 메서드는 일반 메서드들과 다르게 앞에 static이라는 키워드가 붙은 함수들을 뜻한다

인스턴스로 호출이 불가능하며, 별도로 생성되지 않아도 호출이 가능하다

 

var arr = [1, 2, 3];	// 1
arr.push(4);	// [1, 2, 3, 4], 인스턴스 메서드
arr.of(1, 2, 3);	// arr.of is not a function
Array.of(1, 2, 3);	// [1, 2, 3], 정적 메서드

 

 

 

 

class Animal {
    speak() {
        console.log("speak");
        return this;
    }
    static eat() {
        console.log("eat");
          return this;
    }
  }
  
Animal.eat();	// eat 출력

 

Class는 인스턴스를 생성하지 않았기 때문에 정적 메서드만 사용 가능하다

 

 

 

 

class Animal {
    static of() {        
      return new Animal();	// 인스턴스 생성
    }
    speak() {
        console.log("speak");
        return this;	// 인스턴스 자체 반환
    }
  }
  
Animal
    .of()
    .speak();	// speak 출력

 

return this는 인스턴스를 반환하기 때문에 Animal.of() 뒤에 .speak() 메서드를 사용할 수 있는 것이다

 

 

class Animal {
    static of() {        
      return new Animal();
    }
    subtract(num) {        
        return this;
    }
    add(data) {
        console.log(data);
    }
  }
  
Animal
    .of()
    .subtract(1)
    .add(3);

 

subtract() 메서드에서 return this 하는 이유는 메서드 자신이 아니라 Animal 인스턴스 자체를 반환하는 것이다

return this 를 하지 않으면 에러가 발생한다

 

class Animal {
    static of() {        
      return new Animal();
    }
    subtract(num) {        
        return num;
    }
    add(data) {
        console.log(data);
    }
  }
  
Animal
    .of()
    .subtract(1)
    .add(3);	// Animal.of(...).subtract(...).add is not a function

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

https://github.com/lukehoban/es6features

 

GitHub - lukehoban/es6features: Overview of ECMAScript 6 features

Overview of ECMAScript 6 features. Contribute to lukehoban/es6features development by creating an account on GitHub.

github.com

 

반응형