본문 바로가기

노드 공부

2017.07.23 / 2.1장

2.1 ES2015+

2015년 자바스크립트 문법에 매우 큰 변화가 있었다. 바로 ES2015가 등장한 것이다. 2015년을 기점으로 매년 문법 변경 사항이 발표되고 있으며, 새로운 문법 상세에 대해서도 활발한 논의가 이루고 지고 있다.

 

2.1.1 const, let

보통 자바스크립트를 배울 때는 var로 변수를 선언한다. 하지만 var은 이제 const와 let이 대체한다.

 

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

if(true){
	const y=3;
}
console.log(y); //에러 메세지

x는 정상적으로 출력이 되지만 y는 에러가 발생한다. const와 let은 블록 스코프를 가지므로 블록 밖에서는 변수에 접근할 수 없다. 블록의 범위는 중괄고 {와 }의 사이이다. 

const는 한 번 값을 할당하면 다른 값을 할당할 수 없다. 다른 값을 할당하려고 하면 에러가 발생한다. 또한 초기화할 때 값을 할당하지 않으면 에러가 발생한다. 상수라고 부르기도 한다.

 

const a=0;
a=1; //에러

let b=0;
b=1; //1

const c; //에러

 

const와 let 중 쓰면 좋은 것은?

자바스크립트를 사용할 때 한 번 초기화했던 변수에 다른 값을 할당하는 경우는 의외로 적다. 따라서 기본적으로 const를 사용하고, 다른 값을 할당해야 하는 상황이 생겼을 때 let을 사용한다.

 

2.1.2 템플릿 문자열

ES2015 문법에 새로운 문자열이 생겼다. 기존 문자열과 다르게 백틱`` 으로 감싼다. 문자열 안에 변수를 넣을 수 있다.

 

const num3=1;
const num4=2;
const result2=3;
const string2=`${num3} 더하기 ${num4}는 '${result2}'`;
console.log(string2); //1 더하기 2는 '3'

${변수} 형식으로 변수를 더하기 기호 없이 문자열에 넣을 수 있다. 큰따옴표나 작은따옴표와 함께 사용할 수도 있다.

 

2.1.3 객체 리터럴

 

var sayNode=function(){
	console.log('node');
};
var es='ES'
var oldObject={
	sayJS:function(){
    	console.log('JS');
    },
    sayNode:sayNode,
};
oldObject[es+6]='Fantastic';
oldObject.sayNode(); //Node
oldObject.sayJS(); //JS
console.log(oldObject.ES6); //Fantastic

위 코드는 oldObject 객체에 동적으로 속성을 추가하고 있다. 이 코드를 다시 쓸 수 있다.

 

const newObject={
	sayJS(){
    	console.log('JS');
    },
    sayNode,
    [es+6]:'Fantastic',  
};
newObject.sayNode(); //Node
newObject.sayJS(); //JS
console.log(newObject:ES6); //Fantastic

 

sayJS와 같은 객체의 메서드에 함수를 연결할 때 더는 콜론과 function을 붙이지 않아도 된다.

sayNode: sayNode처럼 속성명과 변수명이 동일한 경우에는 한 번만 쓰도록 바뀌었다.

객체 속성명은 동적으로 생성할 수 있다. 

 

2.1.4 화살표 함수

화살표 함수(arrow function)라는 새로운 함수가 추가되었으며, 기존의 function(){}도 그대로 사용할 수 있다.

 

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;

 

add1, add2, add3, add4는 같은 기능을 하는 함수다. 마찬가지로 not1, not도 같은 기능을 한다. 

화살표 함수에서는 function 선언 대신=> 기호로 함수를 선언한다. 또한 변수에 대입하면 나중에 재사용할 수 있다.

화살표 함수에서 내부에 return문밖에 없는 경우에는 return문을 줄일 수 있다. 중괄호 대신 add3과 add4처럼 return할 식을 바로 적으면 된다. not2처럼 매개변수가 한 개면 소괄호로 묶지 않아도 된다. 

 

기존 function과 다른 점은 this 바인드 방식이다.

 

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.logFrineds();

const relationship2={
	name:'zero',
    friends:['nero','hero','xero'],
    logFriends(){
    	this.friends.forEach(friend =>{
        	console.log(this.name, friend);
        });
    },
};
relationship2.logFriends();

relationship1.logFriends() 안의 forEach문에서는 function 선언문을 사용했다. 각자 다른 함수 스코프의 this를 가지므로 that이라는 변수를 사용해서 relationship1에 간접적으로 접근하고 있다.

하지만 relationship2.logFriends() 안의 forEach문에서는 화살표 함수를 사용했다. 따라서 바깥 스코프인 logFriends()의 this를 그대로 사용할 수 있다. 상위 스코프인 this를 그대로 물려받는 것이다.

기본적으로 화살표 함수를 쓰되, this를 사용해야 하는 경우에는 화살표 함수와 함수 선언문 중에서 하나를 고르면 된다. 

 

 

화살표 함수 더하기

더보기

화살표 함수의 기본 문법

var/let/const 함수명 = (매개변수) => {실행문}

 

기본적으로 화살표 함수는 익명 함수로만 사용할 수 있기 때문에, 함수를 호출하기 위해서는 표현식으로 써야한다.

익명함수 : 함수를 재사용하지 않을 목적으로 함수에 이름을 붙이지 않는 것

 

표현식을 이용한 화살표 함수

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

 

표현식을 이용하지 않은 화살표 함수

function(a, b) =>{
	return a+b;
}; //에러

 

화살표 함수는 일반 함수보다 간결하게 콜백함수로 사용될 수 있다.

var numbers = [1, 2, 3, 4, 5]; 
var newArray = numbers.map(a => a + a);

console.log(newArray); //[2, 4, 6, 8, 10]

.map() 메서드 : 배열 내 모든 요소에 주어진 함수를 호출한 결과를 모아 새로운 배열을 반환하는 메서드

 

화살표 함수를 사용할 수 없는 경우

1. 객체의 메소드를 정의하는 경우

2. prototype에 메소드를 할당하는 경우

3. 생성자 함수로 사용하는 경우

 

 

화살표 함수와 this

화살표 함수의 this는 화살표 함수가 호출되는 시점과는 무관하게 선언되는 시점에 결정되며 언제나 상위 스코프의 this를 가리킨다. 화살표 함수에는 this와 argument가 없기 때문이다.

일반적으로 this가 개입되는 경우라면 일반 함수를 사용한다.

var obj = { 
    myName: 'Barbie', 
    logName: function() { 
        console.log(this.myName); 
    }
};

obj.logName();   //"Barbie"
//콜백함수로 일반함수표현방식을 사용하면 dot notation법칙에 따라 this는 obj객체가 된다.

 

위 예제를 화살표 함수로 표현할 경우, this는 달라진다.

var obj = { 
    myName: 'Barbie', 
    logName: () => { 
        console.log(this.myName); 
    }
};

obj.logName();   //undefined 
/*콜백함수로 화살표함수를 사용하면 이 예제의 경우 this는 상위 스코프인 전역스코프 혹은 
window객체가 된다. 
현재 상위 스코프에는 변수 myName이 존재하지 않으므로 undefined를 반환한다.*/

 

따라서 window 객체에 myName의 값을 할당해주면 아래와 같은 결과를 얻게 된다.

window.myName = "Hannah"; 
var obj = { 
    myName: 'Barbie', 
    logName: () => { 
        console.log(this.myName); 
    }
};

obj.logName();   //"Hannah"
//여기서 this는 obj객체의 상위스코프의 this인 window객체가 된다.

 

var status = "😎";

setTimeout(() => { 
  const status = "😍"; 
  
  const data = { 
    status: "🥑", 
    getStatus: function() { 
      return this.status;
    }
  }; 
  
console.log(data.getStatus.call(this));        //😎

 위 예제의 맨 아랫줄 this를 가지고 있는 함수는 setTimeout() 화살표 함수이다.

하지만 화살표 함수는 this를 갖고 있지 않으므로, 예제의 this값은 상위 스코프인 global scope의 this가 된다. 따라서 this.status는 😎를 반환하게 된다.

 

2.1.5 구조분해 할당

구조분해 할당을 사용하면 객체와 배열로부터 속성이나 요소를 쉽게 꺼낼 수 있다.

 

객체의 속성을 같은 이름의 변수에 대입하는 코드

var candyMachine={
	status:{
    	name:'node',
        const: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;

 

candyMachine 객체 안의 속성을 찾아서 변수와 매칭한다. count처럼 여러 단계 안의 속성도 찾을 수 있다. getCandy와 count 변수가 초기화된 것이다. 다만 구조분해 할당을 사용하면 함수의 this가 달라질 수도 있다. 달라진 this를 원래대로 바꿔주려면 bind 함수를 따로 사용해야 한다.

 

 

배열에 대한 구조분해 할당 문법

var array = ['node.js',{},10,true];
var node = array[0];
var obj = array[1];
var bool = array[3];

 

array란 배열의 첫번째, 두번째 요소와 네번째 요소를 변수에 대입하는 코드이다.

위 코드를 바꿀 수 있다.

const array = ['nodejs',{},10,true];
const [node, obj, bool] = array;

 

 

구조분해 할당 더하기

더보기

구조 분해 할당

배열이나 객체의 속성을 해체하여 그 값을 개별 변수에 담을 수 있게 하는 자바스크립트 표현식

 

변수에 기본값을 할당하면, 분해한 값이 undefinded일 때 그 값을 대신 사용한다.

 

const [c, ,d] = [1,2,3];
console.log(c); //1
console.log(d); //3

c는 배열의 첫번째 요소이고, d는 세번째 요소이다. 가운데 콤마는 두번째 요소를 가리키지만 변수에는 대입하지 않는다.

 

  일반 객체에서도 해제가 가능하다.

var obj={
	e:'Eeee',
    f:{
    	g:'Gee'
    }
};
var e=obj.e; //'Eeee'
var g=obj.f.g; //'Gee'

 

const obj2={
	h:'Eich',
    i:{
    	j:'Jay'
    }
}
const {h,i:{j},k}=obj2;
console.log(h, j, k) //'Eich','Jay',undefinded

 k처럼 없는 변수를 선언했을 경우, 자동으로 undefinded가 들어가기 때문에 에러가 발생하지 않는다.

 

객체 값을 받는 매개변수도 해체가 가능하다.

const destruct = ({ value: x }) => {
 console.log(x);
};
const arg = { value: 3 };
destruct(arg); // 3

 

 

2.1.6 클래스

프로토타입 기반 문법을 보기 좋게 클래스로 바꾼 것이다.

 

프로토타입 상속 예제 코드

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.protptype.constructor = Zero; //상속하는 부분
Zero.prototype.sayName = function() {
	alert(this.firstName + ' ' +this.lastName);
};
var oldZero = new Zero('human','Zero','Cho');
Human.isHuman(oldZero); //true

 

Human 생성자 함수가 있고, 그 함수를 Zero 생성자 함수가 상속한다. Human.apply와 Object.create 부분이 상속받는 부분이다.

 

class Human {
	constructor(type = 'human'){
    	this.type=type;
    }
    static isHuman(human){
    	return human istanceof Human;
    }
    breathe(){
    	alert('h-a-a-a-m');
    }
}

class Zero extends Human{
	constructor(type, firstName, lastName){
    	super(type);
        this.firstName=firstName;
        this.lastName=lastNamel
    }
    sayName(){
    	super.breathe();
        alert(`${this.firstName} ${this.lastName}`);
    }
}

const newZero = new Zero('human','Zero',' 'Cho');
Human.isHuman(newZero); //true

전반적으로 class 안으로 그룹화 되었다. 생성자 함수는 constructor 안으로 들어갔고, Human.isHuman 같은 클래스 함수는 static 키워드로 전환되었다. 상속도 간단해져서 extends 키워드로 쉽게 상속 가능하다. 

 

 

2.1.7 프로미스

자바스크립트와 노드에서는 주로 비동기를 접한다. 특히 이벤트 리스너를 사용할 때 콜백 함수를 자주 사용한다. 

 

const condition = true; //true resolve, false reject
const promise = new Promise((resolve, reject) =>{
	if(condition){
    	resolve('성공');
    }
    else {
    	reject('실패');
    }
});
//다른 코드가 들어갈 수 있음
promise
	.then((message) =>{
    	console.log(message); //성공한 경우 실행
        
    })
    .finally(() => { //끝나고 무조건 실행
    	console.log('무조건');
    });

 

new Promise로 프로미스를 생성할 수 있으며, 그 내부에 resolve와 reject를 매개변수로 갖는 콜백 함수를 넣는다. promise 변수에 then과 catch 메서드를 붙일 수 있다. finally 부분은 성공/ 실패 여부와 상관없이 실행된다.

resolve와 reject에 넣어준 인수는 각각 then과 catch의 매개변수에서 받을 수 있다. 즉 resolve가 호출되면 then의 message가 '성공'이 된다. 만약 reject가 호출되면 catch의 error가 '실패'가 되는 것이다. condition 변수를 false로 바꿔보면 catch에서 에러가 로깅된다.

프로미스는 실행은 바로 하되 결과값은 나중에 받는 객체이다. 결과값은 실행이 완료된 후 then이나 catch 메서드를 통해 받는다. 

then이나 catch에서 다시 다른 then이나 catch를 붙일 수 있다. 이전 then의 return 값을 다음 then의 매개변수로 넘긴다. 프로미스를 return한 경우에는 프로미스가 수행된 후 다음 then이나 catch가 호출된다.

 

promise
	.then((message) => {
    	return new Promise((resolve, reject) => {
        	resolve(message);
        });
    })
    .then((message2) => {
    	console.log(message2);
        return new Promise((resolve, reject) => {
        	resolve(message2);
        });
    })
    .then((message3) => {
    	console.log(message3);
    })
    .catch((error) => {
    	console.error(error);
    });

처음 then에서 message를 resolve하면 다음 then에서 message2로 받을 수 있다. 여기서 message2를 resolve한 것을 다음 then에서 message3로 받았다. 단 then에서 new Promise를 return해야 다음 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);
        });
}

 

 then 메서드들은 순차적으로 실행한다. 콜백에서 처리했던 에러도 마지막 catch에서 한번에 처리할 수 있다. 

 

프로미스 여러 개를 한 번에 실행하는 방법

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);
    });

Promise.resolve는 즉기 resolve하는 프로미스를 만드는 방법이다. 비슷하게 즉시 reject하는 Promise.reject도 있다. 프로미스가 여러개 있을 때 Promise.all에 넣으면 모두 resolve가 될 때까지 기다렸다가 then으로 넘어간다. result 매개변수에 각가의 프로미스 결과값이 배열로 들어있다. Promise 중 하나라도 reject가 되면 catch로 넘어간다.

 

2.1.8 async/await

프로미스를 사용한 코드를 한번 더 깔끔하게 줄인다.

 

function findAndSaveUser(Users) {
	users.findond({})
    .then((user) => {
    	user.name='zero';
        return user.save();
    })
    .then((user) => {
    	return Users.findOne({ gender : 'm' });
    })
    .then((user) => {
    	//생략
    })
    .catch(err => {
    	console.error(err);
    });
}

 

async/await 문법을 사용하여 코드를 줄인다.

async function findAndSaveUser(Users) {
	let user = await Users.findOne({});
    user.name = 'zero';
    user = await user.save();
    user = await Users.findOne({ gender : 'm' });
    //생략
}

 

함수 선언부를 일반 함수 대신 async function으로 바꾼 후 , 프로미스 앞에 await을 붙였다. 함수는 해당 프로미스가 resolve될 때까지 기다린 뒤 다음 로직으로 넘어간다. 위 코드는 에러를 처리하는 부분이 없으므로 추가 작업이 필요하다.

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);
    }
}

 

try/catch문으로 로직을 감쌌다. 프로미스의 catch 메서드처럼 try/catch문의 catch가 에러를 처리한다.

 

 

화살표 함수도 async와 같이 사용할 수 있다.

const findAndSaveUser = async (Users) => {
	try {
    	let user = await Users.findOne({});
        user.name='zero';
        user=awit user.save();
        user=await Users.findOne({ gender : 'm'});
        //생략
    }catch(error){
    	console.error(error);
    }
};

for문과 async/await을 같이 써서 프로미스를 순차적으로 실행할 수 있다. 

 

const promise1 = Promise.resolve('성공1');
const promise2 = Promise.resolve('성공2');
(async () => {
	for await (promise of [promise1, promise2]){
    	console.log(promise);
    }
})();

 

for awit of문을 사용해서 프로미스 배열을 순회한다. async 함수의 반환값은 항상 Promise로 감싸진다. 따라서 실행 후 then을 붙이거나 또 다른 async 함수 안에서 await을 붙여서 처리할 수 있다.

 

async function findAndSaveUser(Users) {
	//생략
}
findAndSaveUser(). then(() => { //생략 });
//또는
async function other() {
	const result = await findAndSaveUser();
}

'노드 공부' 카테고리의 다른 글

2021.07.24 / 2.2장  (0) 2021.07.24
2021.07.21 / 1장  (0) 2021.07.21