본문 바로가기

JavaScript/NodeJS

"Node.js 교과서" 정리 노트 - 2장, 알아두어야 할 자바스크립트

JavaScript는 매년 새로운 버전으로 업데이트되고 있습니다.

이 장에선, JavaScript의 새로운 문법을 간단히 알아보고, 프런트엔드에서 사용하는 자바스크립트 코드를 알아봅니다.

 

 

1. ES2015(ES6)

2015년 JavaScript 문법에 큰 변화가 있었습니다.

 

JavaScript는 현재 ES2020까지 발표되었고, 일부 구형 브라우저에선 최신 JavaScript 문법을 사용할 수 없습니다.

이로 인해, 호환성에 문제가 있었지만 babel과 같은 구형 브라우저에 맞게 문법을 변환해주는 도구가 있어서 큰 문제는 되지 않습니다.

 

ES6 부터는, 다른 언어들의 장점을 본딴 편리한 기능이 많이 추가되었기 때문에, 이제는 ES6 문법을 배워야 할 때입니다.

 

2. const, let

이전에는 변수를 선언할 때 var 키워드를 사용했지만, 이제는 const와 let으로 대체합니다.

아래 예제들로 const와 let이 가지는 특징인 블록 스코프에 대해 알아봅니다.

if ( true ) {
  var x = 3;
}

console.log(x);

if ( true ) {
  const y = 3;
}

console.log(y);

 

결과는 아래와 같습니다.

 

 

var을 사용한 x는 정상적으로 출력되지만, const를 사용한 y는 에러가 발생합니다.

 

1) var함수 스코프를 가지므로, if 문의 블록과 상관없이 접근할 수 있습니다.

2) const블록 스코프를 가지므로, if 문의 블록 밖에서는 접근할 수 없습니다.

 

블록이란, 중괄호 "{  }"의 범위를 뜻합니다.

함수 스코프 대신 블록 스코프를 사용함으로써, 호이스팅 문제도 해결되고 코드 관리도 수월해졌습니다.

 

여기서 호이스팅이란, JavaScript코드를 실행하기 전, 선언된 변수나 함수들을 맨 위로 올리는 JavaScript의 기본적인 동작을 뜻합니다.

https://www.w3schools.com/js/js_hoisting.asp

 

const와 let의 차이점변수의 값을 변경할 수 있는지의 차이입니다.

예를 들어, const는 변수를 선언하고 초기값을 할당하지 않거나 변경하려 하면 에러가 발생합니다.

 

실제로 JavaScript 코드를 작성할 땐 변수의 값을 변경하는 경우가 적으므로, 기본적으로 const를 사용하고, 다른 값을 할당해야 할 때 let을 사용하는 것이 좋습니다.

 

3. 템플릿 문자열

ES6+에선 스트링을 선언할 때 사용하던 큰 따옴표(")와 작은 따옴표(') 외에 백틱(`)이 새로 추가되었습니다.

백틱을 사용하면 문자열 안에 변수를 넣을 수 있습니다.

const num1 = 1;
const num2 = 2;
const result = 3;
const string = `${num1} 더하기 ${num2}는 '${result}'`;

console.log( string );

 

'${result}'처럼 작은 따옴표를 string으로 넣을 수도 있습니다.

 

4. 객체 리터럴

리터럴(Literal)이란, JavaScript 데이터의 그 자체값입니다.

리터럴의 종류는 아래와 같습니다.

 

1) { } (Object)

2) [ ] (Array)

3) function( ){ } (function)

4) '123' (String)

5) 123 (Number)

6) true (Boolean)

7) undefined (undefined)

 

ES6+에선 객체 리터럴( { } )에 편리한 기능이 추가되었습니다.

아래 예제는 ES5 JavaScript 코드입니다.

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

 

위 코드를 ES6+ 문법으로 변경하면, 아래와 같이 작성할 수 있습니다.

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

newObject.sayNode();
newObject.sayJS();
console.log(oldObject.ES6);

 

위와 같이, ES6+에선 객체 리터럴을 만들 때 콜론(:)과 function을 붙이지 않아도 되고, [es + 6]처럼 동적인 속성명을 사용할 수도 있습니다.

 

또한, 속성명과 변수명이 같을 경우, 한 번만 써도 되게 바뀌었습니다.

{ name: name, age: age } // ES5
{ name, age } // ES6+

 

5. 화살표 함수

ES6+에선 화살표 함수가 새로 추가되었습니다.

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~4, not1~2는 모두 같은 기능을 하는 함수입니다. 이런 식으로 함수를 간결하게 선언해 사용할 수 있습니다.

 

기존 함수와 다른 점은 this 바인드 방식입니다.

var relationship1 = {
  name: 'jaynull',
  friends: ['a', 'b', 'c'],
  logFriends: function() {
    var that = this; // relationship1을 가리키는 this를 that에 저장
    this.friends.forEach(function (friend) {
      console.log(that.name, friend);
    });
  },
};
relationship1.logFriends();

const relationship2 = {
  name: 'jaynull',
  friends: ['a', 'b', 'c'],
  logFriends() {
    this.friends.forEach(friend => {
      console.log(this.name, friend);
    });
  },
};
relationship2.logFriends();

 

relationship1에선 this를 that이라는 변수를 통해 간접적으로 접근했지만,

relationship2에선 forEach 함수에 화살표 함수를 사용해 logFriends()의 this를 그대로 사용할 수 있습니다. 상위 스코프의 this를 물려받는 것입니다.

 

6. 구조분해 할당

구조 분해 할당 구문은 배열이나 객체의 속성을 해체하여 그 값을 개별 변수에 담을 수 있게 하는 JavaScript 표현식입니다.

 

아래는 기존 ES5 문법의 예제입니다.

// ES5
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;

 

위 코드를 ES6+ 문법을 적용하면 아래와 같습니다.

// ES6+
const candyMachine = {
  status: {
    name: 'node',
    count: 5,
  },
  getCandy() {
    this.status.count--;
    return this.status.count;
  }
};
const { getCandy, status: { count } } = candyMachine;

 

위처럼, candyMachine 객체 안의 속성을 찾아서 getCandy, count 변수를 매칭해 초기화할 수 있습니다.

배열에도 구조분해 할당을 적용할 수 있습니다.

// ES5
var arr = ['nodejs', {}, 10, true];
var node = arr[0];
var obj = arr[1];
var bool = arr[3];

// ES6
const ARR = ['nodejs', {}, 10, true];
const [NODE, OBJ, , BOOL] = ARR;

 

구조분해 할당 문법은 코드 줄 수를 줄여주므로 유용하게 사용할 수 있습니다.

 

7. 클래스

JavaScript는 여전히 프로토타입 기반으로 동작하지만, 다른 객체지향 언어들처럼 클래스 개념이 새로 추가되었습니다.

 

다음은 기존의 프로토타입 상속 예제입니다.

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 Jay = function(type, firstName, lastName) {
  Human.apply(this, arguments);
  this.firstName = firstName;
  this.lastName = lastName;
};

Jay.prototype = Object.create(Human.prototype);
Jay.prototype.constructor = Jay; // 상속받는 부분
Jay.prototype.sayName = function() {
  alert(this.firstName + ' ' + this.lastName);
};
var oldJay = new Jay('human', 'Jay', 'Null');
Human.isHuman(oldJay);

 

다음은 위 예제를 클래스로 변경한 코드입니다.

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

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

const newJay = new Jay('Human', 'Jay', 'Null');
Human.isHuman(newJay);

 

위처럼 클래스를 사용해 프로토타입 방식보다 상속을 좀 더 쉽고, 좀 더 가독성 있게 코드를 작성할 수 있습니다.

 

8. 프로미스

JavaScript와 노드에서는 주로 비동기를 사용하고, 이벤트 리스너를 사용할 때 콜백 함수를 자주 사용합니다.

ES6+에서는 콜백 대신 프로미스를 기반으로 재구성되며, 콜백 지옥 현상을 극복할 수 있는 객체입니다.

 

아래는 프로미스 객체를 생성하는 예제입니다.

const condition = true;
const promise = new Promise((resolve, reject) => {
  if ( condition ) {
    resolve('성공');
  } else {
    reject('실패');
  }
});

promise
  .then((message) => {
    console.log(message);
  })
  .catch((error) => {
    console.error(error);
  })
  .finally(() => {
    console.log('무조건');
  });

 

프로미스는 resolve와 reject를 매개변수로 하는 new Promise로 생성할 수 있으며, 프로미스 내부에서 resolve가 호출되면 .then()이 실행되고, reject가 호출되면 .catch()가 실행되며, 마지막으로 .finally()가 무조건 실행됩니다.

 

.then(), .catch() 다음에 또 다른 .then(), .catch()가 올 수도 있으며, 이전 .then()의 return 값이 다음 .then()의 매개변수로 전달됩니다.

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

 

프로미스 여러 개를 한 번에 실행하는 방법이 있습니다. 여러 중첩 콜백을 간결하게 바꿀 수 있습니다.

const promise1 = Promise.resolve('성공1');
const promise2 = Promise.resolve('성공2');
Promise.all([promise1, promise2])
  .then((result) => {
    console.log(result);
  })
  .catch((error) => {
    console.error(rror);
  });

 

Promise.all에 프로미스들을 넣으면 모두 resolve될 때까지 기다렸다가 .then()으로 넘어갑니다.

result 매개변수엔 각 프로미스의 결괏값이 배열로 들어 있습니다.

이 중 하나라도 reject되면 .catch()로 넘어갑니다.

 

9. async/await

노드 7.6+ 버전에서 지원되고, ES2017에서 추가된 편리한 기능입니다.

프로미스가 콜백 지옥을 해결했지만, async/await 문법은 코드를 더 깔끔하게 만들게 해줍니다.

 

아래는 프로미스의 .then(), .catch() 함수 대신, async/await을 사용한 예제입니다.

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

 

async를 사용한 함수는 await의 결과가 resolve 될 때까지 기다린 다음, 다음 작업을 수행합니다.

위 코드는 에러 처리를 할 수 없기 때문에, try/catch 문으로 감싸서 해결할 수 있습니다.

async function findAndSaveUser(Users) {
  try {
    let user = await Users.findOne({});
    user.name = 'jaynull';
    user = await user.save();
    user = await Users.findOne({ gender: 'm' });
  } catch(error) {
    console.error(error);
  }
}

 

화살표 함수도 async와 함께 사용할 수 있습니다.

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

 

ES2018+에선 for 문과 함께 사용할 수도 있습니다.

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

 

async 함수의 반환값을 항상 Promise로 감싸지기 때문에, .then(), .catch()를 붙이거나, 또 다른 async 함수 안에서 await을 붙여서 처리할 수 있습니다.

 

 

 

 

 

'JavaScript > NodeJS' 카테고리의 다른 글

Mac) node.js 버전 변경  (1) 2023.07.09
Express CORS 설정  (1) 2023.01.18
Rocky Linux에 node.js 설치하기  (0) 2021.11.16
"Node.js 교과서" 정리 노트 - 1장, 노드 시작하기  (0) 2021.10.10