본문 바로가기
Programming/JavaScript

[JS] 자바스크립트 변수의 정의, 선언/할당, 메모리 구조, 리터럴

 

*해당 포스팅은 김기수의 '코딩 자율학습 HTML + CSS + 자바스크립트', 정재남의 '코어 자바스크립트' 도서를 참고했습니다.*

 

 

1. 변수란?

 

변수란 흔히 '변할 수 있는 수'라고 알려져있다.  

'수'라고 해서 숫자만 포함되는 것이 아닌, 말 그대로 변할 수 있는 '데이터'를 의미한다.

데이터를 저장하는데 사용되는 이름이 붙은 저장소, 변수를 사용함으로 우리가 원하는대로 저장, 수정, 검색할 수 있다.

 

 

아마 흔히들 이렇게 생각할 것이다. name이라는 변수명(식별자)에 "seo"라는 문자열을 담으면, 메모리에 name이라는 곳이 "seo"일 것이라고. 하지만 실제로 이렇게 저장되진 않는다.

 


변수 vs 식별자?

변수와 식별자의 의미를 명확히 할 필요가 있다고 생각한다. 
변수는 말 그대로 변할 수 있는 수, 즉 위의 예제에서는 name이 아닌 "seo"가 된다. name은 문자열로 선언되어있기 때문에, "seo"가 아닌 "kim"이 될 수도 있는 것이다.

그렇다면 식별자는 무엇일까? 
식별자는 위 그림에서 name이 될 것이다. name은 변수가 아닌가?라고 생각할 수 있는데, 변수는 변할 수 있는 데이터 그 자체라고 위에서 언급했다. 식별자는 어떤 데이터를 식별하는 데 사용하는 이름으로, 변수명인 것이다. 

 

 

선언과 할당

변수가 메모리에 어떻게 저장되는지 좀 더 들여다 보기 전에 '변수의 선언과 할당'에 대해서 먼저 알아보겠다.

1.명사 널리 펴서 말함. 또는 그런 내용.

2.명사 국가나 집단이 자기의 방침, 의견, 주장 따위를 외부에 정식으로 표명함.

3.명사 어떤 회의의 진행에 한계를 두기 위하여 말함. 또는 그런 말.

 

먼저 선언이라 함은 우리가 익히 알고 있을 것이다. 어떤 것을 하겠노라고 알리는 것이다. 선언의 사전적 의미는 '널리 펴서 말함'이다. 

 

let name;

 

위와 같은 예제는,

변수 종류는 let, 변수명(식별자)는 name이라는 변할 수 있는 데이터를 만든다는 것이다. 이것이 변수 선언.

지금 당장은 name에 담긴 것이 없어 'undefined'이지만, 여기에 변할 수 있는 무언가를 담을 수 있다.

즉 변수 자체가 변경 가능한 데이터가 담길 수 있는 공간 또는 그릇이라고 도서에는 표현되었지만, 변수와 변수명(식별자)의 분리 해석을 알 필요는 있을 것 같다.

 

 

그렇다면 메모리에는?

 

처음 변수에 대해 설명할 때 메모리에 name이라는 곳에 "seo"가 존재할 것이라 설명했다.

하지만 실제 메모리에 저장되는 형태는 조금 다르다.

 

메모리에는 변수명으로 식별되어 저장되지 않는다. 변수명이 아주 다양할텐데 컴퓨터가 그 모든 것을 기억하고 일일히 이름을 부여하기에는 어려울 것이다.

메모리에는 주소로 저장된다. 

예컨대 let name;이라고 변수를 선언했다고 가정하자.

명령을 받은 컴퓨터는 우선 메모리에서 빈 공간 하나를 확보한다. (임의로 Add_1 ~ Add_4로 정함)

그 다음, 이 공간의 이름(식별자)은 name이라고 지정한다. name에는 값이 저장되는 것이 아니라 주소값이 저장된다.

(여기까지 변수 선언)

현재는 값을 할당하지 않았기 때문에 값이 비어있다. 그래서 undefined인 것이다. 

 

 

값을 넣어보자(할당)

 

변수를 어떤 식별자로 사용하겠다고 선언을 했다면, 그 변수에 데이터를 할당해야된다.

할당 割當 
명사 몫을 갈라 나눔. 또는 그 몫.

 

사전 또한 몫을 갈라 나누거나 그 몫을 의미한다고 한다. 메모리에서 나눠진 몫 중 하나를 가진다고 생각하면 될 듯 하다.

 

우선 name의 주소값을 확인한다. 예시상 Add_1이 name 변수의 주소값이니, 해당 주소대로 메모리를 찾아간다.

여기서 값을 할당할 때 Add_1의 값을 "seo"라고 바꾼다고 생각할 것이다. 하지만 JS에선 이렇게 작동하지 않는다.

간단히 설명하자면, 변수 영역 메모리 따로, 데이터 영역 메모리 따로 저장된다.

 

 

왜?

 

JS는 python과 같이 자료형을 미리 선언하지 않는다. 

JS는 숫자형의 경우 64비트의 공간을 먼저 확보한다. 즉, 할당시에 "=10"이면 숫자형으로 판단되어 미리 8바이트를 확보하는 것. 하지만 문자열과 여러가지 객체 등은 특별히 정해진 규격이 없다. 다시 말해 가변적으로 크기가 늘어날 수도 있고, 줄어들 수도 있다는 것이다. 

 

자료구조를 하다 보면 얼핏 봤을테지만, 정렬과 탐색에서 효율을 위해 그동안 아주 많은 고민이 이뤄졌던 걸 엿볼 수 있다. 

간단히 예를 들어 크기 변경을 하고자하는 주소가 메모리의 가장 마지막이면 뒤로 늘리기만 하면 그만이지만, 중간 지점에 있다거나 처음 지점에 있다면 모든 데이터들의 주소를 뒤로 옮겨 다시 식별자와 연결해야 하는 수고스러움이 발생한다.

그렇기 때문에 값을 저장할 때 참조를 사용한다. 

 

 

만약 위와 같은 상황이 있다고 가정해보자. 

변수 "seo"를 "Hello world"로 바꾼다면, "seo"가 할당되어 있는 주소에 직접 찾아가서 바꾸지 않는다.

대신 새로운 Add_2에 "Hello world"를 할당한다.

하지만, 자바스크립트에서 데이터(변수)를 저장할 때 이러한 구조로 저장되지 않는다. 

 

(여기서 기존에 있던 "seo"는 더이상 자신의 주소를 저장하는 변수가 없기 때문에 가비지 컬렉터에 의해 사라진다.

이 때 주소의 데이터를 참조 하고 있는 변수의 수를 참조 카운트라고 하는데, 이것이 0이 되면 GC의 대상이 되는 것)

 

 

 

결론, 변수는 이정표다

 

필자는 변수를 이정표라고 생각한다. 

우리가 자동차를 타고 부산으로 갈 때면, 네비게이션이 없어도 갈 수 있다. 

더 효율적이고 빠른 지름길은 모를 수 있다. 하지만 이정표가 부산으로 향하는 길이 어디인지 알려준다. 

변수도 마찬가지다. 변수를 선언하고 값을 할당할 때 두번의 참조가 이뤄지는데, 이 때 모두 주소를 알고 그 주소를 참조한다. 결국 변수는 이정표가 되지 않을까...하는 것이 내 생각.

 

 

리터럴

리터럴은 데이터(값) 그 자체를 의미한다. 변수에 넣는 변하지 않는 데이터.

const number =4;
const greeting = "안녕";
const isHappy = true;

위와 같은 예시에서 4, "안녕", true는 리터럴 값이다. '값'이라고 생각할 수 있다. 값으로 평가될 수 있는 모든 것은 리터럴이다.

 

리터럴의 종류

 

리터럴의 종류는 여러가지가 있다. 대표적으로 몇가지만 자세히 알아보겠다.

 

정수 리터럴 - 숫자형은 모두 실수(float)로 처리 된다.

var integer = 2; //정수
var double = 10.12; //실수
var negative = -20; //음의 정수
console.log(10 / -0); //Infinity
console.log(10 / -0); //-Infinity
console.log(1 * 'String'); //NaN(Not a Number)

 

0으로 나누면 왜 DivideByZeroException이 발생하지 않을까?

우리가 잘 알고 있는 자바와 파이썬은 0으로 나누려 시도하면 산술 예외가 발생한다. 하지만 자바스크립트에선 Infinity로 출력되는 걸 볼 수 있다. 나눗셈 연산에서 0으로 나누는 것이 수학적으로 유효하지 않기 때문이다. 하지만 자바스크립트에서는 0으로 수를 나누려고 하면 무한대가 되는데, 이는 동적으로 처리하는 특성 때문이다. 동적 타입 언어에서는 변수의 타입이 런타임에 결정되므로, 코드 실행 중에 변수의 값이 변경될 수 있다. 즉 언제든 변수 타입이 변경될 수 있는 걸 염두에 두고 유연성 있게 예외를 발생하지 않는 것. 숫자와 문자열을 곱하는 마지막 예제에서 NaN이 출력되는 것과 같음.

 

위 코드의 예제는 이해하기 어려운 내용은 아니다. 그러면 아래 코드의 결과를 예측해보자.

console.log(0.1+0.2)

아마 0.3이 출력될 것이라 생각하겠지만, 결과는 아래와 같다.

0.30000000000000004

 

왜 0.3이 아닐까?

앞서 정수 리터럴을 소개할 때 모든 숫자는 실수 처리된다는 것을 보았을것이다. 자바스크립트는 숫자를 담기위한 별도의 선언자 타입이 없다. 때문에, 숫자는 모두 64비트의 부동 소수점을 이용해서 저장된다. 이때 IEEE 754의 부동소수점 표현 형식 중 배정밀도 64비트 부동소수점 형식을 따른다. 

 

IEEE Standard 754 Floating Point Numbers

 

- 부호: 양수(0)인지 음수(1)인지 나타내며 1bit를 사용한다.

- 지수: 지수 부분으로 11bit를 차지한다.

- 가수: 52bit로, 부호 + 지수 + 가수를 합쳐 총 64bit로 수를 나타낸다.

 

부동 소수점으로 바꿔보자

컴퓨터는 모든 데이터를 2진법을 사용해 관리한다는 것을 알 것이다. 0.1을 2진수로 나타내면 아래와 같다.

console.log(0.1.toString(2));

0.0001100110011001100110011001100110011001100110011001101

 

이 숫자를 뜯어보겠다.

 

부호

왼쪽에서 첫번째 자리수가 0이니까 부호는 양수다.

 

지수

지수는 2^(n-1)-1+m로 계산하여 구한다. 

n은 지수부분의 bit 수다. 우리는 지수 부분의 bit가 11이므로 n=11이다.

m은 소수점을 몇번 이동해야 정수값 1이 도출되는지의 수다. 위의 예제에선 4번을 이동해야 1이 되므로, m=4.

즉 2^10 - 5가 되므로 지수는 1019가 된다. 1019를 2진법으로 나타내면 아래와 같이 10자리가 나온다.

 

console.log((1019).toString(2));
1111111011
왜 1019에 괄호를 쳤을까?

자바스크립트는 숫자를 모두 부동 소수점으로 취급한다. 0.1.toString의 경우 '숫자'라는 걸 인식할 수 있는 반면에 1019의 리터럴이 끝나면 이어지는 `.`을 속성 접근 연산자로 해석하게 된다. 1019를 하나의 객체로 인식하게 되는 것. 하지만 숫자는 기본적으로 객체가 아니므로 이런 형태의 호출은 구문 오류로 이어진다.

 

가수

가수는 0.1을 2진수로 변환한 값에서 정수값 1을 만든다. 총 4번 이동했기 때문에 

1.100110011001100110011001100110011001100110011001101이 된다. 이 값의 소수점 이후 부분이 가수다.

100110011001100110011001100110011001100110011001101은 51자리므로, 앞서 이야기했던 가수는 52자리가 되어야하기에 마지막에 0을 붙여 52 bit로 만들어준다. 이어서 0.2 또한 같은 방식으로 구하면 된다.

 

0.1(2) + 0.2(2) = ? 

그럼 이제 두 수를 더해보겠다. 

0111111110000011001100110011001100110011001100110011001100110100이 출력된다. 실제 0.3의 2진수는 얼마일까?

실제로 0011111111010011001100110011001100110011001100110011001100110011며, 우리가 계산한 값과는 다르다.

몇몇 소수는 10진법에서 2진수로 변환하는 과정에서 무한 소수가 되어버린다. 이때 저장공간에 한계가 있는 컴퓨터는 무한 소수를 유한 소수로 바꾼다. 그러면서 값들이 손실되거나 초과하는 것. (정밀도 문제) 

소수점 이하를 깊게 다루거나 큰 숫자를 다룰 때 bignumber.js같은 라이브러리를 사용하기도 한다고.

 

 

undefined와 null은 같지 않다. - 특수 자료형 

undefined 

변수나 상수를 컴퓨터 메모리 공간에 선언하면 반드시 생성한 공간에 저장할 데이터를 할당해야 한다.

할당하지 않을 경우 자바스크립트 내부적으로 변수와 상수 공간에 임시로 데이터를 할당하는데, 이때 할당되는 값이 undefined다. undefined는 다른 자료형과 다르게 사용자가 임의로 정의하고 할당하는 자료형은 아니다. 

실제 저장 구조와는 다르다는 것을 명심

 

앞에서 변수를 설명할 때 이 그림을 봤을 것이다. 이것처럼 선언은 했으나 값을 할당하고 있지 않다고 생각하면 된다.

아래에 undefined 예시를 기술했다.

 

1) 값을 대입하지 않은 변수, 즉 데이터 영역의 메모리 주소를 지정하지 않은 식별자에 접근할 때 

var a;
console.log(a);

 

2) 객체 내부의 존재하지 않는 프로퍼티에 접근하려고 할 때

var obj = {a:1};
console.log(obj.a); //1
console.log(obj.b); //undefined
console.log(b); //ReferenceError: b is not defined

 

3) return 문이 없거나 호출되지 않는 함수의 실행 결과

var func = function() {};
var c = func();
console.log(c); //undefined

 

undefined 예외

var arr1 = [];
arr1.length = 3;
console.log(arr1); //(3)[]

var arr2 = new Array(3);
console.log(arr2); //(3) []

var arr3 = [undefined, undefined,undefined];
console.log(arr3); //(3)[undefined, undefined,undefined]

arr1의 경우, 배열을 만들고 그 크기를 3으로 하자 길이 3의 빈 배열이 출력됐다. 배열에 3개의 빈 요소를 확보했지만 확보된 각 요소에는 어떤 값도, undefined도 할당되어 있지 않다. arr2도 마찬가지다. 하지만, arr3은 다르다. undefined가 3개가 할당된 배열이 출력됐다. 이처럼 '비어있는 요소'와 'undefined를 할당한 요소'는 출력 결과부터 다르다. 비어있는 요소는 배열 메소드들의 순회 대상에서 제외된다. 

 

undefined는 for문, filter, map, reduce에서 서로다른 출력 결과를 가져온다. 그러므로 undefined는 사용을 지양하는 것이 좋다. 같은 의미를 가진 null을 사용하는 것이 좋은 것. 이런 규칙을 따른다면 undefined는 오직 '값을 대입하지 않은 변수에 접근하고자 할 때 자바스크립트 엔진이 반환해주는 값'으로만 존재할 수 있다. 

 

 

null

null 자료형은 null 값 하나만 존재하며, 변수나 상수를 선언하고 의도적으로 선언한 공간을 비워 둘 때 할당한다. 

null은 자바스크립트에서 typeof null이 object다. 이는 자바스크립트 자체 버그라고 한다. 그래서 유효한 객체를 검사할 때 typeof가 아닌 n === null과 같이 써야 정확한 결과를 판별할 수 있다. 

 

 

결론, 리터럴은 값을 표현하는 방식

필자는 리터럴을 처음 이해할 때 약간의 헷갈림이 있었다. 데이터의 자료형이라고 하면 안 되는 걸까?

하지만 자바스크립트에서 리터럴을 자료형이라고 하기에는 무리가 있다. 리터럴은 값을 표현하는 표기법, 자료형은 데이터의 유형을 나타내는 것이다. 

 

예를 들어 숫자 리터럴인 `42`는 정수지만, 단순히 값의 표현 방식이지 자료형은 `number`다. 마찬가지로 문자열 리터럴인 "Hello"는 문자열이지만 자료형은 'string'이다. 

이러한 변하지 않는 데이터들이 있는데, 이를 리터럴이라고 한다. 리터럴은 소스 코드의 고정된 값을 대표하는 용어라고도 한다.

 

 

Reference

[1]

 

[JavaScript/자바스크립트] 리터럴과 타입

리터럴이란 사람이 이해할 수 있는 문자 또는 미리 약속된 기호로 표기한 코드. 리터럴은 값으로 평가된다. 따라서 리터럴도 표현식. ** 값으로 평가될 수 있는문은 모두 표현식** 리터럴의 종류

hans-j.tistory.com

 

[2]

 

자바스크립트의 실수 계산 오류

자바스크립트에서 0.1과 0.2를 더하면 0.3이 아닌 0.30000000000000004가 나온다. 도대체 왜 이럴까?

medium.com

 

[3]

 

자바스크립트에서 0.1+0.2는 왜 0.3이 아닐까?

자바스크립트에서 0.1 + 0.2 를 하면 0.3이 나올것 같지만, 0.30000000000000004이 나옵니다.왜 이런 문제가 발생하는 것일까요🤔?이번 시간에는 부동소수점을 구하는 방법을 알아보고0.1 + 0.2가 0.3이 나

velog.io