[JavaScript] 1. Variable

100tick·2022년 12월 12일
0

JavaScript Deep Dive

목록 보기
1/16
post-thumbnail

1.1 expression, evalutation

1 + 2 // 3

1 + 2라는 연산의 결과는 3이 된다.
즉, 1 + 2는 결과로 3을 반환한다고 할 수 있다.
이렇게 결과적으로 연산을 마치면 하나의 값이 되고 그 값을 반환하는 식을 expression, 표현식이라고 부르고 표현식 -> 값으로 계산하는 것을 evaluation, 평가라고 한다.

1, 2operand, 피연산자, +operator, 연산자라고 한다.

그러나 위 코드는 연산 결과를 어디에도 저장하고 있지 않다.
연산 결과를 재사용하기 위해서는 어딘가에 저장해야 한다.
프로그래밍에서 연산 결과, 값 등을 저장하기 위한 공간을 변수라고 한다.


1.2 Variable, Statement

let x = 1 + 2; // 3

아까와 같은 수식처럼 보이지만, 한가지 차이는 좌측에 let a =가 추가되었다는 것이다.
수학에서 =같다는 의미지만, 프로그래밍에서 =대입한다는 뜻이다.
(수학에서는 임의의 변수에 값을 지정할 때, 대입이라는 용어를 사용하지만 프로그래밍에서는 assignment, 할당이라는 표현을 주로 사용한다)

x라는 변수에 1 + 2의 결과값인 3을 저장하라는 명령이 되는 것이다.

let은 나중에 자세히 보겠지만 프로그래밍은 대부분 영어로 이루어져 있기 때문에 영어로 읽으면 "x라는 변수에 3을 둬라"라는 직관적인 문장이 된다.

바둑판의 어딘가에 돌을 두는 이미지를 상상해보자.

19 x 19, 총 361칸의 자리에 검은 돌, 흰 돌 중 원하는 돌을 놓을 수 있을 것이다.
바둑돌이 변수에 저장되는 1, 2, 3 등의 이라고 한다면, 이 바둑판이 변수가 저장되는 공간인 RAM이라고 생각할 수 있겠다.
(💡SDD, HDD, Flash Memory 등 데이터를 저장하기 위한 장치는 다양하지만 일반적으로 JS에서 생성한 변수들은 RAM에 위치하게 되며, RAM메모리라고도 부른다.)

바둑판은 저장할 수 있는 공간의 수가 361칸이었지만, RAM은 그보다 훨씬 더 방대한 공간을 가지고 있다.
1GB가 약 1,073,741,824byte로, 숫자를 저장한다고 치면 약 1.3억 칸을 가진 바둑판이 되는 셈이다.(JS에서 숫자 데이터 하나를 저장하기 위해서 8byte가 필요하기 때문)

최근의 데스크탑 컴퓨터들은 RAM이 보통 16GB쯤 되니, 20억 개이상의 숫자 데이터를 저장할 수 있다고 생각하면 된다.

바둑을 둘 때는 사람이 직접 바둑판 위의 원하는 공간에 돌을 놓지만, 컴퓨터에서 그런 식으로 데이터를 RAM에 놓는 것은 어려운 이야기다.

적어도 JS에서는.

let x = 1 + 2;이라는 수식을 다시 보자.

첫번째로 1 + 23으로 evaluation, 평가된다.
두번째로 x라는 변수에 3대입한다.

x수십억 개의 공간을 가지고 있는 RAM의 어딘가3을 저장한다.
어딘지는 모르지만 x라는 변수를 통해 그 공간에 접근해서 그 안에 저장된 3이라는 값을 읽어올 수 있게 되는 것이다.

연락처에 수천 개의 전화번호가 저장되어 있다고 생각해보자.
보통은 전화번호를 다 외우지 않고 전화번호와 이름을 같이 저장하여 이름을 보고 번호의 주인이 누군지 식별할 것이다.(그래서 변수명을 Identifier, 식별자라고도 부른다)

여기서 전화번호는 RAM의 주소(수십억 개의 공간을 구별하기 위해 각 공간마다 주소값을 가짐)
번호 주인의 이름은 변수명이라고 생각하면 이해가 쉬울 것이다.

김삿갓: 010-1234-5678으로 전화를 건다면, 전화번호부에서 김삿갓을 찾아서 통화 버튼을 누를 것이다.
그러면 010-1234-5678라는 번호를 가리키고 있기 때문에 김삿갓에게 전화를 걸면 010-1234-5678로 통화 연결이 될 것이다.

이제 [변수 - 메모리 주소][이름 - 전화번호]와 바꿔서 생각해보자.
let x = 3;
간단히 보기 위해 3이라는 값은 잠깐 잊고 x라는 변수명메모리 주소(RAM 상의 주소)만 놓고 생각해보자.

x3을 할당하라는 명령을 내리면 사용 가능한 메모리 공간을 찾아서 주소값을 알려준다.(메모리 할당/관리는 운영체제의 역할)
운영체제가 배정한 메모리 주소가 0xFF7F5BFF이라면 x에 값을 할당하는 것은 0xFF7F5BFF에 값을 할당하라는 의미인 것이다.

즉, x0xFF7F5BFF라는 주소값을, 인간이 알아보기 편하게 alias, 별칭을 지정해준 것과 같으며, [이름 - 전화번호]의 관계와 비슷하다.


1.3 var, let, const

var a = 1;
let b = 2;
const c = 3;

변수를 선언하는 방법은 3가지가 있다.
위에서 본 letvar, const

= 연산자를 이용해 변수에 값을 할당할 수 있었다.
값을 변경하고 싶으면 다시 한 번 =를 이용해 재할당하면 된다.

a = 10;
b = 10;
c = 10; // Uncaught TypeError: Assignment to constant variable.

var, let으로 선언된 변수는 재할당이 가능하지만, const는 최초 할당 이후 재할당이 불가능하다는 차이가 있다.(var, let으로 할당된 공간은 변수, const로 할당된 공간은 상수라고 부른다)

변수는 얼마든지 재할당이 가능하지만, 상수에 재할당을 하는 순간 위와 같이 오류가 발생한다.

상수를 사용할 일이 별로 없을 것 같다고 생각할 수도 있지만,
의외로 변수에 재할당을 하면 안 되는 경우가 많기 때문에 const도 상당히 자주 사용된다.

변수, 상수 구분 없이 헷갈리지 않게 그냥 let 하나로 통일해서 사용하고 상수는 변경을 안 하면 되는 것 아닌가 라고 생각할 수도 있겠지만, 변경하면 안 되는 변수들은 const로 선언하는 것이 좋다.

실수로 변경하는 일이 생기더라도 const는 변경이 불가능하므로 오류를 띄워줘서 바로잡을 수 있기 때문이다.

경험 많고 숙련된 프로그래머들도 인간이기에 실수를 덜 할지언정 안 할 수는 없는 것이고 실수를 하더라도 바로잡아줄 수 있는 장치를 마련해 두는 것이 좋은 습관이다.

const가 재할당이 불가능한 특별한 공간인 것은 알겠다.
그럼 var, let는 똑같이 재할당이 가능한 공간인데, 왜 중복해서 존재하는가.

let는 새롭게 추가된 문법으로 var의 단점을 보완하기 위해서 나왔다.
그러나 이미 var를 사용하는 수십 억, 혹은 수백 억 줄일지도 모르는 코드들이 존재하기 때문에 하위 호환성을 위해 var을 그대로 유지하고 있다.

만약 var을 갑작스럽게 없애버린다면 그 많은 예전 코드들이 다 박살이 날 수 있기 때문이다.
그래서 varJS에서 없애지 못하고 유지는 하고 있지만, 새로 만드는 코드는 let 사용이 권장되고 있다.


1.4 Variable Declaration, Initialization

지금까지는 let a = 1;과 같이 변수를 생성했다.
사실 이는 2가지 단계가 합쳐진 것으로 변수 선언 Declaration초기화 Initialization를 한번에 처리한 것이었다.

아래 코드를 보자.

let a; // declaration

a = 1; // initialization

변수 생성 과정이 2단계로 나눠진 것을 볼 수 있다.
위에서 변수를 생성할 때, 운영체제에게 RAM 어딘가에 위치한 사용 가능한, 다른 변수에 의해 사용되고 있지 않은 공간을 찾아서 그 주소값을 받아오고, 변수명을 마치 그 주소의 별칭인 것처럼 사용할 수 있으며, 그 별칭을 통해 원하는 값을 해당 주소에 넣을 수 있다고 하였다.

let a;를 코드로 작성하면, 이 중 RAM에서 사용 가능한 공간을 찾고, 주소값을 받아와서 별칭과 주소값을 연결하는 과정까지만 실행된다고 생각하면 된다.
이 과정을 변수 선언 Variable Declaration이라고 한다.

이 과정이 중요한 것이, 만약 다른 변수가 이미 사용중인 공간을 또 다른 변수에서 사용하게 되면 예상치 못한 오류가 발생할 수 있기 때문이다.
만약 은행 서버에서 이러한 일이 발생한다면, 통장 잔고가 0인 A와 잔고가 10억이 있는 B의 잔고가 같은 변수를 사용하게 되어, 잔고가 하나도 없는 A가 큰 돈을 인출해버리는 그런 큰 사건이 발생할 수도 있다.

컴퓨터의 연산이 초당 수 억 번인데 초당 대체 몇만, 몇십만 번의 오류가 일어날지 상상하기도 어려울 것 같다.

그러한 사고를 미연에 방지하기 위해 운영체제가 메모리를 관리하고, 변수 선언 과정에서 운영체제로부터 다른 변수에 의해 사용되고 있지 않은 메모리 공간을 사용하겠다고 표시하는 것이다.

조금 더러운 이야기지만 화장실에 들어가서 문을 걸어 잠그면 그 칸은 다른 사람이 사용할 수 없는 것과도 비슷하다고 볼 수 있겠다.

그리고 그 다음 과정이 원하는 값을 변수 안에 넣어주는 할당 Assignment인데, a = 5;와 같이 간단하게 작성할 수 있다.
a는 운영체제로부터 제공 받은 메모리 공간의 주소의 별칭으로, 연결된 메모리 공간 안에 5라는 값을 넣을 것이다.
(C언어의 포인터 변수와 헷갈릴까봐 적자면 포인터 변수는 자신의 공간 안에 값으로 주소를 들고 있는 것이다. 우리의 a는 값으로 5를 들고 있지만, 포인터 변수는 0xFFFFFFFF 등의 어떤 주소값을 들고 있을 것이다. 포인터를 모른다면 이 부분은 넘어가자)

새로운 변수를 생성할 때, let a;로 변수를 선언하고, a = 5;로 값을 할당하는 두 번의 과정으로 쪼개서 자세히 알아보았다.
여기서 다시 자세히 살펴봐야 할 점이 있는데, 선언한 값에 최초로 값을 할당하는, 초기화 Initialization라고 하는 과정이다.
특정 변수가 가리키는 메모리 공간에 값을 넣어준다는 점에서 할당과 같다고 볼 수도 있겠지만 약간의 차이가 존재한다.

우선 const로 선언한 변수는 최초 할당 이후 재할당이 불가능하다고 하였다.
그렇기에 JS에서 const a;로 상수를 선언하는 것은 오류를 발생시키도록 설계되어 있다.

const a; // Uncaught SyntaxError: Missing initializer in const declaration

이후에 a에 재할당을 할 수 없기 때문에 어떠한 값도 들어갈 수 없는 의미 없는 공간이 되어버리기 때문이다.(메모리 낭비)

그래서 const 상수는 아래와 같이 선언, 할당(초기화)가 반드시 한 번에 이루어져야 한다.

const a = 1;

그럼 let, var는 어떨까

var a; // undefined
let b; // undefined

a = 1;
b = 2;

아까 되는 것을 확인했지만 다시 한 번 선언, 할당(초기화)를 나누어서 실행해보았다.
여기서도 중요한 사실이 하나 있다.

a, b에 최초로 값을 할당(초기화) 하기 전인 선언 상태에서 각 변수에 들어 있는 값을 출력해보면 undefined가 나온다는 것이다.

이유인 즉슨, a, b라는 변수를 선언했으므로 운영체제로부터 사용 중이지 않은 메모리 공간의 주소를 받아왔을 것이다.
하지만 그 메모리 공간도 이전에 다른 변수에 의해 사용중이었을 것이다.
그리고 그 공간 안에 어떠한 값이 담겨 있었을 것이다.

선언만 하고 할당을 하지 않았을 때, 이전에 사용하던 값이 그대로 저장되어 있다면?

let level = 100;
// ...
//
// 시간이 지나 level 변수가 사라지고 그 변수가 사용하던 메모리 공간을 level2가 사용한다고 가정
let level2; // 100 (만약 자동으로 undefined로 초기화 해주지 않는다면)

level이라는 변수에 100을 담아 사용하다가 그 변수가 시간이 지나 사라지고,
level2라는 새로운 변수가 그 메모리 공간을 그대로 사용하게 되었다고 가정해보자.
사용자의 레벨이 10인데 level2가 따로 초기화를 하지 않아 100을 그대로 사용하게 된다면?

버그가 발생한 셈이다.
심지어 level이 아니라 경험치, 돈 등
값의 크기 자체가 다른 값이 사용하던 메모리 공간이라면?
1154191239, 44331112 등의 말도 안되는 레벨이 될 수도 있다.

그래서 이러한 사고를 방지하기 위해 JS는 선언 후 초기화 되지 않은 변수에 undefined라는 값을 넣어준다.
1154191239 등 터무니 없는 값이 나온다면 개발자가 대체 어디서 잘못된 것인지 원인을 파악하기도 힘들겠지만, undefined라면 값이 초기화가 안되었구나 라고 보다 빠르게 파악할 수 있을 것이다.


1.5 var, let의 차이점

먼저 아래 코드를 살펴보자

console.log(a); // undefined
var a = 10;
console.log(a) // 10
console.log(b);
let b = 10; // Uncaught ReferenceError: b is not defined

var로 선언한 a는 변수 선언 이전에 사용했는데도 불구하고 오류가 발생하지 않았다.
대신 선언과 동시에 10으로 초기화를 했는데, undefined라는, 선언만 하고 초기화가 이루어지지 않는 변수에 JS가 자동으로 할당하는 값이 들어간 것을 볼 수 있다.

let으로 선언한 b는 아예 정의되지 않은 변수라는 오류를 뱉는다.

var로 선언된 변수들은 선언과 초기화를 동시에 하더라도 미리 undefined를 할당 받는 것이다.

// a, b, c는 모두 undefined
// console.log(a, b, c);
var a = 1;
var b = 2;
var c = 3;

위 코드에서 var a = b = c = undefined;가 코드 시작 전에 미리 실행되는 것이다.
그래서 각 변수는 2번의 할당이 일어나게 된다.
처음엔 undefined로, 두번째는 초기화 때 값(1, 2, 3)으로.

0개의 댓글