[JavaScript] - 메모리 모델

Lee Jeong Min·2021년 9월 12일
1
post-thumbnail

이 글은 JavaScript's Memory Model을 참고하여 작성한 글입니다.

📝 글을 쓰게 된 계기

개인적으로 자바스크립트로 프로그래밍을 하거나 알고리즘 문제를 풀다 보면 다음과 같은 상황을 많이 마주할 수 있었습니다.

// 주어진 n안의 정수 중 2의 배수를 반환하여라.

function solution(n) {
  let answer = []; // let 대신 const를 써도 되네?

  for (let i = 1; i <= n; i++) {
    if (i % 2 === 0) answer.push(i);
  }

  return answer;
}

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

위의 알고리즘 풀이에서 변수 answer를 선언할 시, let 대신에 const를 사용하여도 리턴되는 answer의 값은 똑같이 2, 4, 6, 8, 10이 나왔습니다.

const는 한번 할당하면 값을 바꿀 수 없지 않나?

처음에 저는 let과 const에 대해서 변하지 않는 값을 사용할 때는 const를, 변하는 값을 사용할 때는 let을 사용하여 한다고만 단순하게 알고 있었습니다.

하지만 이번에 모던 자바스크립트 deep dive와 여러 자바스크립트 관련 자료를 보면서 정확하게 동작되는 원리를 알게 되었고 위 식에서 let, const가 둘다 동일하게 동작하는 것인지 알게되어서 이 부분에 대해서 정리하고자 글을 작성하게 되었습니다.

JS에서 원시타입의 선언과 할당

let number = 1;

다음과 같은 코드가 실행된다고 가정해 봅시다. 그렇다면 자바스크립트 내부적으로는 어떤일이 발생할까요?

number라는 식별자를 js엔진이 인식하고, 이 1이라는 값을 저장하기 위해 메모리공간을 확보하여 메모리 주소를 식별자를 통해 접근할 수 있게 만들어 줍니다.

이러한 상황에서 아래와 같은 코드를 실행시키게 되면 다음의 그림처럼 내부에서 동작하게 됩니다.

let newNumber = number;

newNumber는 number와 같은 메모리 주소를 가리키면서 1이라는 값을 참조합니다.

두 변수가 같은 메모리 공간을 가리키고 있는 현재 상태에서

number = number +1;

위와 같은 코드를 실행시키게 되면 JS엔진은 원래 참조하고 있는 0x00000012 메모리 주소 공간에 값을 +1 시키는 것이 아닌 새로운 메모리 주소에 공간을 확보하여 변경된 값을 저장합니다. 다음 그림과 같이 말이죠

이렇듯 모든 JS의 원시타입 값(숫자, 문자열 등등...)들은 값이 변경되면 기존의 메모리 공간이 아닌 새로운 메모리 공간을 확보하여 변경된 값을 저장합니다.

자바스크립트 메모리 모델: 콜스택과 힙

여기에서는 설명의 편의상 JS 메모리 모델로 콜스택과 힙만 있다고 가정하여 설명하겠습니다.

콜 스택은 위 그림에서 볼 수 있듯이, 원시타입의 값이 저장되는 메모리입니다. 반면 힙은 원시타입이 아닌 비 원시타입의 값들이 저장되는 공간이고 큰 차이점은 배열이나 객체와 같이 동적으로 변할 수 있는 공간입니다.

JS에서 비원시 타입 변수의 선언과 할당

let myArray = [];

콜 스택과 힙이라는 메모리 공간에 대해서 인지하고, myArray라는 빈 배열의 선언이 이루어졌다고 가정해봅시다.

[] 빈 배열과 같은 비 원시 타입이 선언이 되었을 때, 이를 메모리에 할당하기 위해서 다음과 같은 작업이 이루어집니다.

  1. myArray라는 식별자를 만듦
  2. 메모리 주소를 런타임 시에 할당
  3. 힙에 할당된 메모리 주소 값을 저장(런타임 시)
  4. 힙의 메모리 주소에 할당 된 값인 빈 배열을 저장

여기에서 myArray 변수에 push 혹은 pop을 할 수 있습니다.

myArray.push(1);
myArray.push(2);
myArray.push(3);
myArray.push(4);
myArray.pop();

let vs const

이 부분에서 제가 처음에 계기로 언급한 값이 바뀌는 데 왜 const를 사용해도 똑같은 결과가 나오는 지에 대한 답이 나옵니다.

아래에 const로 원시타입을 선언한 경우와 비 원시타입을 선언한 경우를 보겠습니다.

const로 원시타입을 선언한 경우

const TAX_RATIO = 0.1;
TAX_RATIO = 0.2; // TypeError

위와 같은 코드가 실행된다고 보면 처음 변수가 선언되었을 시 다음과 같은 그림을 하게 됩니다.

그 이후에 TAX_RATIO를 0.2로 재할당하는 경우 const로 선언하였기 때문에 다음과 같이 타입에러를 발생시키고 재할당 시킬 수 없게 됩니다.

const로 비 원시타입(배열)을 선언한 경우

const myArray = [];

다음과 같이 myArray가 선언되면, 다음 그림과 같이 메모리에 저장이 됩니다.

이 상황에서 배열에 값을 추가해주면

myArray.push(1);
myArray.push(2);
myArray.push(3);
myArray.push(4);
myArray.push(5);

힙에 존재하는 배열에 값을 넣어줍니다. 그러나 const로 선언한 myArray의 메모리 주소는 변경되지 않아서 오류가 발생하지 않게 됩니다.

그러나 할당을 하는 경우, call stack의 새로운 메모리 공간을 확보하여 값을 저장시켜야 하기 때문에 이는 오류를 발생시킵니다.

🚩 마무리

결국 정리하자면, 비 원시타입(배열, 객체 등등..)의 경우 값은 힙이라는 메모리 공간에 저장이 되고 힙 메모리 주소가 콜 스택의 값으로 저장이 됩니다. 할당이 아닌 다른 함수를 이용하여 값을 변경시키는 경우는 결국엔 콜 스택의 메모리 주소와 값을 변경시키기 않기 때문에 const를 사용하여도 됩니다.

profile
It is possible for ordinary people to choose to be extraordinary.

0개의 댓글