- 함수 스코프 : 이와 관련 설명은 이전 포스팅(스코프)
- 값을 재할당 가능
- 초기화하지 않아도 됨
- 이미 선언된 변수에 재선언 가능
var 는 기본적으로 함수 스코프를 가진다. 그리고 초기화 하지 않고 사용할 수 있으며 var 로 선언한 변수명을 재사용 할 수 있다.(재할당 가능) 아래는 이에 대한 간략한 예시이다.
var variable1;
variable1 ="(var) 초기화된 값1";
console.log(variable1);
function printV(){
variable1 ="(var) 초기화된 값2";
console.log(variable1);
}
var variable1 = "(var) 초기화된 값3";
console.log(variable1);
printV();
영어 동사 "hoist"의 사전적 의미는 아래와 같다.
자바스크립트에서 호이스팅이란 변수와 함수 선언(var, let, const, function 등)이 스코프의 최상단으로 끌어올려지는 동작 을 말한다. 스코프의 최상단으로 끌어올려진다는 것은 무슨 의미인가? 또 이러한 호이스팅은 왜 필요한가?
호이스팅은 자바스크립트 엔진이 동작하는 과정 중 하나로, 자바스크립트 엔진은 코드를 실행하기 전에 두 번의 단계를 거친다.
호이스팅은 컴파일 단계에서 변수와 함수 선언을 메모리에 등록하는 과정으로, 변수나 함수 선언을해당 스코프의 최상단으로 끌어올림으로써 변수와 함수를 선언하기 전에 사용할 수 있는 효과를 갖게 된다. 하지만 호이스팅은 실제 코드의 순서를 변경하는 것이 아니라 자바스크립트 엔진이 코드를 해석하는 과정에서 수행되는 것이다.
greet();
function greet() {
console.log("Hello!");
}
greet(); // TypeError: greet is not a function
var greet = function() {
console.log("Hello!");
};
var x = 1;
if (x === 1) {
var y = 2;
} else {
var y = 3;
}
console.log(y); // 2
호이스팅은 적절하게 활용하면 유용할 수 있지만, 코드의 가독성을 저해할 수 있으며 예상치 못한 동작을 초래할 수 있다. 아래의 var의 호이스팅 예시를 통해 구체적으로 알아보자.
console.log(myVariable); // undefined
var myVariable = "Hello, world!";
console.log(myVariable); // "Hello, world!"
위 코드의 첫줄에서 myVariable 은 선언된 적이 없다. 하지만 콘솔에서는 에러 메세지가 아니라 "undefined"가 출력된다. 이는 호이스팅 때문으로, 호이스팅에 의해 선언부가 스코프의 최상단으로 끌어올려진다. 따라서 위의 코드를 자바스크립트 엔진이 호이스팅 매커니즘을 통해 아래와 같이 해석한다.
var myVariable;
console.log(myVariable); // undefined
var myVariable = "Hello, world!";
console.log(myVariable); // "Hello, world!"
위에서 언급했지만, 이는 자바스크립트 엔진이 컴파일 과정에서 개발자의 코드를 "해석"하면서 변수나 함수 선언을 최상단으로 끌어올리는 매커니즘을 작동시키기 때문이다. 여기서 문제가 발생한다.
console.log(x); // undefined
var x = 10;
위 예시에서 변수 x를 선언하기 전에 출력하려고 했는데, 출력 결과는 undefined이다. 이는 var 변수의 호이스팅으로 인해 발생하는 현상이다. var x 선언이 코드 상단으로 이동하지만 초기화는 원래 위치에 그대로 남아있게 된다. 따라서 console.log(x)가 실행될 때 변수 x는 선언은 되었지만 초기화되지 않은 상태이기 때문에 undefined가 출력된다.
이러한 동작은 개발자의 의도와 다르게 동작할 수 있으며, 디버깅이 어려워질 수 있다. 물론 아주 짧은 코드를 짤때는 이런 실수를 안 할 수 있겠지만, 프로젝트가 커질 경우에는 문제가 발생할 수 있다. 따라서 변수를 사용하기 전에 선언하고 초기화하는 것이 가독성과 예측 가능성을 높이는데 도움이 된다.
호이스팅은 변수가 중복 선언되는 경우에도 예상치 못한 동작을 초래할 수 있다.
var helloMsg = "Hello World";
if (true) {
var helloMsg = "Hello Javascript";
}
console.log(helloMsg) // "Hello Javascript"
만약 개발자가 의도를 가지고 변수를 재정의했다면 문제가 되지 않는다. 하지만 규모가 큰 프로젝트에서 기존의 정의된 변수가 있다는 사실을 인지하지 못한 채 위와 같은 코드를 작성한다면, 의도치 않게 중복 선언된 변수가 덮어쓰여지는 결과를 초래할 수 있으며, 디버깅과 유지 보수를 어렵게 만들 수 있다.
변수의 위치 예측 어려움: var는 선언과 초기화가 분리되어 있으므로, 변수 선언이 실제 코드의 위치와 다르게 끌어올려질 수 있으며, 코드의 가독성과 이해가 어려워질 수 있다. 변수 선언이 실제 사용보다 이후에 위치하면 의도하지 않은 결과를 초래할 수 있다.
스코프 오염 문제: var로 선언된 변수는 함수 스코프를 갖기 때문에 함수 내에서 선언된 변수는 해당 함수 스코프 전체에서 접근 가능하다. 이로 인해 의도하지 않은 변수의 공유나 값의 변경이 발생할 수 있다. 특히 반복문 내에서 var로 선언된 변수를 사용하면 예상치 못한 동작이 발생할 수 있다.
재선언 허용: var는 동일한 변수명을 다시 선언할 수 있다. 이는 의도하지 않은 변수 충돌을 유발할 수 있다. 이러한 문제는 코드의 복잡성을 증가시키고 디버깅을 어렵게 만들 수 있다.
위와 같은 단점으로 인해 ES6부터 let과 const가 도입되었다. let과 const는 블록 스코프를 갖고 호이스팅 동작도 var보다 예측 가능하게 변경되었다.
- 블록 스코프 : 이와 관련 설명은 이전 포스팅(스코프)
- 재할당 가능
- 초기화 하지 않아도 됨
- 이미 선언된 변수에 재선언 불가능
아래 코드와 결과들에서 볼 수 있듯이, let도 값을 재할당할 수 있고, 초기화하지 않고 사용할 수도 있다. 하지만 var와는 달리 let은 이미 선언한 동일한 변수명을 재선언(중복선언)할 수 없다.
let variable2;
variable2 = "(let) 초기화된 값1";
console.log(variable2);
variable2 = "(let) 초기화된 값2";
console.log(variable2);
function printL(){
variable2 ="(let) 초기화된 값3";
console.log(variable2);
}
// let variable2 = "(let) 초기화된 값3"; // SyntaxError: Identifier 'variable2' has already been declared
//console.log(variable2);
printL();
위 코드에서 // let variable2 부분의 주석을 해제하면 에러가 발생한다.
let은 var와는 달리 변수를 선언하기 전에 출력할 수 없다.
console.log(x); // ReferenceError: x is not defined
let x = 10;
위에서 언급햇듯이 중복선언도 불가능하다.
let x = 10;
let x = 20; // SyntaxError: Identifier 'x' has already been declared
하지만 아래의 예시는 에러가 발생하지 않는다.
let helloMsg = "Hello World";
if (true) {
let helloMsg = "Hello Javascript";
console.log(helloMsg) // "Hello Javascript"
}
console.log(helloMsg) // "Hello World"
그 이유는 두 변수의 스코프가 다르게 작동하기 때문이다. let 키워드는 기본적으로 블록 스코프(범위)를 가지는데, 위 코드에서는 같은 이름의 변수가 각각 전역 스코프와 블록 스코프를 가지기 때문에 각기 다른 변수로 취급된다.
다음은 let의 호이스팅이 적절히 사용된 예시이다. 이 예시는 let 변수의 호이스팅을 활용하여 변수의 범위를 제한하면서도 반복분 내에서 활용하는 것을 보여준다.
function printNumbers() {
for (let i = 0; i < 5; i++) {
setTimeout(function() {
console.log(i);
}, 1000);
}
}
printNumbers();
위 코드에서는 반복문 안에서 setTimeout 함수를 사용하여 1초 후에 i 값을 출력하도록 설정하였다. let 키워드를 사용하여 변수 i를 선언하면 매번 반복될 때마다 새로운 블록 스코프가 생성되며, 각 반복마다 독립적인 변수 i가 생성된다. 이로 인해 setTimeout의 콜백 함수는 클로저로써 자신이 선언된 스코프인 반복문의 블록 스코프에 접근할 수 있다. 따라서 setTimeout이 실행되었을 때 해당 반복문의 인덱스 값을 제대로 유지하게 된다.
위 코드를 실행하면 0부터 4까지 순서대로 1초 간격으로 출력되는 것을 확인할 수 있다. 이는 let 변수의 호이스팅을 적절하게 활용하여 반복문에서 인덱스 값을 제대로 유지한 결과이다.
클로져(closure)? 클로저는 함수 내부에서 정의된 함수가 외부 함수의 변수에 접근할 수 있는 것을 의미한다..
함수가 종료되더라도 해당 함수가 생성될 때의 환경을 기억하고, 그 이후에도 접근할 수 있게 해준다.function outerFunction() { var outerVariable = 'Hello'; function innerFunction() { console.log(outerVariable); } return innerFunction; } var closure = outerFunction(); closure(); // 출력: Hello
outerFunction 내부에서 innerFunction이 정의되고 반환된다. 반환된 innerFunction은 외부 함수인 outerFunction의 변수인 outerVariable에 접근할 수 있다. 이는 클로저의 특성으로 인해 가능한 것...
바로 위의 let 호이스팅 예시에서도 마찬가지로, setTimeout의 콜백 함수는 자신의 외부에 있는 변수 i에 접근할 수 있다.
- 클로져에 대해 더 자세히 알고 싶으면 : https://developer.mozilla.org/ko/docs/Web/JavaScript/Closures
- 재할당이 불가능하며, 선언과 동시에 초기화해야 한다. const로 선언된 변수는 블록 스코프(이전 포스팅: 스코프)를 갖는다.
- 객체나 배열과 같은 참조 타입의 const 변수의 경우, 변수 자체는 변경할 수 없지만 해당 객체 또는 배열의 내부 속성이나 요소는 변경할 수 있다.
//const pi; //SyntaxError: Missing initializer in const declaration
const pi= 3.141592;
console.log(pi); 3.141592
//pi = 4.2222; // TypeError: Assignment to constant variable.
위 코드에서 볼 수 있듯이 const는 선언만 하고 초기화하지 않을 경우 에러가 발생한다. 또한 이미 선언된 변수 pi에 재할당이 불가능하다. const 변수는 한 번 할당된 값은 변경할 수 없다.
하지만 const 변수에 객체를 할당한 후, 객체의 속성 값을 변경하는 것은 가능하다.
const person = {
name: 'John',
age: 30
};
console.log(person); // 출력: { name: 'John', age: 30 }
person.name = 'Jane';
console.log(person); // 출력: { name: 'Jane', age: 30 }
위 코드에서 const 변수인 person은 객체 자체를 변경할 수는 없다. person 객체를 아래와 같이 변경할 수는 없다는 말이다.
person = { name: 'Jane',
age: 30,
addr: "New York"
} // TypeError: Assignment to constant variable.
하지만 person 객체의 name 속성 값을 'Jane'으로 변경할 수 있다.
const 변수의 호이스팅은 선언 단계까지만 호이스팅되고, 초기화는 호이스팅되지 않는다. 즉, const 변수는 선언 이전에 접근하면 "ReferenceError"가 발생한다. const 변수는 반드시 선언과 동시에 초기화되어야 한다.
console.log(x); // ReferenceError: x is not defined
const x = 10;
재할당이 허용되지 않는다.
const x = 10;
x = 20; // TypeError: Assignment to constant variable.
function calculateArea(radius) {
if (radius > 0) {
const pi = 3.14159;
const area = pi * radius * radius;
console.log(area);
} else {
console.log("반지름은 양수여야 합니다.");
}
}
calculateArea(5); // 출력: 78.53975
calculateArea(-2); // 출력: 반지름은 양수여야 합니다.
위의 코드는 원의 넓이를 계산해보는 코드이다. 위에서 const로 선언된 pi와 area는 블록 내에서만 유효한 스코프를 갖는다. 또한 호이스팅이 발생할 때 문제가 발생하지 않는다.