Group Study (2022-2023)/Node.js

[Node.js] 1주차 스터디 : JS 문자열 리터럴, 호이스팅, JS와 Node에서의 모듈

🌊유파도🌊 2022. 10. 2. 03:21

먼저 1주차 강의(구름에듀 섹션 02)를 듣던 중…. 강사님이 var를 쓰시는 것을 보고야 말았다. 그래서 모듈만 적으려고 했는데 호이스팅에 대해서도 적고 싶어졌다.

자바스크립트에서는 변수를 3가지 형태로 선언할 수 있는데, 이는 var, let, const이다. 여기서 const는 상수로 선언하는것이고 varlet은 변수로 선언한다.

그렇다면 왜 기존에 var가 있는데도 ES6부터 let을 추가했을까?

바로 이유는 var에 호이스팅이라는 아주 치명적인 단점이 있기때문이다.

물론 var, let, const는 이외에도 스코프 차이등 여러 차이점이 존재하지만 가장 큰 주안점인 호이스팅에 대해 주로 말하려고 한다.

 

호이스팅(hoisting)이란?

인터프리터가 변수와 함수의 메모리 공간을 선언전에 미리 할당하는 것이다. 간단하게 말하면 값을 어디에 선언하던 상관없이 선언을 가장 위로 올리는 것이다.

 

즉 변수는 선언과 초기화로 분리되는데 이때 선언만 코드의 최상단으로 옮겨 변수를 정의하는 코드보다 사용하는 코드가 앞서 등장할 수 있게 한다.

호이스팅을 하는 대상은 함수선언과 var로 선언한 변수이다.

정말….. 설명만 들어도 이상하다.

코드로 봐볼까?

console.log(a); //undefined
var a=10;
console.log(a); //10

위의 코드는 아래의 코드와 동일하다.

var a; //선언부를 위로 끌어올림
console.log(a); //undefined : 이 시점에서는 초기화되지 않은 변수이기 때문
a=10; //하지만 할당 위치는 그대로
console.log(a); //10 : 이 시점에서는 초기화되어 값이 있음

따라서 var는 최대한 지양하는 것이 좋다.

 

자바스크립트의 문자열 리터럴

자바스크립트의 문자열 리터럴은 작은따옴표(‘)로 감싸지거나 큰 따옴표(”)로 감싸진다.

그러나 만들고 싶은 문자열이 전부 리터럴이면 좋겠지만 현실은 그렇지 않은 경우가 더 많다.

let date=new Date(); //현재 날짜와 시간을 가지는 객체

console.log('현재는 date 입니다'); //현재는 date 입니다.
console.log('현재는 '+ date.toDateString()+' 입니다'); //현재는 Sun Oct 02 2022 입니다

위와 같이 중간에 expression을 넣어도 하나의 문자열로 만들수 있다. 그러나 문장 사이사이에 변수나 상수를 많이 넣으면 더더욱 알아보기 힘들어 진다.

아래는 문자열 리터럴을 쉽게 만드는 방식들이다.

 

형식 지정자 사용

자바스크립트 뿐만 아니라 여러 언어들이 해당 방식을 사용한다.

let date=new Date(); //현재 날짜와 시간을 가지는 객체

console.log('현재는 %s 입니다',date.toDateString()); //현재는 Sun Oct 02 2022 입니다
타입 형식지정자
문자열 %s
숫자 %d(c언어에서는 10진 정수), %f(c언어에서는 실수) …

하지만 이 방식은 일단 타입에 맞는 형식지정자를 미리 알아야하고, c언어에 기본을 두는 형식지정자를 자바스크립트에 맞추어 다시 분류해야하는 불편함이 있다.

그래서 ES6 부터 새로운 방식이 추가되었다.

 

템플릿 리터럴 사용

ES6 부터 추가된 새로운 방식은 템플릿 리터럴(templete literals)이라고 부른다. 따옴표대신 (backtick)으로 감싸 문자열을 만드는데, 해당 문자열 중간에${expression}` 형태로 값을 넣을 수 있다.

let date=new Date(); //현재 날짜와 시간을 가지는 객체

console.log(`현재는 ${date.toDateString()} 입니다`); //현재는 Sun Oct 02 2022 입니다

이 방식이 사실 제일 편해서 널리 쓰인다.

마지막으로 모듈에 대해서만 작성하고 글을 마치려고 한다. 사실 1주차에서 모듈이 가장 핵심이라 이것만 정리하면 되었는데…… 호이스팅이랑 템플릿 리터럴을 정리하고 싶어서 그냥 다 적었다^^

 

 

모듈(module)

모듈은 별도의 파일로 분리된 독립적인 기능이다. 기능을 독립적인 파일로 분류하면 해당 파일을 읽어 일종의 라이브러리처럼 사용이 가능하다.

노드에서 모듈을 만드는 방식은 두가지가 있다. 원래 2015년 이전까지만해도 자바스크립트 자체에서 모듈을 만드는 기능이 없었다. 그래서 노드의 자체적으로 모듈을 만들수 있는 기능을 만들었다. 이를 CommonJS 라고 한다.

하지만 2015년에 ES6에서 자바스크립트 자체에 모듈을 만드는 기능을 새로 도입했다. 그래서 브라우저상에서는 ES6 문법으로, 노드에서는 CommonJS로 기본적으로 모듈을 만드는 방식이 다르다.

 

CommonJS

require을 통해 모듈을 가져오고, exports를 통해 모듈로 만드는 방식이다.

아래의 코드를 통해 살펴보자.

module.exports 객체에 내보내고 싶은 것들은 넣어주는데, 객체자체에 할당을 하는 방식이 있고, 해당 객체의 속성이나 메소드로 할당하는 방식이 있다.

 

module.exports 객체에 객체를 할당

//m.js (모듈로 만들 파일)
let instant={
    name : 'GDSC',
};

module.exports=instant; //module.exports 객체에 instant 객체를 할당

//app.js (모듈을 사용할 파일)
const M=require('./m.js') //모듈의 이름은 임의로 정할수있음
console.log(M.name); //GDSC

 

module.exports 객체의 속성 or 메소드로 할당

//m.js (모듈로 만들 파일)
function A(a,b){
    return a+b
}

let instant={
    name : 'GDSC',
};

module.exports.B=A; //모듈로 내보낼 함수의 이름은 임의로 정할수있음(그러나 통일하는게 보기 편함)
module.exports.I=instant;

//app.js
const M=require('./m.js') //여기서 M의 의미는 './m.js' 파일에서 내보내고자하는 module.exports를 의미합니다.

console.log(M.B(1,2)); //3
console.log(M.I.name); //GDSC

이때 module.exports 에서 module을 생략하더라도 암묵적으로 module.exports를 가리킨다. 그러나 생략하여 쓸때 이 암묵적이 약속이 깨지지는 않았는지 즉 중간에 새롭게 할당을 받았는지 확인해야 한다.

//m.js (모듈로 만들 파일)
function A(a,b){
    return a+b
}

let instant={
    name : 'GDSC',
};

exports.B=A; //처음 exports는 module.exports를 가리키지만,
exports.I=instant;

console.log(exports);//{ B: [Function: A], I: { name: 'GDSC' } }

exports={} //다른 객체를 할당하면, module.exports와의 연결이 끊겨 그냥 exprots라는 변수가 된다.
console.log(module.exports===exports); //false
exports.I=instant;
console.log(exports); //{ I: { name: 'GDSC' } }

//app.js
const M=require('./m.js') //여기서 M의 의미는 './m.js' 파일에서 내보내고자하는 module.exports를 의미합니다.

console.log(M.B(1,2)); //3
console.log(M.I.name); //GDSC

 

 

ES6에 도입된 모듈을 만드는 기능

내보내고자하는 함수나 변수앞에 export, 가져오고자하는 파일(모듈)을 import 한다. CommonJS에 비해 좀 더 직관적이다.

//m.js (모듈로 만들 파일)
export let instant={
    name : 'GDSC',
};

export function A(a,b){
    return a+b
}

//app.js (모듈을 사용할 파일)
import {instant, A} from './m.js';

console.log(instant.name); //GDSC
console.log(A(4,5)); //9

 

Node에서 import-export를 사용하고 싶을 때

위와 같이 편리한 ES6 문법을 노드에서도 사용하고 싶으면!

package.json 에 아래의 한 문구만 추가하면 된다.

~~~~~~~~~~~~~~~
  "main": ~~~~~~,
  "type":"module", //node 가아니라 js 자체 모듈을 쓸거다

  "scripts": {
    ~~~~~~~
~~~~~~~~~~~~~

 

 

모듈의 특징

모듈은 최초로 실행된 값이 계속 나온다. 즉 특정 모듈을 사용하는 여러 실행파일이 있어도, 모듈파일은 최초로 실행된후 메모리에 캐싱되어, 해당 모듈에 계속 접근하면 캐싱된 결과를 보낼 뿐 새롭게 모듈 코드를 돌리지 않는다.

//귀찮으니까 임의로 ES6 문법으로 통일한다. 

//a.js

export let data=5;

//b.js
import {data} from './a.js';

console.log(data); //5
data=9;
console.log(data); //9

//c.js
import {data} from './a.js';

console.log(data); //5
data=7;
console.log(data); //7

이런 모듈을 뭐 할때 유용하게 쓸까?

보통 라우팅을 하거나 이벤트를 분리시킬때 유용하게 쓴다.

예시를 들자면,

//event.js
const E=require('events'); // node의 기본 event 모듈에서 eventemitter 가져옴

//재사용성을 높이기 위해 이벤트 이미터 객체(instant)가 아닌 클래스를 내보냄 
class customEvent extends E{
    raiseEvent(){
        this.emit('a','이벤트 a가 발생');
        this.emit('b','이벤트 b가 발생');
        this.emit('c','이벤트 c가 발생');
    }
}

module.exports.CE=customEvent;

//main.js
const M=require('./event.js');

let emitter = new M.customEvent(); //내보낸 클래스로 이미터 객체 만듬

emitter.on('a',(data)=>{
    console.log(data);
});
emitter.on('b',(data)=>{
    console.log(data);
});
emitter.on('c',(data)=>{
    console.log(data);
});

emitter.raiseEvent();

위의 코드를 실행하면

이런 결과가 나온다.


출처1