변수에 값을 저장하는 것을 할당(assignment, 대입, 저장)이라 하며 변수에 저장된 값을 읽어 들이는 것을 참조(reference)라 한다. 그리고 변수명을 자바스크립트 엔진에 알리는 것을 선언(declaration)이라 한다.
변수 선언 방식
변수의 선언은 var, const, let 키워드로 할 수 있으며 자바스크립트에서 변수 선언은 선언 → 초기화 단계를 거쳐 수행된다.
- 선언 단계: 변수명을 등록하여 자바스크립트 엔진에 변수의 존재를 알린다.
- 초기화 단계: 값을 저장하기 위한 메모리 공간을 확보하고 암묵적으로 undefined를 할당해 초기화한다.
var name;
console.log(name); // output: undefined
name: 'kjm';
var 키워드를 이용한 변수 선언은 선언 단계와 초기화 단계가 동시에 진행되어, kmj에 암묵적으로 undefined를 할당해 초기화한다.
반대로, console을 먼저 찍어도 반환 값이 undefined로 나온다. var로 선언된 변수와는 달리 let으로 선언된 변수를 선언문 이전에 참조하면 참조 에러(ReferenceError)가 발생한다.
console.log(kjm); // output: undefined
var kjm;
console.log(kkk); // Error: Uncaught ReferenceError: bar is not defined
let kkk;
이는 변수 선언이 런타임에서 되는 것이 아니라, 그 이전 단계에서 먼저 실행되기 때문이다. 자바스크립트 엔진은 소스코드를 한 줄씩 순차적으로 실행하기에 앞서, 변수 선언을 포함한 모든 선언문(ex. 변수 선언문, 함수 선언문 등)을 찾아내 먼저 실행한다. 즉, 변수 선언이 어디에 있든 상관없이 다른 코드보다 먼저 실행되는 특징을 호이스팅(hoisting)이라 한다.
변수 선언 뿐만 아니라, var, let, const, function, function*, class 키워드를 사용해 선언한 모든 식별자(변수, 함수, 클래스 등)는 호이스팅이 된다.
var는 한번 선언된 변수를 다시 선언할 수 있다. 코드량이 많아진다면 어디에서 어떻게 사용될지도 파악하기 힘들뿐더러 값이 바뀔 우려가 있다.
var name = 'Mike';
console.log(name);
var name = 'Jaus';
console.log(name);
let의 경우는 에러가 발생한다. 변수 재선언이 되지 않는다.(const도 마찬가지)
let name = 'Mike'
console.log(name); // Mike
let name = 'javascript';
console.log(Jaus);
// Uncaught SyntaxError: Identifier 'name' has already been declared
name = 'Jeong';
console.log(name); // Jeong
let은 값의 재할당이 가능하다.
const는 변수 재선언, 변수 재할당 모두 불가능하다.
const name = 'Mike'
console.log(name); // Mike
const name = 'javascript';
console.log(Jaus);
// Uncaught SyntaxError: Identifier 'name' has already been declared
name = 'Jeong';
console.log(name); // Uncaught TypeError: Assignment to constant variable.
JavaScript의 변수는 선언이 아닌 할당에 의해 타입이 결정(타입 추론: type inference)된다.
재할당에 의해 변수의 타입은 언제든지 동적으로 변할 수 있다.
유연성(flexibility)은 높지만 신뢰성(reliability)은 떨어진다.
동적 타입 언어인 JavaScript에서 변수를 사용할 때 주의 사항 4가지를 살펴보자.
- 변수는 꼭 필요한 경우에 한해 제한적으로 사용한다. 변수 값은 재할당에 의해 언제든지 변경될 수 있고, 타입을 잘못 예측해 오류가 발생할 가능성이 크다.
- 변수의 유효 범위(스코프)는 최대한 좁게 만들어 변수의 부작용을 억제해야 한다.
- 전역 범수는 최대한 사용하지 않도록 한다. 어디서든지 참조/변경 가능한 전역 변수는 의도치 않게 값이 변경될 가능성이 높고 다른 코드에 영향을 줄 수 있다. 전역 변수는 프로그램의 복잡성을 높이고 처리 흐름을 추적하기 어렵게 만들고, 오류가 발생할 경우 오류의 원인을 특정하기 어렵게 만든다.
- 변수보다는 상수를 사용해 값의 변경을 억제한다.
- 변수 이름은 변수의 목적이나 의미를 파악할 수 있도록 네이밍한다.
호이스팅
스코프 내부 어디서든 변수 선언은 최상위에 선언된 것처럼 행동하는 것을 호이스팅(hoisting)이라고 한다.
var 선언문이나 function 선언문 등을 해당 스코프의 선두로 옮긴 것처럼 동작하는 특성을 말한다.
자바스크립트는 ES6에서 도입된 let, const를 포함하여 모든 선언(var, let, const, function, function*, class)을 호이스팅한다.
TDZ(Temporal Dead Zone): 변수의 선언과 변수의 초기화 사이의 변수에 접근할 수 없는 지점
즉, 초기화되지 않은 변수가 있는 곳을 Temporal Dead Zone이라고 한다.
TDZ에 영향을 받는 구문의 종류
- const 변수
- let 변수
- class 구문
- constructor() 내부의 super()
- 기본 함수 매개변수
const와 let은 할당을 하기 전에는 사용할 수 없다. 이는 코드를 예측 가능하게 하고 잠재적인 버그를 줄일 수 있다.
let age = 30;
fuction showAge() {
console.log(age);
}
showAge(); // 30
위 코드는 문제가 없고 아래 코드는 문제가 발생한다. 호이스팅은 스코프 단위로 발생한다.
즉, 현재 코드의 전역, showAge함수의 지역(함수 내부)으로 나뉜다.
let age = 30;
fuction showAge() {
console.log(age); // TDZ
let age = 20;
}
showAge(); // Error
함수 내부에 let age = 20;이 호이스팅을 일으킨다. 만약 호이스팅이 되지 않았다면 함수 바깥에서 선언한 age = 30이 정상적으로 찍혀야 한다.
변수의 생성과정
위에서 언급했지만 변수의 생성과정을 확실히 알아가자.
변수는 선언 단계, 초기화 단계, 할당 단계로 3단계의 생성과정을 거친다.
var: 1. 선언 및 초기화 단계: 선언과 초기화가 동시에 된다.
2. 할당 단계
let: 1. 선언단계
2. 초기화 단계
3. 할당 단계
호이스팅 되면서 선언 단계가 이루어지지만 초기화 단계는 실제 코드가 도달했을 때 되기 때문에 레퍼런스 에러가 발생한다.
const:1. 선언 + 초기화 + 할당
let과 var는 선언만 해두고 나중에 할당하는 것을 허용한다. let과 var는 값을 바꿀 수 있기 때문에 어찌 보면 당연한 것이다.
let name;
name = 'Mike';
var age;
age = 30;
const gender; // Unacught SyntaxError: Missing initializer in const declaration
gender = 'male';
let과 var는 괜찮지만 const부분에서 에러가 발생한다. 왜냐하면 선언하면서 바로 할당해주지 않았기 때문이다.
스코프(scope): 함수가 실행될 때, 함수 내에서 변수에 대한 접근이 어떻게 되는지를 나타내는 용어.
(함수의 실행 context내에서 변수 환경이 무엇인지) 스코프는 함수를 기반으로 한 용어이다.
변수에 접근할 수 있는 범위
컨텍스트(context)
this 키워드 값이 무엇인지를 나타내는 용어이다. 현재 실행 컨텍스트 내에서 어떤 객체를 참조하고 있는지를 의미한다. 컨텍스트는 객체를 기반으로 한 용어이다.
- 스코프 : 함수가 선언되면 무조건 스코프가 생성된다.
- 컨텍스트: 함수가 속해있는 객체가 무엇인지 의미한다. (만약 함수가 글로벌 스코프에서 선언되었다면, 이때의 컨텍스트는 global(window)이다.)
var: 함수 스코프(function-scoped)
let, const: 블록 스코드(block-scoped)
# 함수 스코프
- 자바스크립트는 기본적으로 함수 스코프를 따르는 언어.
- 함수 스코프를 따른다? => 새로운 함수가 생성될 때마다 새로운 스코프가 발생한다.
- 함수 몸체에 선언한 변수는 해당 함수 안에서만 접근할 수 있음. - 함수 스코프 -> 지역 스코프
- 유일하게 벗어날 수 없는 스코프가 함수이다.
if(5 > 4) {
var secret = '12345';
}
secret; // '12345'
위 코드에서는 함수가 선언되어 있지 않기 때문에 새로운 환경, 새로운 스코프가 형성되지 않는다.
스코프가 형성되지 않으므로 동일한 실행 context내에서 존재하는 것이다. 그렇기 때문에 어디에서나 secret 변수에 대한 접근이 가능하다.
function a() {
var secret = '12345';
}
secret; // ReferenceError
반면 이 경우는 함수 생성과 동시에 a함수에 대한 새로운 실행 context가 생성되고, 이 실행 context내부에 존재하는 변수 환경(variable environment)에 secret변수가 저장된다.
따라서 함수 외부에서 secret에 접근하려고 할 경우 스코프가 달라 해당 변수에 접근이 불가능하다.
함수 외부는 global scope이고, 함수 내부는 a함수의 scope인데 부모 스코프는 자식 스코프에서 간섭할 수 없기 때문에 접근 불가능하다.
#블록 스코프
- 블록 스코프는 말 그래도 블록 {}이 생성될 때마다 새로운 스코프가 형성되는 것을 의미
- 원래 자바스크립트는 함수 스코프를 따르지만, let과 const 키워드의 등장으로 블록 스코프를 형성하는 것도 가능해졌다.
- 함수, if문, for문, while문, try/catch문 등
function loop() {
for(var i = 0; i < 5; i++) {
console.log(i);
}
console.log('final',i);
}
loop();
/*
0
1
2
3
4
final 5
*/
이 코드를 보면, for문 안에서 변수 i를 var키워드로 초기화해줬다.
이 경우, 블록 스코프가 아닌 함수 스코프를 따르게 되는데, for문 안쪽과 바깥쪽에서 모두 변수 i에 접근해서 console.log하고 있다. 이때는 어차피 loop라는 함수 스코프 안에 둘 모두 존재하기 때문에 for문 안이든 밖이든 문제없이 i에 접근해서 값을 출력한다.
function loop() {
for (let i = 0; i < 5; i++) {
console.log(i);
}
console.log('final', i);
}
loop(); /* ReferenceError: i is not defined */
반면 for문 내에서 i를 let으로 선언한 경우, 변수 i는 for문 내에서만 종속되며, 외부에서는 i에 접근할 수가 없다.
스코프가 다르기 때문이다. 코드 블럭 내부에서 선언한 변수는 지역 변수인 것이다.
function add() {
// Block-level Scope
}
if() {
// Block-level Scope
}
for(let i = 0; i<10; i++) {
// Block-level Scope
}
정리하자면,
let과 const의 유효 범위는 블록{} 레벨의 유효 범위를 가진다.
var, let은 재사용 가능하다. const는 재사용 불가능하다.
var는 함수 레벨의 유효 범위를 가진다. 사용 권장 X
let과 const를 이용하는 것이 관리에 효과적이다.
'개발자의 공부 > JS' 카테고리의 다른 글
JS 코드 개선+Optional chaining+null 병합 연산자 (1) | 2022.09.10 |
---|---|
[JS]함수 간단 정리(내용 업데이트 + 생성기 함수) (1) | 2022.09.05 |
[JS]LocalStorage와 SessionStorage(내용 업데이트) (0) | 2022.09.05 |
[JS문법]for...of와 for...in (0) | 2022.09.03 |
코드 프로그래머스 문제 풀이4 (0) | 2022.08.31 |