...
JavaScript 최신 문법 정리 (ES6 ~ ES13)
자바스크립트의 혁명이라 할수 있는 ECMASript 2015(ES6) 이후 추가된 자바스크립트 최신 문법 중 자주 이용할것 같은 기능들을 추려 정리해본다.
정말 편한 기능들이 많으니 자바나 C처럼 생코딩으로 구현하려 하지말고 단축문법으로 개발시간을 확 줄여보자!
...들어가기에 앞서,
💩 이모티콘은 옛날 못생긴 문법을 의미하는거고,
✨ 이모티콘은 최신 트렌드 문법을 의미하는 것이다.
편리한 연산자 최신 문법
지수 연산자
- 아주 간단하게 곱셈 기호를 두번쓰면 제곱으로 처리
2**10 // 1024
Numeric separators
- 1000000000과 과 같은 단위가 큰 숫자의 가독성을 높일 수 있게 언더바(_)로 단위를 구분할 수 있는 표현이 허용.
- ex) 1_000_000_000과 같이 천 단위로 끊어서 표기를 하는 것이 가능하기 때문에 0의 개수를 일일이 세어 볼 필요 없이 10억이라는 숫자임을 조금 더 쉽게 알 수 있음.
- 구분자는 임의의 위치에 맘대로 삽입 가능.
- 그냥 구분자 표현하는 것일뿐 구분자가 있다고 해서 숫자가 달라지거나 그러지 않음
// ✨
console.log(1_000_000_000 + 10_000); // 1000010000
console.log(1_00_00_00 + 10_0); // 1000100
Tagged Template Literal
- 함수의 실행을 템플릿 리터럴로 구현
const tag = (...args) => console.log(args);
tag`너의 정체가 도대체 뭐니?`;
// [["너의 정체가 도대체 뭐니?", raw: ['너의 정체가 도대체 뭐니']]]
const a = '정체가';
const b = '뭐니?';
tag`너의 ${a} 도대체 ${b}`;
// [['너의 ', ' 도대체 ', ' ', raw: ['너의 ', ' 도대체 ', '']], '정체가', '뭐니?']
Shorthand property names
- 프로퍼티 이름과 value값의 변수이름과 동일할때는 하나로 생략 가능
const ellie1 = {
name: 'Ellie',
age: '18',
};
const name = 'Ellie';
const age = '18';
// 💩
const ellie2 = {
name: name,
age: age,
};
// ✨
const ellie3 = {
name,
age,
};
let room = {
number: 23,
name: "hotel",
toJSON() {
return 9999;
}
};
let meetup = {
title: "Conference",
room
};
Destructuring Assignment
- 구조분해 문법
- 객체, 배열안의 원소값들을 바깥 변수로 한번에 빼서 사용하기 위한 기법
// object
const student = {
name: 'Anna',
level: 1,
};
// 💩
const name = student.name;
const level = student.level;
console.log(name, level); // Anna 1
// ✨
const { name, level } = student;
console.log(name, level); // Anna 1
const { name: studentName, level: studentLevel } = student;
console.log(studentName, studentLevel); // Anna 1
// array
const animals = ['🐶', '😽'];
// 💩
const first = animals[0];
const second = animals[1];
console.log(first, second); // 🐶 😽
// ✨
const [first, second] = animals;
console.log(first, second); // 🐶 😽
Spread Syntax
- 전개연산자
- 객체나 배열의 안의 요소들을 펼쳐 복사에 이용. 자기 자신 객체, 배열은 영향 안받음
- 함수의 아규먼트에 쓰이면, 나머지 연산자로 작용. 나머지 인자값들을 모아 배열로 생성
const obj1 = { key: 'key1' };
const obj2 = { key: 'key2' };
const array = [obj1, obj2];
// array copy
const arrayCopy = [...array];
console.log(arrayCopy); // [ { key: 'key1' }, { key: 'key2' } ]
const arrayCopy2 = [...array, { key: 'key3' }];
obj1.key = 'newKey'; // array배열은 래퍼런스 값을 갖고있는 배열이다. 그래서 전개연산자로 복사하여도
// 레퍼런스 변수는 복사로 취급하지만, 그걸 잇는 주소연결은 똑같다.
console.log(array); // [ { key: 'newKey' }, { key: 'key2' } ]
console.log(arrayCopy2); // [ { key: 'newKey' }, { key: 'key2' }, { key: 'key3' } ]
// object copy
const obj3 = { ...obj1 };
console.log(obj3); // { key: 'newKey' }
// array concatenation
const fruits1 = ['🍑', '🍓'];
const fruits2 = ['🍌', '🥝'];
const fruits = [...fruits1, ...fruits2];
console.log(fruits); // [ '🍑', '🍓', '🍌', '🥝' ]
// object merge
const dog1 = { dog: '🐕' };
const dog2 = { dog: '🐶' };
const dog = { ...dog1, ...dog2 };
console.log(dog); // { dog: '🐶' }
Short circuit
- 단축 평가.
- and연산자와 or연산자 특성을 이용해 반환값을 결정하는 기법
|| 연산자
const seaFood = {
name: "박달대게"
};
function getName(fish) {
/*if(!fish) {
return '이름없음'
}
return fish;*/
return fish || '이름없음' // 만약 fish가 null이라면 대신 or '이름없음'을 리턴
}
const name = getName(seaFood)
console.log(name) // {name : 박달대게}
const name2 = getName()
console.log(name2) // '이름없음'
fish 가 Truthy 하면 왼쪽 값인 fish 를 return 하고
fish 가 Falsy 하면 오른쪽 값인 '이름없음' 을 return 한다.
명제로 정리해보다면 아래와 같다.
- 왼쪽 값이 Truthy 하면 왼쪽 값을 리턴한다.
- 왼쪽 값이 Falsy 하면 오른쪽 값을 리턴한다.
console.log(false || 'hello') // 'hello'
console.log('' || 'hello') // 'hello'
console.log('트루' || 'hello') // '트루'
console.log(1 || 'hello') // 1
console.log('hello1' || false) // 'hello1'
console.log('hello2' || NaN) // 'hello2'
console.log(null && false) // false
console.log(undefined || null) // null
var a;
var b = null;
var c = undefined;
var d = 4;
var e = 'five';
var f = a || b || c || d || e; // null, undefiend, 빈값은 falsy하다
console.log(f); // 4
&& 연산자
const seaFood = {
name: "킹크랩"
}
function getName(fish) {
/*if(fish) {
return fish.name;
}
return undefined*/
return fish && fish.name // 만약 fish가 참이면, 우측값을 리턴한다.
}
const name = getName(seaFood);
console.log(name); // '킹크랩'
fish 도 Truthy 하고 fish.name 또한 Truthy 하다면 오른쪽의 값을 return 한다.
명제로 정리해보다면 아래와 같다.
- 왼쪽 값이 Truthy 하면 오른쪽 값이 리턴된다. 만일 오른쪽 값이 없으면 undefined나 null
- 왼쪽 값이 Falsy 면 왼쪽 값이 리턴된다.
console.log(true && "hello"); // 'hello'
console.log(null && undefined); // null
console.log(undefined && "hello"); // undefined
console.log("hello" && null); // null
console.log("hello" && "bye"); // bye
console.log(null && "hello"); // null
console.log(undefined && "hello"); // undefined
Nullish Coalescing Operator
- ?? 문법
- 거짓의 판단을 유연하게 판단. 그냥 심플하게 값이 있고 없고로 판단
var named = 'Ellie';
var userName = named || 'Guest';
console.log(userName); // Ellie
var named = null;
var userName = named || 'Guest';
console.log(userName); // Guest
// 💩
var named = '';
var userName = named || 'Guest'; // 논리값은 빈값도 false로 판단
console.log(userName); // Guest
var num = 0;
var message = num || 'Hello'; // 논리값은 0은 false로 판단
console.log(message); // Hello
// ✨
var named = '';
var userName = named ?? 'Guest'; // 그냥 심플하게 값이 있고 없고로 판단. 빈칸도 결국 값이 빈칸인 것이다.
console.log(userName); // ''
var num = 0;
var message = num ?? 'Hello'; // 0도 결국 값이 0인것
console.log(message); // 0
let a = null ?? 'hello';
let b = '' ?? true;
let c = false ?? true;
let d = 0 ?? 1;
let e = undefined ?? 'world';
console.log(a); // 'hello'
console.log(b); // ''
console.log(c); // false
console.log(d); // 0
console.log(e); // 'world'
Logical Operators and Assignment Expressions
- &&=, ||=
- 위의 Short circuit 단축평가 ||, && 의 연산자의 += *= 같은 버젼
let oldName = 'oldPerson';
let newName = 'newPerson';
// -- if문을 통한 값 대입
if(oldName) {
oldName = newName;
}
// && 연산자를 활용한 값 대입
oldName && (oldName = newName);
// Logical Operators and Assignment Expressions (&&) 를 통한 값 대입
oldName &&= newName
let oldName;
let newName = 'newPerson';
// -- if문을 통한 값 대입
if(!oldName) {
oldName = newName;
}
// && 연산자를 활용한 값 대입
oldName || (oldName = newName);
// Logical Operators and Assignment Expressions (||) 를 통한 값 대입
oldName ||= newName
Logical nullish assignment
- ??=
x ??= y 에서x가 null 이나 undefined 일 경우 y를 대입
const a = { duration: 50 };
// a.duration = a.duration ?? 10; 의 단축 버전
a.duration ??= 10; // a의 속성에 duration 속성이 있으니 10은 무시
console.log(a.duration); // expected output: 50
a.speed ??= 25; // a의 속성에 speed 라는 키가 없으니 25가 들어감
console.log(a.speed); // expected output: 25
function config(options) {
options.duration ??= 100;
options.speed ??= 25;
return options;
}
config({ duration: 125 }); // { duration: 125, speed: 25 }
config({}); // { duration: 100, speed: 25 }
문자열 최신 문법
String.prototype.replaceAll()
- 일치하는 모든 문자열을 replace
- String.prototype.replace()는 처음 나오는 일치하는 문자열만 바꾸는 것. 그래서 추가적으로 정규표현식을 사용했었지만 이제는 그럴 필요가 없어졌다
// 💩
console.log("문자열에서 여러번 나오는 문자열을 한꺼번에 변경할 수 있습니다.".replace(/문자열/g,""));
// 에서 여러번 나오는 을 한꺼번에 변경할 수 있습니다.
// ✨
console.log("문자열에서 여러번 나오는 문자열을 한꺼번에 변경할 수 있습니다.".replaceAll("문자열",""));
// 에서 여러번 나오는 을 한꺼번에 변경할 수 있습니다.
String padding
- 문자열 끝 부분이나 시작 부분을 다른 문자열로 채워 주어진 길이를 만족하는 새로운 문자열을 만들어낼 수 있다.
"hello".padStart(6); // " hello"
"hello".padEnd(6); // "hello "
"hello".padStart(3); // "hello" // 문자열 길이보다 목표 문자열 길이가 짧다면 채워넣지 않고 그대로 반환
"hello".padEnd(20, "*"); // "hello***************" // 사용자가 지정한 값으로 채우는 것도 가능
String.prototype.trimStart / trimEnd
- 빈공간을 제거하는 trim을 좀더 세부화
// trimStart()
'Testing'.trimStart() //'Testing'
' Testing'.trimStart() //'Testing'
' Testing '.trimStart() //'Testing '
'Testing '.trimStart() //'Testing '
// trimEnd()
'Testing'.trimEnd() //'Testing'
' Testing'.trimEnd() //' Testing'
' Testing '.trimEnd() //' Testing'
'Testing '.trimEnd() //'Testing'
배열 최신 문법
Array.prototype.flat()
- 중첩 배열 삭제 / 빈공간 삭제
// 중첩 다차원 배열 평평하게
const array = [1, [2, 3], [4, 5]];
array.flat(1); // 결과 : [1,2,3,4,5]
// 데이터 정리도 됨
const entries = ["bob", "sally", , , , , , , , "cindy"];
entries.flat(); // 결과 ['bob', 'sally', 'cindy'];
// flat()
['Dog', ['Sheep', 'Wolf']].flat()
//[ 'Dog', 'Sheep', 'Wolf' ]
// flatMap()
let arr1 = ["it's Sunny in", "", "California"];
arr1.map(x=>x.split(" "));
// [["it's","Sunny","in"],[""],["California"]]
arr1.flatMap(x => x.split(" "));
// ["it's","Sunny","in", "", "California"]
Array.prototype.at()
- at() 함수를 사용하여 양수 및 음수 인덱스를 모두 사용하여 문자열을 인덱싱할 수 있다.
const arrays = ['a','b','c','d'];
console.log(arrays.at(-1)); // 'd
객체 최신 문법
Optional chaining
- ?. 문법
- 프로퍼티가 없는 중첩 객체를 에러 없이 안전하게 접근할 수 있다
- ?.은 ?.'앞’의 평가 대상이 undefined나 null이면 평가를 멈추고 undefined를 반환. 평가대상이 true이면 쭉쭉 이어나가 최종값을 반환
const person1 = {
name: 'Ellie',
job: {
title: 'S/W Engineer',
manager: {
name: 'Bob',
},
},
};
const person2 = {
name: 'Bob',
};
// 💩💩💩💩💩💩
function printManager(person) { // 중첩 객체의 값을 불러오는 함수
console.log(person.job.manager.name);
}
printManager(person1); // Bob
printManager(person2); // 에러
// 💩
function printManager(person) {
console.log(person.job && person.job.manager && person.job.manager.name);
}
printManager(person1); // Bob
printManager(person2); // undefined
// ✨
function printManager(person) {
console.log(person?.job?.manager?.name);
}
printManager(person1); // Bob
printManager(person2); // undefined
?.() 함수 접근
let user1 = {
admin() {
alert("관리자 계정입니다.");
}
}
let user2 = {};
user1.admin?.(); // 관리자 계정입니다.
user2.admin?.(); // undefined
?.[] key 접근
let user1 = {
firstName: "Violet"
};
let user2 = null; // user2는 권한이 없는 사용자라고 가정해봅시다.
let key = "firstName";
alert( user1?.[key] ); // Violet
alert( user2?.[key] ); // undefined
alert( user1?.[key]?.something?.not?.existing); // undefined
delete user?.name; // user가 존재하면 user.name을 삭제합니다.
globalThis
- globalThis는 환경에 관계없이 전역객체를 통일된 방법으로 참조할 수 있는 방법이다.
- 원래는 브라우저에서의 전역(window)과 노드에서의 전역(global)이 달랐는데 이를 통일한 것으로 보면 된다.
// browser environment
console.log(globalThis); // => Window {...}
// node.js environment
console.log(globalThis); // => Object [global] {...}
// web worker environment
console.log(globalThis); // => DedicatedWorkerGlobalScope {...}
클래스 최신 문법
Class Field Declarations
- 처음에는 클래스 인스턴스를 정의하기 위해선, 자바 같이 그냥 변수 선언으로 하면 에러가 나고 무조건 생성자(constructor) 내에서만 this를 통해 클래스 필드를 선언해야 했지만, 이제는 바로 선언이 가능해 졌다.
class Hello {
fields = 0;
title;
}
Static 키워드
- static 키워드를 사용하여 정적 클래스 필드와 개인 정적 메서드를 제공
class Hello {
name = 'world';
static title = 'here';
static get title() { return title; }
}
new Hello().name // 'world'
Hello.title // 'here'
private 키워드
- 자바의 private 기능을 추가
- 메서드와 필드명 앞에 "#"을 붙여서 프라이빗 메서드와 필드 정의가 가능.
- "#"이 붙은 메서드와 필드는 프라이빗으로 동작하면 클래스 외부에서 접근이 되지 않는다.
class ClassWithPrivateField {
#privateField;
constructor() {
this.#privateField = 42;
}
}
class SubClass extends ClassWithPrivateField {
#subPrivateField;
constructor() {
super();
this.#subPrivateField = 23;
}
}
new SubClass(); // SubClass {#privateField: 42, #subPrivateField: 23}
class myClass {
#privMethod(){
return "프라이빗 메서드";
}
publicMethod(){
return "퍼블릭 메서드";
}
}
let newClass = new myClass();
console.log(newClass.privMethod()); // ERROR
Ergonomic Brand Checks for Private Fields
- private 속성/메소드를 체크
- public 필드에 대해, 클래스의 존재하지 않는 필드에 접근을 시도하면 undefined가 반환되는 반면에, private 필드는 undefined대신 예외를 발생시키게 된다.
- 따라서 in 키워드를 사용해 private 속성/메소드를 체크할 수 있다.
class VeryPrivate {
constructor() {
super()
}
#variable
#method() {}
get #getter() {}
set #setter(text) {
this.#variable = text
}
static isPrivate(obj) {
return (
#variable in obj && #method in obj && #getter in obj && #setter in obj
)
}
}
프로미스 최신 문법
Promise.all
: 모든 프라미스가 이행될 때까지 기다렸다가 그 결과값을 담은 배열을 반환하는 메서드
여러개의 프로미스가 모두 리졸브(resolve 성공) 된 후, 다음 로직을 실행해야한는 경우에 사용한다.
복수의 URL에 request를 보내고, 모든 요청의 응답이 올때 화면을 렌더 해야하는 상황이 그 예시이다. 요청에 필요한 정보를 배열로 저장하고, 그후 해당 정보를 프로미스로 매핑하여 Promise.all()에 입력 하는 방법이 자주 쓰인다.
입력된 프로미스 배열이 하나라도 리젝트(reject 실패) 되면 Promise.all 또한 리젝트 된다.
let urls = [
'https://www.example.com/users/1',
'https://www.example.com/product/2',
'https://www.example.com/article/3'
];
// fetch를 사용해 url을 프라미스로 매핑
let requests = urls.map(url => fetch(url));
// Promise.all은 모든 작업이 리졸브 될 때까지 대기
Promise.all(requests)
.then(responses => responses.forEach(
response => alert(`${response.url}: ${response.status}`)
));
const promise1 = new Promise((resolve, reject) => {
setTimeout(resolve, 200, 'foo2');
});
const promise2 = new Promise((resolve, reject) => {
setTimeout(resolve, 300, 'foo3');
});
const promise3 = new Promise((resolve, reject) => {
setTimeout(resolve, 100, 'foo1');
});
Promise.all([promise1, promise2, promise3]).then((values) => {
console.log(values);
});
// > Array ["foo2", "foo3", "foo1"]
Promise.all() 메소드는 인자를 배열로 받고, 배열 인자 순서대로 비동기 결과를 출력해준다.
위 코드같이 3번째 순서 프로미스 코드가 먼저 끝났다고 해서, 결과 배열인자가 뒤바뀌거나 그러지는 않는다.
Promise.any
: 여러 개의 프로미스를 담은 배열을 인자로 받아서 배열의 프로미스 중 하나라도 결과가 반환되면 프로미스 조건을 만족하고 종료합
여러 개의 프로미스 비동기 처리 중에서 하나라도 성공하면 모든 조건이 만족한 것으로 처리하는 구조.
const promise1 = new Promise((resolve) => setTimeout(resolve, 300, 'soso'));
const promise2 = new Promise((resolve) => setTimeout(resolve, 100, 'quick'));
const promise3 = new Promise((resolve) => setTimeout(resolve, 500, 'slow'));
const promises = [promise1, promise2, promise3];
Promise.all(promises ).then((values) => {
console.log(values);
});
// all은 모두 비동기응답이 와야 then 실행
// expected output: Array ["soso", "quick", "slow"]
Promise.any(promises).then((value) => console.log(value));
// any는 하나라도 비동기 응답이 오면 then 실행
// expected output: "quick"
Promise.allSettled
- Promise.allSettled() 메서드는 주어진 모든 프로미스를 이행하거나 거부한 후, 각 프로미스에 대한 결과를 나타내는 객체 배열을 반환한다. (글 참조)
const promiseArr = [
new Promise((resolve, reject) => setTimeout(resolve, 1000, 'abc')),
new Promise((resolve, reject) => setTimeout(reject, 2000)),
new Promise((resolve, reject) => setTimeout(resolve, 3000)),
];
Promise.allSettled(promiseArr).then(data => console.log(data));
[
{
"status": "fulfilled",
"value": "abc"
},
{
"status": "rejected"
},
{
"status": "fulfilled"
}
]
Top-level Await
- await 키워드를 사용하려면 무조건 async 함수가 필요해서 IIFE로 묶어 사용해왔지만,
- 이제 비동기 함수 및 클래스 외부에서 await 연산자를 선언하여 동기화 문제를 해결할 수 있다.
단, 최상위 계층에서 await을 async없이 사용할수 있다는 것이지, 함수 내에서 쓸때 async이 전혀 필요없어졌다라는 말이 아님을 유의하자
import {getUser} from "./data/User"
let user = await getUser();
정규식 최신 문법
s 플래그(dotAll)
- 원래 정규식의 . 표현식은 개행문자를 제외한 모든 문자였으나, s플래그를 달면 개행식도 포함하게 된다.
/hi.welcome/.test('hi\nwelcome') // false
/hi.welcome/s.test('hi\nwelcome') // true
Regexp Match Indices
- d문자를 활용하여 일치하는 문자열의 시작 및 끝 인덱스가 있는 배열을 얻을 수 있다.
const re1 = /a+(?<Z>z)?/d;
// indices are relative to start of the input string:
const s1 = "xaaaz";
const m1 = re1.exec(s1);
m1.indices[0][0] === 1;
m1.indices[0][1] === 5;
s1.slice(...m1.indices[0]) === "aaaz";
named capture group
- 미리 명명된 정규식 캡쳐 드룹 이름 지정
- named capturing group: (?<name>x)
- non-capturing group: (?:x)
const re = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/
const result = re.exec('2015-01-02')
// result.groups.year === '2015';
// result.groups.month === '01';
// result.groups.day === '02';
// 이메일 주소를 정규식을 통해 ID와 도메인을 각각 email_id와 domain이란 이름으로 분리한 obj로 가져오기
const emailAddress = 'bloodguy@gmail.com';
const result = /(?<email_id>\w+)@(?<domain>\w+\.\w+)/.exec(emailAddress).groups
// { email_id: "bloodguy", domain: "gmail.com" }
// 필요한 게 ID만이라면 :?를 통해 그룹핑만 하고 결과값에서는 제외하는 것도 가능
const result2 = /(?<email_id>\w+)@(?:\w+\.\w+)/.exec(emailAddress).groups
// { email_id: "bloodguy" }
RegExp lookbehind assertions
- ?= / ?! / ?<= / ?<!
- 앞에 오는 항목에 따라 문자열 일치
// ?= 특정 하위 문자열이 뒤에 오는 문자열을 일치시키는데 사용
/Roger(?=Waters)/
/Roger(?= Waters)/.test('Roger is my dog') //false
/Roger(?= Waters)/.test('Roger is my dog and Roger Waters is a famous musician') //true
// ?! 문자열 뒤에 특정 하위 문자열이 오지 않는 경우 일치하는 역 연산을 수행
/Roger(?!Waters)/
/Roger(?! Waters)/.test('Roger is my dog') //true
/Roger(?! Waters)/.test('Roger Waters is a famous musician') //false
// ?<= 새로 추가된 표현식
/(?<=Roger) Waters/
/(?<=Roger) Waters/.test('Pink Waters is my dog') //false
/(?<=Roger) Waters/.test('Roger is my dog and Roger Waters is a famous musician') //true
// ?<! 새로 추가된 표현식
/(?<!Roger) Waters/
/(?<!Roger) Waters/.test('Pink Waters is my dog') //true
/(?<!Roger) Waters/.test('Roger is my dog and Roger Waters is a famous musician') //false
이 글이 좋으셨다면 구독 & 좋아요
여러분의 구독과 좋아요는
저자에게 큰 힘이 됩니다.