1 + 2 // 3
1 + 2
라는 연산의 결과는 3
이 된다.
즉, 1 + 2
는 결과로 3
을 반환한다고 할 수 있다.
이렇게 결과적으로 연산을 마치면 하나의 값이 되고 그 값을 반환하는 식을 expression, 표현식이라고 부르고 표현식 -> 값으로 계산하는 것을 evaluation, 평가라고 한다.
1, 2
는 operand, 피연산자, +
는 operator, 연산자라고 한다.
그러나 위 코드는 연산 결과를 어디에도 저장하고 있지 않다.
연산 결과를 재사용하기 위해서는 어딘가에 저장해야 한다.
프로그래밍에서 연산 결과, 값 등을 저장하기 위한 공간을 변수라고 한다.
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 + 2
는 3
으로 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 상의 주소)만 놓고 생각해보자.
x
에 3
을 할당하라는 명령을 내리면 사용 가능한 메모리 공간을 찾아서 주소값을 알려준다.(메모리 할당/관리는 운영체제의 역할)
운영체제가 배정한 메모리 주소가 0xFF7F5BFF
이라면 x
에 값을 할당하는 것은 0xFF7F5BFF
에 값을 할당하라는 의미인 것이다.
즉, x
는 0xFF7F5BFF
라는 주소값을, 인간이 알아보기 편하게 alias, 별칭을 지정해준 것과 같으며, [이름 - 전화번호]의 관계와 비슷하다.
var a = 1;
let b = 2;
const c = 3;
변수를 선언하는 방법은 3가지가 있다.
위에서 본 let
과 var
, 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
을 갑작스럽게 없애버린다면 그 많은 예전 코드들이 다 박살이 날 수 있기 때문이다.
그래서 var
을 JS에서 없애지 못하고 유지는 하고 있지만, 새로 만드는 코드는 let
사용이 권장되고 있다.
지금까지는 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
라면 값이 초기화가 안되었구나 라고 보다 빠르게 파악할 수 있을 것이다.
먼저 아래 코드를 살펴보자
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
)으로.