자바스크립트 문법 한장에 정리 
서사 
자바스크립트의 기반이 되는 ECMAScript가 매년 새로운 문법이 추가되기 때문에 자바스크립트는 매년 새로운 문법이 추가됩니다.
프런트 엔드 개발자라면 자바스크립트는 기본적으로 능숙하게 다루겠지만 그렇다고 자바스크립트의 문법을 잘 안다고 할 순 없습니다. 실무에 몇 년, 몇십 년 일해도 가끔은 뒤돌아서 기본을 볼 필요가 있다고 생각합니다.
이 글에서는 현재의 자바스크립트 기반이 되는 ECMAScript 2015를 정리했습니다.
var vs let 
var is Function Scope 
var 키워드는 Function Scope로 블럭에 정의를 해도 블럭 밖에서 사용이 가능합니다.
if (true) {
  var x = 3
}
console.log(x) //3let is Block Scope 
let 키워드는 Block Scope로 블럭에 정의 후 블럭 밖에서 참조시 ReferenceError가 발생하게 됩니다.
if (true) {
  let x = 3
}
console.log(x) //ReferenceErrorvar vs let - loop scoping 
for 루프를 통해 Function Scope와 Block Scope의 차이를 알수 있습니다. var 키워드의 경우 for 의 선언문에 정의된 변수는 블럭 밖에서 사용할 수 있습니다. 그렇기 때문에 for 밖에서 선언을 하기도 했습니다. let 키워드를 사용할 경우 for 선언문에 정의를 해도 블럭 밖에서 사용을 할 수 없기 때문에 더이상 for loop를 사용하기 전에 같은 변수를 정의할 필요없어졌습니다.
for(var i = 0; i < 3; i++) {}
console.log(i); //3
for(let i = 0; i < 3; i++) {}
console.log(i); //ReferenceErrorlet vs const 
Block Scope 선언 키워드에서 let 이외에도 const 라는 키워드가 있습니다. 다른 점은 const는 불변이고 let은 변경이 가능합니다. 여기서 볼 수 있듯이 const로 정의할 경우 값을 변경하려고 했을 때 TypeError가 발생합니다.
let is not immutable 
let num = 0
num = 1 // Fineconst is immutable 
const num = 0
num = 1 // TypeErrorconst 
const 키워드는 해당 값을 불변으로 만들기 때문에 오브젝트를 할당했을 때는 오브젝트에 프로퍼티는 변경이 가능합니다. 그 이유는 오브젝트는 Heap Memory에 할당이 되고 할당된 주소값만 상수에 할당되기 때문입니다. 그래서 프로퍼티를 변경/수정/삭제를 할 수 있습니다.
content can be changed 
const obj = { a: 'a' }
obj.b = 'B' //Working
obj.a = 'A' //Working
delete obj.a //Workingfreeze 
프로퍼티 변경을 막을려면 freeze라는 함수를 사용됩니다. 하지만 프로퍼티의 값이 오브젝트일경우는 변경이 가능합니다.
const obj = {a: 'a'}
Object.freeze(obj)
obj.b = 'B' //Not Working
obj.a = 'A' //Not Working
delete obj.a //Not Workingconst obj = {x:{}}
Object.freeze(obj)
obj.x.a = 'A' //Workingfunction declaration 
function sum (a, b) {
  return a + b
}
function getBMI (weight, height) {
  height /= 100
  return weight / Math.pow(height, 2)
}Arrow function 
const sum = (a, b) => a + b
const getBMI = (weight, height) => {
  height /= 100
  return weight / Math.pow(height, 2)
}Always anonymous 
화살표함수는 항상 익명함수로 정의됩니다.
const sum = (a, b) => a + b
const sum = sum(a, b) => a + b //SyntaxErrorLexical this 
화살표함수에서는 this를 주변(lexical)에서 가져옵니다. 즉, 더이상 bind(this), self = this 코드를 선언할 필요가 없습니다.
const obj = {
  data: '',
  updateData () {
    $http.get('/path').then(data => this.data = data)
  }
}It can’t be used constructor 
화살표함수는 프로토타입 생성하지 않기 때문에 함수의 기능만 할수 있습니다. 즉, 생성자의 기능을 할 수 없습니다.
const Person = () => {}
new Person() //TypeError
Person.prototype //UndefinedClass 
class 키워드를 통해 클래스를 선언합니다. new 생성자 없이 초기화할 수 없습니다.
Class declaration 
class MyClass {}
const instance = new MyClass()Class expression 
const MyClass = class {}
const instance = new MyClass()Sub classing 
class Point { 
  constructor (x, y) { 
    this.x = x     
    this.y = y   
  }   
  toString () {     
    return `${this.x} ${this.y}`   
  }
 }
class ColorPoint extends Point {
   constructor (x, y, color) {     
    super(x, y) //Must call super     
    this.color = color   
  }   
  toString () {     
    return `${super.toString()} in ${this.color}`   
  }
}Getter & Setter 
class Point {
  constructor(x, y) {
    this.x = x
    this.y = y
  }
  get axis() {
    return [this.x, this.y]
  }
  set axis([x, y]) {
    this.x = x
    this.y = y
  }
}
const point = new Point(0, 0)
console.log(point.axis) //[0, 0]
point.axis = [10, 10]
console.log(point.x, point.y) //10, 10Static 
class Point {
  static pointMethod() {
  }
}
class ColorPoint extends Point {
  static pointmethod() {
    super.pointMethod()
  }
}
Point.pointmethod()
ColorPoint.pointmethod()Assignment 
Object property 
변수를 그대로 할당하면 변수명은 property 명으로 들어가고 변수의 값이 property 값으로 들어가게 됩니다.
const ip = '127.0.0.1'
const port = 1234
const serverInfo = {ip, port}
// { ip: '127.0.0.1', port: 1234 }Method Definition 
프로퍼티와 익명함수를 사용하지 많고 메소드를 사용할 수 있습니다.
const person = {
  name: '',
  getName() {
    return this.name
  },
  setName(name) {
    this.name = name;
  }
}
person.setName('Peter')
console.log(person.getName()) //PeterDestructuring 
object나 Array의 구조를 알고 있으면 새로운 변수를 정의할 때 필요한 부분만 해체해서 사용할 수 있습니다.
Object 
객체에 정의된 프로퍼티를 가져와 변수명과 같을 정의할 수 있습니다.
const {weight, height} = {weight: 72, height: 173}
console.log(weight, height) // 72 173Array 
Array는 대괄호를 사용해서 인덱스에 변수를 정의하면 해당 변수에 해당 인덱스 값이 정의됩니다.
const [a, , b] = [0, 1, 2]
console.log(a, b) //0 2Default value 
Parameter와 Destructuring 부분에 기본값을 정의할 수 있습니다. 변수명 이퀄을 사용해서 기본값을 정의할 수 있습니다.
Parameter 
const serverInfo = {
  ip: null,
  port: null,
  setDevInfo(ip = '127.0.0.1', port = 1234) {
    this.ip = ip
    this.port = port
  }
}
serverInfo.setDevInfo() //ip: 127.0.0.1, port: 1234Destructuring 
const peter = {weight: 72, height: 173}
const {weight, height, age = 25} = peter
console.log(weight, height, age) //72, 173, 25해체할당 
나머지 연산자를 통해 객체 프로퍼티와 배열 요소에 할당할 수도 있습니다.
const obj = {};
[, ...obj.prop] = ['a', 'b', 'c'];해체를 통해 할당하는 경우 할당 대상은 좌변에 올수 있는 모든 것이 될 수 있습니다.
const obj = {};
const arr = [];
({foo: obj.prop, bar: arr[0]} = {foo: 123, bar: true});
console.log(obj) //{prop: 123}
console.log(arr) // [true]기존에 정의된 변수를 할당대상으로 지정할 경우 괄호를 묶어야 합니다.
let a, b
{a, b} = someObject; //SyntaxError
({a, b} = someObject) //Ok... 
점점점이라는 문법이 추가 됬는 데, 이 문법은 두가지 기능이 있습니다.
Rest Parameter 
Rest Operator 기능은 함수로 전달되는 argument들중 변수로 정의되지 않는 것들을 모두 가져옵니다. 가장 중요한 것은 Rest Operator는 항상 마지막에 사용해야 합니다.
function foo(...args) {} //args : [1,2,3] 
foo(1,2,3)  
function bar (first, ...args) {} //args : [2,3] 
bar(1,2,3)Destructuring assignment 
Spread 기능은 반복가능한 데이터 앞에 ...을 사용하면 아이템들을 순서대로 꺼내줍니다.
const odd = [1, 3, 5]
const even = [2, 4, 6]
const num = [...odd, ...even]
// [1, 3, 5, 2, 4, 6]
sum(...odd) //9
const obj1 = {a: 'a'}
const obj2 = {b: 'b'}
const mergedObj = {...obj1, ...obj2}
// {a: 'a', b: 'b'}String Template 
이 기능은 View 레이어에서 탬플릿 엔진을 사용하는 것 처럼 변수와 연산 기능을 수행할 수 있는 탬플릿 기능입니다. 백틱이라는 것으로 감싸주고 ${}를 사용하면 문자열속에 변수를 사용할 수 있습니다.
String concatenation 
const name = 'Peter'
const txt = `Hello World I'm ${name}`
/*
Hello World
I'm Peter 
*/Expression 
const math = 90
const science = 100
console.log(`Math: ${math} 
  Sciene: ${science} 
  Total: ${math + science} 
  Average: ${(math + science) / 2}`)Undefined variable 
const txt = `Hello ${name}`
console.log(txt) //ReferenceErrorSpecial Character 
특수문자를 사용할 때는 SyntaxError가 발생함으로 이스케이프 문자를 사용해야 합니다.
const txt = `Hello \$\{\}`
console.log(txt) //Hello ${}Function Body 
사례: Vue 컴파일러 결과 실행
const body = `
const a = 10;
const b = 20;
return a + b;
`;
const result = new Function(body)();
console.log(result); // 30Module 
모듈 기능을 사용해서 소스 코드의 목적이나 역할에 따라 파일을 분리하고 다른 파일을 읽을 수 있습니다.
export 
무언가를 내보낼 때는 export 키워드를 사용합니다. 변수, 함수, 클래스를 모두 내보낼 수 있습니다.
export const sqrt = Math.sqrt
export function sum(...numbers) {
  return numbers.reduce((prev, cur) => {
    return prev + cur
  })
}
export function avg(...numbers) {
  const sumResult = sum(...numbers)
  return sumResult / numbers.length
}import 
import {sum, avg} from './lib'
sum(1, 2, 3, 4) //10
avg(1,2,3,4) //2.5default 
default export는 하나만 선언할 수 있습니다.
//myFunc.js
export default function () {} 
//main.js
import myFunc from './myFunc'
myFunc()alias 
이름이 중복되는 것을 방지하기 위해 Alias를 선언할 수 있습니다.
import {getTime} from './bar'
import {getTime} from './foo'
//Duplicate declaration 
import * as bar from './bar'
import * as foo from './foo'
import {getTime as getTimeOfBar} from './bar'
import {getTime as getTimeOfFoo} from './foo'Import is read-only 
모듈에 선언된 값은 읽기전용으로 변경할 수 없습니다. let 키워드로 정의해도 다른 모듈에서는 변경이 불가능합니다.
//main.js
import {counter, incCounter} from './lib'
console.log(counter)
// 3
incCounter()
console.log(counter)
// 4
counter++
//SyntaxError 'counter' is read-only
//lib.js 
export let counter = 3
export function incCounter() {
  counter++
}Data Structure 
Map, WeakMap, Set, WeakSet 네가지의 자료구조를 내장으로 제공합니다
Map 
Map의 키는 어떤 값도 가능합니다. 객체도 키가 될 수 있습니다. 만약에 미정의된 키를 조회할 경우 undefined를 반환합니다.
const map = new Map()
map.set('foo', true)
map.set('bar', false)
map.get('foo') //true
map.has('foo') //true
map.delete('foo')
map.size //2
map.clear() //map.size === 0
const map = new Map([ ['foo', true], ['bar', false] ])Set 
Set의 고유한 데이터를 순서의 상관없이 모아둡니다. 그래서 이미 선언된 데이터도 중복선언이 되지 않습니다.
const set = new Set()
set.add('red')
set.has('red') //true
set.delete('red')
set.has('red') //false 
set.add('red')
set.add('green')
set.size //2
set.clear() //set.size === 0
const set = new Set(['red', 'green', 'blue'])
//Chainable
set.add('purple').add('black')WeakMap 
Map과 WeakMap의 차이는 가비지 컬렉터에 키가 수집이 되는 것에 막지 않습니다. 그래서 WeakMap의 키는 객체만 정의할 수 있고, 해당 객체가 삭제되면 WeakMap에서도 삭제됩니다.
WeakMap을 조회하는 것은 키로만 할 수 있습니다. 그래서 get, set, has, delete 메소드만 제공합니다.
const weakMap = new WeakMap()
let obj = {}
weakMap.set(obj, false)
console.log(weakMap.get(obj)) //false
obj = null // obj in weakMap is garbage-collectedWeakSet 
WeakSet도 WeakMap과 유사하게 동작합니다. 값은 객체만 될 수 있고, add, has, delete 메소드만 제공합니다.
const weakSet = new WeakSet()
let obj = {}
weakSet.add(obj)
weakSet.has(obj) //true
obj = null // obj in weakSet is garbage-collectedPromise 
Promise는 비동기처리에 대한 콜백의 대안입니다. 콜백보다 구현자의 노력이 필요하지만 몇 가지 이점을 제공합니다.
resolve/reject 
const promise = new Promise((resolve, reject) => {
  getData(
    response => resolve(response.data), 
    error => reject(error.message)
  )
})then / catch 
promise
  .then(data => console.log(data))
  .catch(err => console.error(err))all 
Promise의 모든 결과를 받을 때 Promise.all()로 받을 수 있습니다. 그리고 모든 결과를 배열을 통해 받습니다.
Promise.all([
  getPromise(),
  getPromise(),
  getPromise()
])   //response all data 
  .then([result1, result2, result3] => {})
  .catch(err => console.error(err))race 
가장 빠르게 응답되는 Promise를 찾을 때는 Promise.race()를 통해 사용할 수 있습니다. 이 기능을 활용하면 타임아웃기능을 구현할 수 있습니다.
Promise.race([
  getPromise(), //1000ms 
  getPromise(), //500ms 
  getPromise() //250ms
])   //response of 250ms 
  .then(data => console.log(data))
  .catch(err => console.error(err))Symbol 
Unique 
const RED1 = Symbol('red')
const RED2 = Symbol('red')
console.log(RED1 === RED2) //falseProperty Keys 
const height = Symbol('height')
const obj = {age: 25}
obj[height] = 173
Object.getOwnPropertyNames(obj) //[ 'age’ ] 
Object.getOwnPropertySymbols(obj) // [ Symbol(height) ]Clear intention 
Bad 
const SWITCH_OFF = 0
const EQUAL = 0
const getBtnStatus = () => SWITCH_OFF
const compareVersion = () => EQUAL
const btnStatus = getBtnStatus()
const result = compareVersion('0.0.1', '0.0.1')
btnStatus === comparedResult //trueGood 
const SWITCH_OFF = Symbol(0)
const EQUAL = Symbol(0)
const getBtnStatus = () => SWITCH_OFF
const compareVersion = () => EQUAL
const btnStatus = getBtnStatus()
const result = compareVersion('0.0.1', '0.0.1')
btnStatus === comparedResult //falseProxy 
Intercept and customize operations 
const target = {}
const proxy = new Proxy(target, {
  get(target, propKey) {
    console.log('GET', propKey)
    return target[propKey]
  },
  set(target, propKey, value) {
    console.log('SET', propKey)
    target[propKey] = value
  }
})
proxy.foo //GET foo
proxy.bar = 'abc' //SET bar
const target = {}
const proxy = new Proxy(target, {
  has(target, propKey) {
    console.log('HAS', propKey)
    return propKey in target
  },
  deleteProperty(target, propKey) {
    console.log('DELETE', propKey)
    delete target[propKey]
  }
})
'hello' in proxy //HAS hello
delete proxy.bara //DELETE barFunction 
const sum = (a, b) => a + b
const handler = {
  apply(target, thisArg, argumentsList) {
    return target(...argumentsList)
  }
}
const proxySum = new Proxy(sum, handler)
proxySum(1, 2) //3Class 
class Person {
  constructor(name) {
    this.name = name
  }
  getName() {
    return this.name
  }
}
const handler = {
  construct(target, args) {
    return new target(...args)
  }
}
const ProxyPerson = new Proxy(Person, handler)
const peter = new ProxyPerson('peter.cho')
peter.getName() //peter.choBuiltin Method 
String 
"Hello".startsWith("Hell") // output: true
"Goodbye".endsWith("bye") // output: true
"Jar".repeat(2) // output: JarJar
"abcedf".includes("bce") // output: trueNumber 
Number.EPSILON
Number.isNaN()
Number.isFinite()
Number.isInteger()
Number.isSafeInteger()
Number.parseFloat()
Number.parseInt()Array Static Method 
//Array.from()
//from array-like objects
let arrayLike = {
  0: 'zero',
  1: 'one',
  2: 'two',
  3: 'three',
  'length': 4
}
Array.from(arrayLike) //['zero', 'one', 'two', 'three']
Array.from({length: 5}, (v, i) => i) // [0, 1, 2, 3, 4]
Array.from('zero') ['z', 'e', 'r', 'o']
Array.of()
//A better way to create arrays
Array.of(1, 2, 3, 4, 5) //[1, 2, 3, 4, 5]Array.prototype.* 
//Array.prototype.find()
[4, 100, 7].find(x => x > 5) //100
//Array.prototype.findIndex()
[4, 100, 7].findIndex(x => x > 5) //1
//Array.prototype.fill()
(new Array(7)).fill(2).fill(3, 2, 5) //[2, 2, 3, 3, 3, 2, 2]Object Static Method 
//Object.assign()
let x = {a: 1}
Object.assign(x, {b: 2}) //{ a: 1, b: 2}
// DOM Style를 할당할 때도 사용할 수 있다.
Object.assign(dom.style, {
  color: '#fff',
  fontSize: '12px'
})
// 두 값이 같은지 확인한다.
Object.is('y', 'y') //true
Object.is({x: 1}, {x: 1}) //false
Object.is(NaN, NaN) //true