프런트엔드/프로그래밍 언어

자바스크립트 문법 한장에 정리

조드래곤나인 2023. 7. 14. 17:36

 

서사

 

자바스크립트의 기반이 되는 ECMAScript가 매년 새로운 문법이 추가되기 때문에

자바스크립트는 매년 새로운 문법이 추가됩니다.

 

프런트 엔드 개발자라면 자바스크립트는 기본적으로 능숙하게 다루겠지만

그렇다고 자바스크립트의 문법을 잘 안다고 할 순 없습니다.

실무에 몇 년, 몇십 년 일해도 가끔은 뒤돌아서 기본을 볼 필요가 있다고 생각합니다.

 

이 글에서는 현재의 자바스크립트 기반이 되는 ECMAScript 2015를 정리했습니다.

 

 

var vs let

var is Function Scope

var 키워드는 Function Scope로 블럭에 정의를 해도 블럭 밖에서 사용이 가능합니다.

if (true) {
  var x = 3
}
console.log(x) //3
 

 

let is Block Scope

let 키워드는 Block Scope로 블럭에 정의 후 블럭 밖에서 참조시 ReferenceError가 발생하게 됩니다.

if (true) {
  let x = 3
}
console.log(x) //ReferenceError
 

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

 

let vs const

 

Block Scope 선언 키워드에서 let 이외에도 const 라는 키워드가 있습니다.

다른 점은 const는 불변이고 let은 변경이 가능합니다. 여기서 볼 수 있듯이 const로 정의할 경우 값을 변경하려고 했을 때 TypeError가 발생합니다.

 

 

let is not immutable

let num = 0
num = 1 // Fine
 

 

const is immutable

const num = 0
num = 1 // TypeError
 

const

 

const 키워드는 해당 값을 불변으로 만들기 때문에

오브젝트를 할당했을 때는 오브젝트에 프로퍼티는 변경이 가능합니다.

 

그 이유는 오브젝트는 Heap Memory에 할당이 되고

할당된 주소값만 상수에 할당되기 때문입니다.

그래서 프로퍼티를 변경/수정/삭제를 할 수 있습니다.

 

 

content can be changed

const obj = { a: 'a' }
obj.b = 'B' //Working
obj.a = 'A' //Working
delete obj.a //Working
 

 

freeze

 

프로퍼티 변경을 막을려면 freeze라는 함수를 사용됩니다.

하지만 프로퍼티의 값이 오브젝트일경우는 변경이 가능합니다.

const obj = {a: 'a'}
Object.freeze(obj)
obj.b = 'B' //Not Working
obj.a = 'A' //Not Working
delete obj.a //Not Working
 
const obj = {x:{}}
Object.freeze(obj)
obj.x.a = 'A' //Working
 

 

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

 

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

 

Class

 

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, 10
 

 

Static

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

 

Destructuring

 

object나 Array의 구조를 알고 있으면 새로운 변수를 정의할 때

필요한 부분만 해체해서 사용할 수 있습니다.

 

Object

객체에 정의된 프로퍼티를 가져와 변수명과 같을 정의할 수 있습니다.

const {weight, height} = {weight: 72, height: 173}
console.log(weight, height) // 72 173
 

 

Array

 

Array는 대괄호를 사용해서 인덱스에 변수를 정의하면

해당 변수에 해당 인덱스 값이 정의됩니다.

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

 

Default 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: 1234
 

 

Destructuring

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 WorldI'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) //ReferenceError
 

 

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

 

Module

모듈 기능을 사용해서 소스 코드의 목적이나 역할에 따라 파일을 분리하고 다른 파일을 읽을 수 있습니다.

 

 

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

 

default

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

 

WeakSet

 

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

 

Promise

 

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

 

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

Good

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

 

Proxy

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 bar
 

 

Function

const sum = (a, b) => a + b
const handler = {
  apply(target, thisArg, argumentsList) {
    return target(...argumentsList)
  }
}

const proxySum = new Proxy(sum, handler)
proxySum(1, 2) //3
 

 

Class

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

 

Builtin Method

String

"Hello".startsWith("Hell") // output: true
"Goodbye".endsWith("bye") // output: true
"Jar".repeat(2) // output: JarJar
"abcedf".includes("bce") // output: true
 

 

Number

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
 

 


 

 

공식 기술블로그 링크

 

728x90