ARTICLE | [JS] 변수

noopy·2021년 9월 3일
0

✏️ STUDY

목록 보기
1/10

팀원들과 함께하는 모던 JS 딥다이브 스터디 1차 💕

이번 아티클은 이웅모 저자님의 모던 자바스크립트 Deep Dive
정재남 저자님의 코어 자바스크립트를 토대로 정리하였다.

🍡 메모리

JS 엔진은 코드를 실행하기 위해 JS 코드를 계산(평가)하고 메모리에 기억한다.
코드를 평가를 하기 위해선 메모리에 데이터를 먼저 기억한 후 CPU를 통해 연산을 수행한다.

트랜지스터와 스위칭

컴퓨터는 모든 데이터를 0과 1로 바꿔 기억한다.

(OSI 7계층 - 물리계층에서 가져옴)


컴퓨터 내부에는 수많은 트랜지스터가 존재한다.
이 트랜지스터는 전기신호를 받으면 회로를 닫아 (TRUE = 1) 켜지고,
신호를 받지 않으면 회로를 열어 (FALSE = 0) 꺼진다.
즉, 컴퓨터는 트랜지스터를 통해 전기 신호를 0과 1로 바꾸는데,
이러한 전기신호를 수로 표현하는 데 가장 효율적인 2진법을 기반으로 데이터를 처리한다.

비트와 바이트

비트(bit): 0 | 1 만 표현할 수 있는 하나의 메모리 조각
바이트(byte): 8bit. 2^8 = 256개의 값 표현 가능

한 개의 bit로는 0 | 1이라는 2가지 표현밖에 할 수 없기 때문에
적당하게 효율적으로 묶어서 바이트(Byte)라고 칭하고 정보를 표현하는 기본단위로 삼고 있다.

매우 많은 비트를 한 단위로 묶는다면? - 낭비되는 비트가 생김
제약이 따르더라도 적당하게 묶자! = 8bit

1byte = 1Chracter 라고도 부른다.
1byte로 (영어의 경우) 한 개의 문자(chracter)를 표현할 수 있기 때문이다.

영어, 숫자, 공백은 각각 1 byte로 표현하고도 남지만,
한글은 한 글자당 여러 개의 조합으로 이루어졌기 때문에 최소 2byte (2^16 = 65536)이다.

더 자세한 것은 아스키코드와 유니코드 참고.

컴퓨터의 메모리

메모리: 데이터를 저장할 수 있는 메모리 셀(memory cell)의 집합체.
메모리 주소: 0 ~ 메모리의 크기만큼 정수로 표현된 메모리 공간의 위치.

컴퓨터의 메모리는 1Byte(8bit) 크기의 메모리 셀 여러 개로 구성되어 있다.
각 메모리 셀은 0 ~ 메모리 크기만큼의 정수로 표현된 메모리 주소를 갖고 있고,
8개의 bit로 데이터를 저장한다.

용량이 큰 데이터는 어떻게 저장할까?

JS에서는 Number 타입을 64bit(= 8Byte) 부동 소수점 형식으로 저장되도록 설정되어 있다.

JAVA나 C같은 경우의 정적 타입 언어는 메모리 낭비를 최소화하기 위해
데이터 타입 별로 할당할 메모리 영역을 2Byte, 4Byte 등으로 나누어 정해놓았다.
하지만 과거보다 메모리 용량이 커진 상황에서 등장한 JS는 메모리 공간을 좀더 넉넉하게 할당했고,
숫자의 경우 정수형인지(int) 부동 소수형인지(float) 구분하지 않고 64bit(8 Byte)를 확보한다.

따라서 JS의 숫자형 타입은 8Byte를 확보하기 때문에,
컴퓨터의 메모리를 저장할 때 8Byte씩 확보하고 숫자를 저장한다.


주소값을 참조할 땐 첫번째 메모리 셀의 주소로 참조한다.

JS 메모리 모델


프로그램이 실행되는 동안 JS 엔진은 메모리를 필요한 만큼 할당하고 회수한다.
이렇게 할당된 공간을 Resident set이라 부르며, 각각의 세그먼트를 가진다.

스택 메모리: 원시타입(숫자 등)의 데이터가 저장되는 곳.
힙 메모리:

  • 객체와 같은 참조타입 데이터가 저장되는 곳.
  • 가비지 컬렉션이 발생하는 곳.

🍡 변수

변수:
1. 값을 저장하기 위해 확보한 메모리 공간 자체
2. 메모리 공간을 식별하기 위해 붙인 이름 (식별자)

변수는 JS의 컴파일러 | 인터프리터를 통해 메모리 공간의 주소로 치환되어 실행된다.

JS 엔진은 평가단계에서 result 변수를 만나면 실행 컨텍스트의 렉시컬 환경의 환경 레코드에 식별자를 저장한다.
실행단계(런타임)에선 메모리 상에 (콜스택에 ) 10과 20을 저장 후, 더한 값 30도 메모리에 저장한다.
30의 특정 메모리 주소를 result 변수에 연결시킨다.
따라서 result 변수로 30이 저장된 메모리 공간을 식별해 참조할 수 있다.

식별자

식별자:

  • 어떤 값을 식별할 수 있는 고유한 이름
  • 메모리 주소에 붙인 이름

식별자와 변수는 비슷하지만 다르다.
식별자는 어떤 것을 식별할 수 있는 이름, 즉 여기서는 변수명이고
변수는 값을 저장하기 위해 확보한 메모리 공간 자체, 즉 그릇이다.

변수 result가 메모리 주소 참조를 통해 30을 식별할 수 있던 것처럼,
식별자는 값이 아닌 메모리 주소를 기억하고 있다.

즉, 메모리 상에 존재하는 어떤 값을 식별할 수 있는 이름은 모두 식별자이기 때문에
변수, 함수, 클래스 등의 이름은 모두 식별자이다.

변수 선언

변수 선언 키워드: var, let, const

변수 선언 단계

  1. 선언 단계: 렉시컬 환경에 변수 이름을 등록.
  2. 초기화 단계: 값을 저장하기 위한 메모리 공간 확보 후 undefined으로 초기화.

var

실행컨텍스트 참고.
본래 변수 선언 단계는 두 단계로 분리되지만,
var 키워드로 선언된 변수는 선언 단계 + 초기화 단계가 한 번에 진행된다.

console.log(score) // undefined

score = 80;
var score;

console.log(score) // 80

score = 90; // 재할당

(선언 단계에서)
var 키워드를 만나면
1. 실행 컨텍스트> 렉시컬 환경 > 객체 환경 레코드에 식별자 정보 저장.
2. 콜스택에 데이터 저장을 위한 메모리 영역을 확보하고 undefined으로 초기화
3. 해당 메모리 주소를 식별자와 연결.

(런타임)
1. score 식별자를 해당 스코프의 실행 컨텍스트에서 찾고 있으면
연결된 메모리 주소를 통해 참조된 값을 반환.
2. (재할당) 80이 메모리 영역에 있다면 메모리 주소를 연결하고,
없다면 메모리 영역을 확보하고 주소를 연결.

렉시컬 환경: {
  환경 레코드: {
    선언적 환경 레코드: {} // let, const
    객체 환경 레코드: { // var, 함수 선언문...
    'score': undefined
    }
  }
}

여기서 호이스팅이란 개념이 나온다.

호이스팅은 끌어올리다란 의미로
JS 엔진이 평가단계에서 해당 스코프의 코드를 실행하는데 필요한
변수의 선언부와 함수 선언문을 실행 컨텍스트에 저장하는 것을 의미한다.
때문에 실제 코드 선언이 아래에 있음에도 불구하고
호이스팅으로 인해 선언된 코드 위에서 해당 변수를 참조할 수 있는 것이다.

var 키워드로 선언된 변수는 선언단계와 초기화단계가 동시에 수행되어
호이스팅으로 참조가 가능한 것이다.

const, let

console.log(score) // Cannot access 'score' before initialization

score = 80;
let score;

console.log(score) 

score = 90;

(선언 단계에서)
const, let 키워드를 만나면
실행 컨텍스트> 렉시컬 환경 > 선언적 환경 레코드에 식별자 정보 저장.

렉시컬 환경: {
  환경 레코드: {
    선언적 환경 레코드: {
    'score': 
    }
    객체 환경 레코드: {}
  }
}

⚠️ 초기화 되기 전 식별자를 사용할 경우, Temporary Dead Zone 발생.
오류: Cannot access 'score' before initialization

(런타임)
1. 초기화 단계는 런타임에서 해당 키워드를 만났을 때 이루어짐.

  • 런타임에서 let score를 만났을 때 undefined로 초기화 후,
    이후 할당부에서 메모리 상의 값을 찾아 연결.
  1. (재할당) var와 같음.
console.log(score) // score is not defined

score = 80;
const score; // SyntaxError: Unexpected token

console.log(score) 

score = 90; // ❌ 재할당 불가

const는 재할당이 불가능하다 = 즉 메모리 주소를 변경할 수 없다.
때문에 선언만 하고 할당하지 않을 경우,
재할당 가능성을 주기 때문에 선언과 동시에 할당을 해야 하는 것 같다. 🧐

변수 선언 정리


var 키워드로 선언된 변수는 선언단계와 초기화단계가 한번에 진행되기 때문에
호이스팅이 일어난 후 초기화된 값으로 참조가 가능하다.

const, let 키워드로 선언된 변수 또한 호이스팅이 일어나지만
초기화되기 전에 참조될 경우 TDZ 존에 빠져있는 상태로 참조 에러가 발생한다.

🍡 참조타입

let object = {
	a: 1,
	b: '문자열'  	
};


참조타입은 내부의 값이 유동적으로 변할 수 있기 때문에 메모리 힙에 저장된다.
(선언 단계에서)
let object를 만나면
렉시컬 환경의 선언적 환경 레코드에 저장.

(실행 단계에서)
1. let object를 만났을 때 undefined로 초기화 후, 할당부에서 {}를 만난다.
2. {}는 여러개의 프로퍼티로 이루어진 데이터 그룹이므로,
메모리 힙에서 메모리 영역을 확보하고 {}의 주소를 콜스택에 저장한다.
3. 콜스택에서 프로퍼티들을 찾아 해당 메모리 주소와 식별자를 연결한다.

참조타입 프로퍼티 재할당

let object = {
	a: 1,
	b: '문자열 합치기'  	
};

내부 프로퍼티가 바뀌더라도 메모리 힙의 객체 주소는 변함이 없다.

참조타입 복사

let object = {
	a: 1,
	b: '문자열 합치기'  	
};

let copiedObject = object;

🍡 가비지 컬렉터

'문자열'은 어디에도 참조되지 않기 때문에 가비지 컬렉터에 의해 메모리가 해제될 것이다.

참고사이트)

전자 혁명의 시초 '트랜지스터' 알아보기!

[C언어 기초 1] 비트를 알아야 메모리가 보인다

나무위키 | 바이트

메모리 구조,원시타입 변수 생성원리, 가비지 컬렉터
콜스택/ 메모리힙 구조, 데이터 저장 / 참조원리

[자바스크립트] 코드 실행 2단계와 변수/함수 생성 과정

JS 메모리 구조

V8 엔진(자바스크립트, NodeJS, Deno, WebAssembly) 내부의 메모리 관리 시각화하기

한글이 영어보다 용량이 큰 이유

profile
💪🏻 아는 걸 설명할 줄 아는 개발자 되기

0개의 댓글