🤓 모던 자바스크립트 Deep Dive 책을 통해서 자바스크립트를 공부중입니다.
변수는 하나의 값을 저장하기 위해 확보한 메모리 공간 자체 또는 그 메모리 공간을 식별하기 위해 붙인 이름을 말합니다. 메모리 공간에 저장된 값을 식별할 수 있는 고유한 이름을 변수명이라고 하며, 변수에 저장된 값을 변수 값이라고 합니다. 변수에 값을 저장하는 것은 할당(대입, 저장)이라고 하고, 변수에 저장된 값을 읽어 들이는 것을 참조라고 합니다.
메모리
컴퓨터는 CPU를 사용해 연산하고, 메모리를 사용해 데이터를 기억합니다. 메모리는 데이터를 저장할 수 있는 메모리 셀의 집합체입니다. 메모리 셀 하나의 크기는 1바이트(8비트)이며, 컴퓨터는 메모리 셀의 크기 단위로 데이터를 저장하거나 읽습니다.
각 셀은 고유의 메모리 주소를 갖습니다. 이 메모리 주소는 메모리 공간의 위치를 나타내며, 0부터 시작해서 메모리의 크기만큼 정수로 표현됩니다.
식별자는 어떤 값을 구별해서 식별할 수 있는 고유한 이름을 말합니다. 변수, 함수, 클래스 등의 이름은 모두 식별자 입니다. 식별자는 값이 아닌 메모리 주소를 기억하고 있습니다. 식별자로 값을 구별해서 식별한다는 것은 식별자가 기억하고 있는 메모리 주소를 통해 메모리 공간에 저장된 값에 접근할 수 있다는 의미입니다. 식별자는 선언에 의해 자바스크립트 엔진에 식별자의 존재를 알립니다.
자바스크립트 엔진은 변수 선언을 2단계에 걸쳐 수행합니다.
변수 선언은 값을 저장하기 위한 메모리 공간을 확보하고 변수 이름과 확보된 메모리 공간의 주소를 연결해서 값을 저장할 수 있게 준비하는 것을 말합니다. 변수를 사용하려면 반드시 선언이 필요하며, 변수를 선언할 때는 var
, let
, const
키워드를 사용합니다.
var 키워드의 단점
var키워드는 블록 레벨 스코프를 지원하지 않고 함수 레벨 스코프를 지원합니다. 이 때문에 의도치 않게 전역 변수가 선언되어 부작용이 발생하기도 합니다. ES6에서는 var 키워드의 단점을 보완하기위해 let, const 키워드가 도입되었습니다. ES6는 기본적으로 하위 호환성을 유지하면서 ES5의 기반 위에 새로운 기능을 추가한, ES5의 상위 집합입니다.
다음 코드는 var 키워드 뒤에 오는 변수 이름으로 새로운 변수를 선언할 것을 지시하는 키워드입니다.
//변수 선언(변수 선언문)
var score;
키워드
키워드는 자바스크립트 코드를 해석하고 실행하는 자바스크립트 엔진이 수행할 동작을 규정하는 일종의 명령어 입니다. 자바스크립트 엔진은 키워드를 만나면 자신이 수행해야 할 약속된 동작을 수행합니다.
위 변수 선언문은 변수를 선언한 이후 변수에 값을 할당하지 않았습니다. 하지만 변수 선언에 의해 확보된 메모리 공간은 비어있는 것이 아니라 자바스크립트 엔진에 의해 undefined라는 값이 할당되어 초기화 됩니다. 초기화란 변수가 선언된 이후 최초로 값을 할당하는 것을 말합니다.
undefined
undefined는 자바스크립트에서 제공하는 원시 타입의 값입니다.
- 선언 단계: 변수 이름을 등록해서 자바스크립트 엔진에 변수의 존재를 알립니다.
- 초기화 단계: 값을 저장하기 위한 메모리 공간을 확보하고 암묵적으로 undefined를 할당해 초기화 합니다.
변수 이름은 어디에 등록되는가?
모든 식별자(변수, 함수, 클래스 등)는 실행 컨텍스트에 등록됩니다. 실행 컨텍스트는 자바스크립트 엔진이 소스코드를 평가하고 실행하기 위해 필요환 환경을 제공하고 코드의 실행 결과를 실제로 관리하는 영역입니다. 자바스크립트 엔진은 실행 컨텍스트를 통해 식별자와 스코프를 관리합니다. 변수 이름과 변수 값은 실행 컨택스트 내에 key/value 형식인 객체로 등록되어 관리됩니다.
만약 초기화 단계를 거치지 않으면 확보된 메모리 공간에는 이전에 다른 애플리케이션이 사용했던 값이 남아있을 수 있습니다. 이러한 값을 garbage value라고 합니다. 자바스크립트의 var 키워드는 암묵적으로 초기화를 수행하므로 이러한 값을 참조하게 되는 위험으로부터 안전합니다.
선언하지 않은 식별자에 접근하면 ReferenceError(참조 에러)가 발생합니다.
console.log(score); // undefined
var score; // 변수 선언문
위 코드에서는 변수 선언문보다 변수를 참조하는 코드가 앞에 있습니다. 자바스크립트는 인터프리터에 의해 한 줄씩 순차적으로 실행되므로 console.log(score);
가 먼저 실행되고 순차적으로 다음 줄에 있는 코드가 실행됩니다. console.log(score)
가 실행되는 시점에는 아직 score
변수가 선언되지 않았으므로 참조 에러가 발생할 것처럼 보입니다. 하지만 참조 에러 대신 undefined
가 출력됩니다.
변수 선언이 소스코드가 한 줄씩 순차적으로 실행되는 시점, 즉 런타임이 아니라 그 이전 단계에서 먼저 실행되기 때문입니다. 자바스크립트 엔진은 변수 선언이 소스코드의 어디에 있든 다른 코드보다 먼저 실행합니다. 이처럼 변수 선언문이 코드의 선두로 끌어 올려진 것처럼 동작하는 것을 변수 호이스팅이라고 합니다.
호이스팅 (hoisting)
모든 식별자(변수, 함수, 클래스)는 호이스팅됩니다. 모든 선언문은 런타임 이전 단계에 먼저 실행되기 때문입니다.
변수에 값을 할당할 때는 할당 연산자 =
을 사용하여 우변의 값을 좌변의 변수에 할당합니다.
var score; // 변수 선언
score = 80; // 값의 할당
var score = 80; // 변수 선언과 값의 할당
변수 선언과 값의 할당을 2개의 문으로 나누어 표현한 코드와 하나의 문으로 단축 표현한 코드는 동일하게 작동합니다. 즉, 자바스크립트 엔진은 변수 선언과 값의 할당을 하나의 문으로 단축 표현해도 2개의 문으로 나누어 각각 나누어 실행하는 것입니다.
이 때, 변수 선언은 소스코드가 순차적으로 실행되는 시점인 런타임 이전에 먼저 실행되지만 값의 할당은 소스코드가 순차적으로 실행되는 시점인 런타임에 실행된다는 점을 주의해야 합니다.
console.log(score); //undefined
var score; //변수 선언
score = 80;//값의 할당
console.log(score); //80
score
변수에 값을 할당하는 시점에는 이미 변수 선언이 완료된 상태이며, 이미 undefined
로 초기화 되어있습니다. 따라서 score
변수에 값을 할당하면 score
변수의 값은 undefined
에서 새롭게 할당한 숫자 80
으로 재할당됩니다.
변수에 값을 할당할 때는 이전 값 undefined
가 저장되어 있던 메모리 공간을 지우고 그 메모리 공간에 80
을 새롭게 저장하는 것이 아니라, 새로운 메모리 공간을 확보하고 그곳에 80
을 저장합니다.
재할당이란 이미 값이 할당되어 있는 변수에 새로운 값을 또다시 할당하는 것을 말합니다.
var score = 80;//변수 선언과 값의 할당
score = 90;//값의 재할당
변수에 값을 재할당하면 score
변수의 값은 이전 값 80
에서 재할당 값 90
으로 변경됩니다. 처음 값을 할당했을 때와 마찬가지로 이전 값 80
이 저장되어 있던 메모리 공간을 지우고 그 메모리 공간에 재할당 값 90
을 새롭게 저장하는 것이 아니라 새로운 메모리 공간을 확보하고 그 메모리 공간에 숫자 값 90
을 저장합니다.
현재 score
변수의 이전 값인 undefined
와 80
은 어떤 변수도 값으로 갖고 있지 않습니다. 이런 불필요한 값들은 가비지 컬렉터에 의해 메모리에서 자동으로 해제됩니다.
가비지 콜렉터(garbage collector)
가비지 콜렉터는 애플리케이션이 할당한 메모리 공간을 주기적으로 검사하여 더 이상 사용되지 않는 메모리를 해제하는 기능을 말합니다. 즉, 어떤 식별자도 참조하지 않는 메모리 공간을 해제합니다. 이를 통해 메모리 누수를 방지합니다.
위 규칙을 참고하여 아래와 같은 식별자는 변수 이름으로 사용할 수 있습니다.
변수는 쉼표(,)로 구분해 하나의 문에서 여러개의 변수를 한 번에 선언할 수 있습니다. 하지만 가독성이 나빠지므로 권장하지 않습니다.
var person, $elem, _name, first_name, vall;
아래 식별자는 명명 규칙에 위배되므로 변수 이름으로 사용할 수 없습니다.
var first-name; // SyntaxError: Unexpected token -
var 1st; //SyntaxError: Invalid or unexpected token
var this; //SyntaxError: Unexpected token this
자바스크립트는 대소문자를 구별하므로 다음 변수는 각각 별개의 변수입니다.
var firstname;
var firstName;
var FIRSTNAME;
변수 이름은 변수의 존재 목적을 쉽게 이해할 수 있도록 의미를 명확히 표현해야 합니다. 좋은 변수 이름은 코드의 가독성을 높이기 때문입니다.
네이밍 컨벤션은 영어 단어들로 구성된 식별자를 만들 때, 가독성 좋게 단어를 한눈에 구분하기 위해 규정한 것입니다. 아래와 같은 4가지 유형의 네이밍 컨벤션이 자주 사용됩니다.
//카멜 케이스(camelCase)
var firstName;
//스네이크 케이스(snake_case)
var first_name;
//파스칼 케이스(PascalCase)
var FirstName;
자바스크립트에서는 일반적으로 변수나 함수의 이름에는 카멜 케이스를 사용하고, 생성자 함수나 클래스의 이름에는 파스칼 케이스를 사용합니다. 또한 ECMAScript사양에 정의되어 있는 객체와 함수들도 카멜 케이스와 파스칼 케이스를 사용하고 있습니다.
값은 표현식이 평가(식을 해석해서 값을 생성하거나 참조하는 것)되어 생성된 결과를 말합니다.
모든 값은 데이터 타입을 가지며, 메모리에 2진수로 저장됩니다. 메모리에 저장된 값은 데이터 타입에 따라 다르게 해석될 수 있습니다.
메모리에 저장된 값 0100 0001
을 숫자로 해석하면 65지만 문자로 해석하면 A
입니다.
//변수에는 10 + 20이 평가되어 생성된 숫자 값 30이 할당됩니다.
var sum = 10 + 20;
변수 이름 sum이 기억하는 메모리 공간에 저장되는 것은 10 + 20이 아니라, 값 30입니다. 따라서 10 + 20은 할당 이전에 평가되어 값을 생성해야 합니다.
값은 다양한 방법으로 생성할 수 있는데, 가장 기본적인 방법은 리터럴을 사용하는 것입니다.
리터럴은 사람이 이해할 수 있는 문자(아라비아 숫자, 알파멧, 한글 등) 또는 약속된 기호('', "", ., [], {}, // 등)를 사용해 값을 생성하는 표기법을 말합니다. 자바스크립트 엔진은 코드가 실행되는 시점인 런타임에 리터럴을 평가해 값을 생성합니다.
표현식은 값으로 평가될 수 있는 문입니다. 표현식은 리터럴, 식별자(변수, 함수 등의 이름), 연산자, 함수 호출 등의 조합으로 이루어질 수 있습니다. 표현식이 평가되면 새로운 값을 생성하거나 기존 값을 참조합니다.
var score = 100;
위 예제의 100은 리터럴입니다. 리터럴 100은 자바스크립트 엔진에 의해 평가되어 값을 생성하므로, 리터럴은 그 자체로 표현식입니다.
var score = 50 + 50;
50 + 50은 리터럴과 연산자로 이루어져 있지만, 평가되어 숫자 값 100을 생성하므로 표현식입니다.
score; // 100
식별자 참조는 값을 생성하지는 않지만, 변수 값으로 평가되므로 표현식입니다.
표현식과 표현식이 평가된 값은 동등한 관계(동치)입니다. 자바스크립트의 표현식 1 + 2는 평가되어 3을 생성하므로, 표현식 1 +2와 값 3은 동등한 관계입니다. 따라서 표현식은 값처럼 사용할 수 있습니다. 예를 들어 산술 연산자 +
의 좌항과 우항에는 숫자 값이 위치해야 합니다. 이때 숫자 값으로 평가될 수 있는 표현식이라면 숫자 값 대신 사용할 수 있습니다.
var x = 1 + 2;
//식별자 표현식 x는 3으로 평가됩니다.
x + 3; // 6
문은 프로그램을 구성하는 기본 단위이자 최소 실행 단위입니다. 문의 집합으로 이루어진 것이 프로그램이며, 문을 작성하고 순서에 맞게 나열한 것이 프로그래밍입니다. 문은 컴퓨터에 내리는 명령으로 명령문이라고도 부릅니다.
문은 여러 토큰으로 구성됩니다. 토큰이란 문법적인 의미를 가지며, 문법적으로 더 이상 나눌 수 없는 코드의 기본 요소를 말합니다.
문은 선언문, 할당문, 조건문, 반복문 등으로 구분할 수 있습니다.
//변수 선언문
var x;
//할당문
x = 5;
//함수 선언문
function foo () {}
//조건문
if (x > 1) { console.log(x); }
//반복문
for (let i=0; i < 2; i++) { console.log(i); }
세미콜론(;)은 문의 종료를 나타냅니다. 즉, 자바스크립트 엔진은 세미콜론으로 문이 종료한 위치를 파악하고 순차적으로 하나씩 문을 실행합니다. 따라서 문을 끝낼 때는 세미콜론을 붙여야 합니다. 하지만 if
문, for
문, 함수 등의 코드 블록 뒤에는 세미콜론을 붙이지 않습니다. 이러한 코드 블록은 언제나 문의 종료를 의미하는 자체 종결성을 갖기 때문입니다.
변수 선언문은 값으로 평가될 수 없으므로 표현식이 아닙니다. 반면에 1
, 2
, 1 + 2
, x = 1 + 2
는 모두 표현식입니다. 그 중 x = 1 + 2
는 표현식이면서 완전한 문이기도 합니다.
문에는 표현식인 문과 표현식이 아닌 문이 있습니다. 표현식인 문은 값으로 평가될 수 있는 문이며, 표현식이 아닌 문은 값으로 평가될 수 없는 문을 말합니다.
표현식인 문과 표현식이 아닌 문을 구별하는 가장 간단하고 명료한 방법은 변수에 할당해보는 것입니다.
표현식이 아닌 문은 값처럼 사용할 수 없습니다.
var foo = var x; //SyntaxError: Unexpected token var
반면에 할당문 x = 100
은 그 자체가 표현식입니다. 따라서 값처럼 사용할 수 있습니다.
var foo = x = 100;
console.log(foo) //100
완료값
개발자 도구에서 표현식이 아닌 문을 실행하면 undefined를 출력하는데, 이를 완료값이라고 합니다. 완료값은 표현식의 평가 결과가 아니기 때문에 다른 값과 같이 변수에 할당할 수 없고, 참조할 수도 없습니다.var foo = 10; //undefined if (true) {} //undefined
반면에 개발자 도구에서 표현식인 문을 실행하면 평가된 값을 반환합니다.
var num = 10; //undefined 100 + num; //110 num = 100; //100