변수와 메모리

@bbearcookie · June 02, 2023 · 10 min read

변수

변수는 하나의 값을 저장하기 위해 확보한 메모리 공간 또는 메모리 공간을 식별하기 위해 붙인 이름이다. 특히 변수의 이름은 식별자라고도 한다.

식별자
어떤 값을 구별해서 식별할 수 있는 고유한 이름

메모리 공간을 확보하고 값을 저장하는 과정까지는 몇 가지의 단계가 있다.

선언

값을 저장하기 위한 메모리 공간을 확보하고,
변수의 이름과 메모리 공간의 주소를 연결하는 단계이다.

var name; // 선언

초기화

변수의 초기 값을 저장하는 단계이다.
호이스팅시 varundefined 로 초기화되고,
letconst 는 초기화가 발생하지 않는다.

var name = "cookie"; // 초기화

할당

변수의 이름에 할당 연산자(=) 를 사용하여 값을 할당하는 단계이다.

var name; // 선언
name = "cookie"; // 할당

변수 생성 방법

자바스크립트에서 변수나 상수를 생성하는 키워드는 var, let, const 가 존재한다. 이 글에서 varlet 에 존재하는 몇 가지의 차이점을 서술해보고자 한다.

  • var: ES6가 등장하기 전에 변수를 생성하는 유일한 키워드이다.
    현재는 사용하지 않기를 권장한다.
  • let: ES6에서 등장한 키워드이다.
    기존의 var 를 사용했을 때 실수가 발생할 수 있었던 부분을 방지한다.
  • const: ES6에서 등장한 키워드이다.
    값의 재할당이 불가능하다는 점을 제외하고는 let 과 동일한 동작을 한다.

중복 선언

var는 중복 선언이 가능하지만, let은 중복 선언이 불가능하다.

var hello = "hello";
var hello = "rehello"; // (O)

let bye = "bye";
let bye = "rehello"; // (X) Cannot redeclare block-scoped variable 'bye'.

호이스팅

자바스크립트 엔진은 실제 코드를 런타임 환경에서 실행하기 전에 먼저 코드 평가 과정을 통해서 실행 컨텍스트를 생성하는 과정을 거친다.

코드 평가 과정에서는 함수나 변수의 선언 부분을 맨 위로 끌어올려 메모리 공간을 미리 할당하는 호이스팅 과정이 일어나는데 varlet 으로 선언한 변수에는 차이가 존재한다.

  • var: 호이스팅시 선언초기화 단계가 발생하여 값이 undefined 가 된다.
  • let: 호이스팅시 선언 단계만 발생하여 값이 존재하지 않는다.
    따라서 코드상으로 변수를 생성한 코드 구문 위에서 접근할 수 없는데
    변수는 선언이 되었지만 아직 초기화가 되지 않아 접근할 수 없는 구간을 일시적 사각지대(Temporal Dead Zone) 라고 한다.
console.log(v); // undefined
console.log(l); // ReferenceError: Cannot access 'l' before initializationfunc(); // 안녕하세요

var v = "hello";
let l = "bye"; 

function func() {
  console.log("안녕하세요");
}

스코프

var 는 전역에서는 전역 스코프로, 함수 내부에서는 함수 레벨 스코프로 동작한다.
let블럭 레벨 스코프로 동작한다.

전역 스코프에서

var hello1 = 123;
let hello2 = 123;

실행 결과
실행 결과

var 로 선언한 변수는 전역 객체에, let 으로 선언한 변수는 전역이 아닌 블럭 개념의 영역에 저장되고 있다.

함수 스코프에서

function func() {
  var hello1 = 123;
  let hello2 = 123;
}
func();

실행 결과
실행 결과

var 로 선언한 변수와 let 으로 선언한 변수 모두 함수의 스코프에 저장되고 있다.

블럭 스코프에서

function func() {
  {
    var hello1 = 123;
    let hello2 = 123;
    console.log();
  }
}
func();

실행 결과
실행 결과

var 로 선언한 변수는 함수의 스코프에, let 으로 선언한 변수는 블럭 개념의 스코프에 저장되고 있다.

예시 코드

var v = "global"; // global scope

function func() {
  var v = "v"; // func scope
  let l = "l"; // func scope
  console.log(v); // v
  console.log(l); // l
  
  {    var v = "vv"; // func scope    let l = "ll"; // block scope  }  
  console.log(v); // vv
  console.log(l); // l
}

func();
console.log(v); // global

메모리

자바스크립트에서 메모리 공간에 할당한 값을 불변하기 때문에 변경할 수 없다.
그리고 변수 식별자는 메모리 공간의 주소를 가리키게 된다.

만약 변수 식별자 에 대해서 대입 연산자(=) 를 통해 새로운 값을 보관하면,
새로운 메모리 공간을 할당하여 저장하고 변수 식별자 는 그 메모리 공간을 가리키게 된다.

그런데 흔히 원시 타입은 불변하고 참조 타입은 가변적이라고 하는데 원리에 대해서 살펴보자!

  • 원시 타입
    숫자, 문자열, 논리값 같은 원시 값을 저장할 땐
    '변수가 가리키는 메모리 주소''값' 이 모두 스택 영역에 저장된다.
  • 참조 타입
    객체, 배열과 같이 동적으로 프로퍼티가 변화하거나 길이가 달라질 수 있는 값을 저장할 땐 메모리 공간을 얼마나 확보해야 하는지 알 수 없다.
    따라서 '변수가 가리키는 메모리 주소'스택 영역에 저장되고
    '값' 은 메모리 공간을 동적으로 할당할 수 있는 힙 영역에 저장된다.

메모리 할당

원시 타입 예시

let n1 = 10;
let n2 = 20;
let n3 = n1;
n1 = n2 - 5;

메모리
메모리

3번째 줄까지 실행했을 때의 메모리:

  • n1: 새로운 원시 타입의 값이 대입되고 있으므로 새로운 메모리 공간이 할당된다.
  • n2: 새로운 원시 타입의 값이 대입되고 있으므로 새로운 메모리 공간이 할당된다.
  • n3: 기존의 n1 과 같은 위치의 메모리 공간을 가리킨다.

4번째 줄을 실행했을 때의 메모리:

  • n1: 새로운 원시 타입의 값이 대입되고 있으므로 새로운 메모리 공간이 할당된다.
  • n2: 변화가 없다.
  • n3: 변화가 없다.

참조 타입 예시

const kim = {};
kim.name = 'kim';
kim.age = 20;

const lee = kim;
lee.name = 'lee';

console.log(kim); // { name: 'lee', age: 20 }
console.log(lee); // { name: 'lee', age: 20 }

메모리
메모리

  • n1: 새로운 원시 타입의 값이 대입되고 있으므로 새로운 메모리 공간이 할당된다.
  • n2: 새로운 원시 타입의 값이 대입되고 있으므로 새로운 메모리 공간이 할당된다.
  • kim: 새로운 참조 타입의 값이 대입되고 있으므로 힙 영역과 스택 영역에 메모리 공간이 확보된다. 실제 값은 힙 영역에 저장되고 힙 영역의 메모리 주소가 스택 영역에 저장된다.
  • lee: 기존의 kim 이 참조하던 메모리 공간을 그대로 참조한다.
    kimlee 는 같은 힙 영역을 참조하고 있기 때문에 객체의 프로퍼티를 변경시키면 힙 영역의 값에 변경이 발생하게 되고 kimlee 의 프로퍼티가 모두 변경된다.

메모리 해제

더 이상 사용하지 않는 메모리 공간에 대해서는 가비지 컬렉터가 자동으로 삭제한다.
여기에는 Mark and Sweep 알고리즘이 사용되는데, 최상위 객체은 window 객체에서부터 내려가면서 접근하지 않는 메모리 공간은 사용하지 않는 값이라고 판단하여 제거하는 방식이다.

참고 자료

모던 자바스크립트 Deep Dive 4장 변수
호이스팅 (MDN)
03. 원시 값과 객체는 메모리에 어떻게 저장되는가? 객체의 복제란? (좋아요 요정)
[10분 테코톡] 📏 인치의 불변성

@bbearcookie
Frontend Developer