const, letvar 🆚 const, letconst 🆚 let템플릿 문자열( ` . Tab위의 백틱)객체 리터럴메서드 축약 표현프로퍼티 축약 표현계산된 프로퍼티 이름화살표 함수(lambda)구조분해 할당클래스프로미스Promise.resolve, reject, allasync/await
- 2015년 자바스크립트 문법에 매우 큰 변화가 있었음
- 2020년 ES2020 까지 나왔고 인터넷 익스플로러와 같은 구형 브라우저에서는 최신 문법 사용할 수 없지만, 요즘에는 babel 처럼 구형 브라우저에 맞게 문법 변환해주는 도구가 널리 쓰이므로 큰 문제 안됨
const, let
if (true) { var x= 3; } console.log(x); // 3 if (true) { const y = 3; } console.log(y); // Uncaught ReferenceError
var 🆚 const, let
- const, let은 블록 스코프를 가지므로 블록 밖에서는 변수에 접근할 수 없음
- var는 함수 스코프를 가지므로 if 문의 블록과 관계없이 접근 가능함. var 는 블록 레벨 스코프를 지원하지 않아서 의도치 않게 전역 변수로 선언될 수 있음
const 🆚 let
const a = 0; a = 1; // Uncaught TypeError : Assignment to constant variable let b= 0; b = 1; // 1 const c; // Uncaught SyntaxError: Missing Initializer in const declaration
- const는 한 번 값을 할당하면 다른 값 할당 x, 초기화 할 때 값 할당하지 않으면 에러 발생
- let은 할당하고 나서 변경 가능
템플릿 문자열( ` . Tab위의 백틱)
//ES5 문법 var num1 = 1; var num2 = 2; var result = 3; var string1 = num1 + ' 더하기 ' + num2 + '는 \'' + result + '\''; console.log(string1); // 1 더하기 2는 '3' // ES2015 템플릿 문자열 const string = `${num3} 더하기 ${num4} 는 '${result}'`; // 숫자앞에 0 채워넣기 -> String class의 API 이용 const n = 1; n.toString().padStart(2, "0") // 01
객체 리터럴
- 객체 리터럴
{ firstName: 'Jane', lastName: 'Doe' }
// 이전 버전 var sayNode = function() { console.log('Node'); } var es = 'ES'; var oldObject = { sayJs: function() { console.log('JS'); } sayNode: sayNode, }; oldObject[es + 6] = 'Fantastic'; oldObject.sayNode(); oldObject.sayJs(); console.log(oldObject.ES6); // 새로 쓰는 버전 const newObject = { sayJs() { console.log('JS'); }, sayNode, [es + 6]: 'Fantastic', };
메서드 축약 표현
- 위의 newObject에서 보면 함수 정의 시 function 생략하고 바로 정의 가능
프로퍼티 축약 표현
- 속성명과 변수명이 동일한 경우에는 한 번만 써도 됨 (sayNode : sayNode → sayNode)
계산된 프로퍼티 이름
- 객체의 속성명을 동적으로 정의할 시, 원래는 객체 리터럴 바깥에서 [es + 6]를 해야 했지만 이제는 객체 리터럴 안에서 바로 정의 가능함
화살표 함수(lambda)
function add1(x, y){ return x + y; } const add2 = (x, y) => { return x + y; }; const add3 = (x, y) => x + y; const add4 = (x, y) => (x + y); function not1(x) { return !x; } const not2 = x => !x;
- 함수 내부에 return 밖에 없으면 return 문 줄일 수 있음
- not2 처럼 매개변수가 한 개면 매개변수를 소괄호로 묶지 않아도 됨
var relationship1 = { name: 'zero', friends: ['nero', 'hero', 'xero'], logFriends: function () { var that = this; // relationship1을 가리키는 this를 that에 저장 this.friends.forEach(function (friend) { console.log(that.name, friend); }); }, }; relationship1.logFriends(); const relationship2 = { name: 'zero', friends: ['nero', 'hero', 'xero'], logFriends() { this.friends.forEach(friend => { console.log(this.name, friend); }); }, }; relationship2.logFriends();
- 기존의 function() 으로 정의할 때는 각자 다른 함수 스코프의 this를 가지게 되므로 that이라는 변수를 새로 사용해서 relationship1에 접근함
- 그러나 화살표 함수를 씀으로 바깥 스코프인 logFriends()의 this를 그대로 사용할 수 있음. ↔ 화살표 함수는 자신만의 this가 없음. 위 스코프의 this를 이어 받음
- ES6에서는 함수 내부에서
this
가 전역객체를 바라보는 문제를 보완하고자,this
를 바인딩하지 않는 화살표 함수를 도입. 화살표 함수는 실행 컨텍스트를 생성할 때this
바인딩 과정 자체가 빠지게 되어, 상위 스코프의this
를 그대로 이용할 수 있음
구조분해 할당
//이전 var candyMachine = { status: { name: 'node', count: 5, }, getCandy: function () { this.status.count--; return this.status.count; }, }; var getCandy = candyMachine.getCandy; var count = candyMachine.status.count; // 변경 const candyMachine = { status: { name: 'node', count: 5, }, getCandy() { this.status.count--; return this.status.count; }, }; const { getCandy, status: { count } } = candyMachine;
const array = ['nodeJs', {}, 10, true]; const [node, obj, , bool] = array;
클래스
클래스 문법도 추가되었지만 다른 언어처럼 클래스 기반으로 동작하는 것이 아니라 여전히 프로토타입 기반으로 동작함
var Human = function(type) { this.type = type || 'human'; }; Human.isHuman = function(human) { return human instanceof Human; } Human.prototype.breathe = function() { alert('h-a-a-a-m'); }; var Zero = function(type, firstName, lastName) { Human.apply(this, arguments); this.firstName = firstName; this.lastName = lastName; }; Zero.prototype = Object.create(Human.prototype); Zero.prototype.constructor = Zero; // 상속하는 부분 Zero.prototype.sayName = function() { alert(this.firstName + ' ' + this.lastName); }; var oldZero = new Zero('human', 'Zero', 'Cho'); Human.isHuman(oldZero); // true
class Human { constructor(type = 'human') { this.type = type; } static isHuman(human) { return human instanceof Human; } breathe() { alert('h-a-a-a-m'); } } class Zero extends Human { constructor(type, firstName, lastName) { super(type); this.firstName = firstName; this.lastName = lastName; } sayName() { super.breathe(); alert(`${this.firstName} ${this.lastName}`); } } const newZero = new Zero('human', 'Zero', 'Cho'); Human.isHuman(newZero); // true
- 생성자 함수는 constructor
- Human.isHuman 같은 클래스 함수 static 키워드로 전환
- 프로토타입 함수들도 모두 class 블록 안에 포함되어 어떤 함수가 어떤 클래스 소속인지 보기 쉬움
- 상속도 간단해져서 extends로 쉽게 상속 가능. 다만 이렇게 클래스 문법으로 바뀌었더라도 자바스크립트는 프로토타입 기반으로 동작!
프로미스
- 이벤트 리스너를 사용할 때 콜백함수를 자주 사용하는데, ES2015부터는 자바스크립트와 노드의 API 들이 콜백 대신 프로미스 기반으로 재구성됨. 악명 높은 콜백 지옥(callback hell) 현상을 극복.
const condition = true; // true면 resolve, false면 reject const promise = new Promise((resolve, reject) => { if (condition) { resolve('성공'); } else { reject('실패'); } }); // 다른 코드가 들어갈 수 있음 promise .then((message) => { console.log(message); // 성공(resolve)한 경우 실행 }) .catch((error) => { console.error(error); // 실패(reject)한 경우 실행 }) .finally(() => { // 끝나고 무조건 실행 console.log('무조건'); });
- 먼저 프로미스 객체 생성
- 내부에 resolve와 reject를 매개변수로 갖는 콜백 함수를 넣음
- promise 변수에 then과 catch 메서드를 붙일 수 있음
- resolve가 호출되면 then이 실행
- resolve(’성공’)이 호출되면 then의 message가 ‘성공’이 됨
- reject가 호출되면 catch가 실행
- reject(’실패’)가 호출되면 catch의 error가 ‘실패’가 되는 것임
- finally 부분은 성공/실패 여부와 상관없이 실행됨
- 프로미스를 쉽게 설명하자면, 실행은 바로 하되 결과값은 나중에 받는 객체
- 결과값은 실행이 완료된 후 then이나 catch 메서드를 통해서 받음
- new Promise는 바로 실행, 결과값은 then을 붙였을 때 받음
function findAndSaveUser(Users) { Users.findOne({}, (err, user) => { // 첫 번째 콜백 if (err) { return console.error(err); } user.name = 'zero'; user.save((err) => { // 두 번째 콜백 if (err) { return console.error(err); } Users.findOne({ gender: 'm' }, (err, user) => { // 세 번째 콜백 // 생략 }); }); }); }
function findAndSaveUser(Users) { Users.findOne({}) .then((user) => { user.name = 'zero'; return user.save(); }) .then((user) => { return Users.findOne({ gender: 'm' }); }) .then((user) => { // 생략 }) .catch(err => { console.error(err); }); }
Promise.resolve, reject, all
const promise1 = Promise.resolve('성공1'); const promise2 = Promise.resolve('성공2'); Promise.all([promise1, promise2]) .then((result) => { console.log(result); // ['성공1', '성공2']; }) .catch((error) => { console.error(error); });
- 이거 python에서 coroutine들 적용했던 것과 비슷하네
async/await
- 노드 7.6 버전부터 지원되고 ES2017에서 추가됨
- 위의 findAndSaveUser를 then 방식으로 작성한 코드를 await/async 를 이용하면 더 간단하게 만들 수 있음
async function findAndSaveUser(Users) { try { let user = await Users.findOne({}); user.name = 'zero'; user = await user.save(); user = await Users.findOne({ gender : 'm' }); // 생략 } catch (error) { console.error(error); } }
const findAndSaveUser = async (Users) => { try { let user = await Users.findOne({}); user.name = 'zero'; user = await user.save(); user = await Users.findOne({ gender: 'm' }); // 생략 } catch (error) { console.error(error); } };
- await를 붙이면 해당 프로미스가 resolve 될 때까지 기다린 뒤 다음 로직으로 넘어감(비동기 함수 내부적으로)
- 다른 로직의 진행을 차단하지는 않음. 비동기이기 때문에