TIL

23/10/31 TIL __ 모듈 타입에서 변수의 특징

GABOJOK 2023. 10. 31. 23:11

 

 

 

바닐라 자바스크립트를 이용해서 프로젝트를 진행하다가, export import를 이용해 모듈화를 진행해봤다.

어려웠지만 결국 만들어 냈다. 

문제는 여기서부터였다.

만든 프로젝트를 확장해서 팀 프로젝트로 진행하게 되었는데, 

추가 코드 작성부터 추가 리펙토링이 정말 정말 어려웠다.

 

일단 모듈화에 대한 이해도가 낮은 상태에서 만지려고 하니 너무 어려웠다.

어떻게 손을 대야할지도 감이 안왔다. 살짝만 만져도 다 틀어진 것처럼 느껴졌기 때문에 ㅠㅠㅠ

그래서 일단 코드를 작성한 뒤에 리펙토링을 진행했다. 

 

리펙토링을 진행하던 과정 중에서, 기능별로 모아두기 위해 붙여주는 애들은 따로 한 파일에 모아둬야 겠다는 생각을 했다.

그래서 파일에 모았는데, 작동이 안되기 시작했다.

 

에러메세지를 찾아 나섯지만, 문제를 이해할 수 없었다.

 

 

 

🧐  상황은 이랬다.

append.js 파일에 datasRepeat 함수 안에 temp ="" 이부분 에서 계속 에러가 났다. 

 

 

list.js 파일이다.

import { getData } from "./getData.js";
import { clickShow } from "./go.js";
import { genreUrlNum, makeGenreUrl, makeSearchUrl } from "./makeUrl.js";
import { moreHide, more } from "./more.js";
import { searchStart2, searchStart } from "./append.js";

let num = 1;
let temp = ""

//__ 해당 장르 url 추출 후 searchStart2 실행__
//more 클릭으로 페이지 이동한 경우
if (urlVal.includes("id=more&genre")) {
  const genreVal = urlVal.replace("?id=more&genre=", "");

  //장르별 top 10 쪽에서 more를 클릭했을 경우
  if (genreArr.includes(genreVal)) {
    const genreurl = await genreUrlNum(genreVal, num);
    await searchStart2(genreurl);
  }
  //인기영화, 평점높은영화 쪽에서 more를 클릭했을 경우
  else {
    const genreurl = await makeGenreUrl(genreVal, num);
    await searchStart2(genreurl);
  }
} //검색으로 페이지 이동한 경우 _ 주소에서 검색값 가져오기
else {
  const inputVal = urlVal.replace("?val=", "");
  const inputUrl = await makeSearchUrl(inputVal, num);
  await searchStart2(inputUrl);
}
export {num, temp}

 

 

append.js 파일이다.

import { getData } from "./getData.js";
import { moreHide } from "./more.js";
import { getInput, num, cardContainer, temp } from "./list.js";


...생략 


//데이터 가져와서 붙여주기
async function searchStart2(url) {
  const searchData = await getData(url);
  await moreHide(searchData, num);
  console.log(num); //1
  return datasRepeat(searchData.results);
}

//받은 데이터 반복하며 appendFunc 실행 결과물 cardContainer에 붙여주기
function datasRepeat(data) {
  temp = ""; // Assignment to constant variable.
  for (let i = 0; i < data.length; i++) {
    temp += appendFunc(data[i]);
  }
  return (cardContainer.innerHTML += temp);
}

export { appendFunc, searchStart, datasRepeat, searchStart2, num };

 

 

그런데 왜 그러는 건지 이해할 수가 없었다.

그 이유는 이랬다.

  1.  export / import를 통해 변수를 주고 받을 수 있다.
  2. 나는 변수를 let 으로 선언했다.
  3. num은 잘 작동하고 있었다.
  4. 왜 temp만 안되는 걸까?

 

이런 생각을 가지고 계속 봤지만 이유를 알수 없었다.

 

 

맑은 정신으로 아침에 다시 살펴보는데, num을 재할당을 해보니 똑같이 num도 작동하지 않았다....?!!

여기서부터 어랏 ??!! 싶었다.

모듈화를 하게 되면 재할당이 안되나?? 라는 추측을 가지고 다시 찾아보았다. 

 

 

https://blog.naver.com/pjt3591oo/222834625061

 

[javascript] 모듈 시스템 - CJS, ESM 동작원리 이해하기

안녕하세요 멍개입니다. 지난 시간의 자바스크립트 모듈 시스템을 다뤘습니다. https://blog.naver.com/pjt...

blog.naver.com

 

 

 

이 블로그를 보면 정말 자세히 나와있지만, 간단히 정리하면 이렇다. 

 

✅ CJS (CommonJS) 방식과 ESM (ECMAScript) 방식의 차이.

 

❣️요약하면 이렇다.!!!❣️

  사용 명령어 작동 방식 동적으로
모듈값 가져오기
CJS
(CommonJS)
require,  module.exports,  exports 새로운 메모리 공간이 할당되며
값을 복사해 사용한다.

동적으로 모듈을 가져올 수 있다. 
가능
ESM
(ECMAScript)
import,  export,  export default
export, import가 바라보는 메모리 공간이 같다.

동적으로 모듈을 가져올 수 없다.

메모리 공간의 값을 바꿀 수 없다.
(함수로는 가능)


모듈 경로에 대해 하나의 모듈레코드만 가지고 있다. 
두 모듈간 의존성을 가진다면 문제가 발생할 수 있다.
(순환참조 꼭 해야하는 이유가 없다면 하지 말것)

불가능

 

 

 

몇가지 차이가 있지만 변수값 변경에 관한 부분을 중점적으로 정리한다. 

다른 부분은 위 블로그에 정말 잘 나와있어 공부하기 좋았다!

 

 

 

🔮  export되어 가져온 변수값  재할당 하기.

 

일단 아래의 코드를 살펴보자.

먼저 모듈버전( ESM ) 이다. 

 

app.js 파일이다.

import { num, setNum, getNum } from "./testa.js";
console.log(num); //20
num = 30; //TypeError: Assignment to constant variable.
console.log(num);

 

testa.js 파일이다.

export let num = 20;

export function setNum(n) {
  num = n;
}

export function getNum() {
  return num;
}

 

 

ESM 모듈 버전의 스크립트 파일에서는, import 로 가져온 변수값에 재할당이 불가하다는 사실을 확인 할 수 있다.

TypeError: Assignment to constant variable.

 

(처음에는 let으로 변수선언 했는데 갑자기 왠 constant ? 했는데, type = "module" 이기 때문에 발생한 일...)

 

 

다음은 CJS 버전이다. (CommonJS 버전)

 

app.js파일

let { num, setNum, getNum } = require("./testa.js");
num = 23;
console.log(num);  //23

 

testa.js파일

let num = 20;

function setNum(n) {
  return (num = n);
}

module.exports = {
  num,
  setNum,
  getNum,
};

 

 

CJS의 스크립트 에서는 변수값을 가져와서 재할당을 하게 되면 잘 진행되는 것을 확인할 수 있다. 

 

 

 

 

 

 

🧐그렇다면 변수 값을 직접 변경하지 않고, 다른 방법으로 변경한다면 어떤 결과가 나올까?🧐

 

 

🔮  export되어 가져온 변수값에 다른 방법으로 재할당 하기.

 

CommonJS 방식부터 살펴보자.

 

app.js 파일

let { num: num0 } = require("./testa.js");
let { num: num1 } = require("./testa.js");

num0 = 10;
console.log(num0);  //10
console.log(num1); //20

 

구조분해할당을 이용해서  num 값을 num0으로 담아주고, 또다시 num1로도 담아줬다.

그리고 num0만 재할당을 한 뒤 num0, num1을 출력했다. 결과는  재할당 가능!

 

 

 

ESM 방식을 살펴보자

 

app.js 파일

import { num as num0, setNum } from "./testa.js";
import { num as num1 } from "./testa.js";

console.log(num1);  //20
setNum(14);
console.log(num0); //14
console.log(num1); //14

 

testa.js 파일

export let num = 20;

export function setNum(n) {
  num = n;
}

export function getNum() {
  return num;
}

 

보면, setNum() 함수를 이용해 num 값을 바꿔주기 전까지는 기존에 20으로 출력된다.

그러나, 함수를 이용해 값을 바꾼 이후에는 계속 20이 아닌 바뀐 값으로 출력이 일어난다. 

 

 

❗️❗️❗️❗️❗️❗️❗️❗️❗️❗️❗️

 

이는 import, export 모두 같은 메모리 공간을 가리키고 있기 때문이다. 

따라서 setNum이 호출되면, 같은 스코프의 num을 바꾸며,

import 하면 해당 스코프를 바라보는 num은 당연하게도 바뀐 값을 가져온다. 

 

 

어찌되었던, ESM 방식도 재할당을 하려면 함수를 이용해서 재할당이 가능하다. 

 

❗️❗️❗️❗️❗️❗️❗️❗️❗️❗️❗️

 

 

 

 

 

추가로.. 

모듈화에 대해 잘 모르고 진행하다 보니, 순환참조를 하는 코드를 만들어 버렸다..ㅎ

이 상황에 대한 심각성을 전혀 모르고 이게 왜 안되지? 만 하고 있었는데

정말 피해야 할 방법이였던 것이다...ㅎㅎㅎ

 

 

🔮  순환참조가 일어난다면?

 

 

testa.js

let { num } = require("./app.js");
let num = 20;   //SyntaxError: Identifier 'num' has already been declared

function setNum(n) {
  return (num = n);
}

function getNum() {
  return num;
}

module.exports = {
  num,
  setNum,
  getNum,
};

 

 

app.js

let { num, setNum, getNum } = require("./testa.js");
num = 23;

module.exports = {
  num,
};

 

 

app.js 에서 num을 받아서 23 이라는 값으로 재할당을 한 뒤에,  다시  testa.js 파일로 넘겨줘보려 한다고 치자.

그런 경우 이런 에러메세지가 나온다. 

 

SyntaxError: Identifier 'num' has already been declared

 

 

즉 이미 가져왔는데 왜 또 선언해? 이런 말이다. 

var은 사용을 지양하고 있기 때문에, let과 const를 사용하고 있는 현 시점에서,

재선언을 하고 있으니 오류가 나는 것!!

 

당연한 결과였다. 😂🤣😂🥲

 

추가로, 이런 순환참조를 하게 되면, 나중에 에러찾기가 정말 정말 힘들어 지는 결과가 생길 수 있다고 하니 

정말 지양할것!!!!!!

 

 

 

 

다시 처음으로 돌아와서 

그렇다면 num은 왜 작동했던 것일까?

 

자세히 살펴보면, num이라는 애는 재선언보다 값을 가져와서 사용하는 용도로 쓰였던 상황이라서 값이 출력되었던 것이였다.

num을 재할당 하려고 했을때에는 바로 오류메세지가 나왔다.