FrontEnd Develop/JavaScript DeepDive

JS THEME #4. 기본 문법 (상)+ Statement 때문에 설명한 호이스팅과 스코프

Frisbeen 2025. 2. 1. 18:07

변수

변수(Variable)는 값(value)을 저장(할당)하고 그 저장된 값을 참조하기 위해 사용한다. 한번 쓰고 버리는 값이 아닌 유지(캐싱)할 필요가 있는 값은 변수에 담아 사용한다. 또한 변수 이름을 통해 값의 의미를 명확히 할 수 있어 코드의 가독성이 좋아진다.

변수는 위치(주소)를 기억하는 저장소이다. 위치란 메모리 상의 주소(address)를 의미한다. 즉, 변수란 메모리 주소(Memory address)에 접근하기 위해 사람이 이해할 수 있는 언어로 지정한 식별자(identifier)이다.

변수를 선언할 때 var 키워드를 사용한다. 할당 연산자 =는 변수에 값을 할당하기 위해 사용한다.

아래의 예에서 x는 변수로 선언되었고 변수 x에는 정수값 6이 할당되었다.

var x; // 변수의 선언
x = 6; // 정수값의 할당

용어 의미

데이터 타입(Data Type) 프로그래밍 언어에서 사용할 수 있는 값의 종류
변수(Variable) 값이 저장된 메모리 공간의 주소를 가리키는 식별자(identifier)
리터럴(literal) 소스코드 안에서 직접 만들어 낸 상수 값 자체를 말하며 값을 구성하는 최소 단위

 은 프로그램에 의해 조작될 수 있는 대상을 말한다. 값은 다양한 방법으로 생성할 수 있다. 가장 간단한 방법은 리터럴 표기법(literal notation)을 사용하는 것이다.

리터럴 표기법 예시

// 숫자 리터럴
10.50
1001

// 문자열 리터럴
'Hello'
"World"

// 불리언 리터럴
true
false

// null 리터럴
null

// undefined 리터럴
undefined

// 객체 리터럴
{ name: 'Lee', gender: 'male' }

// 배열 리터럴
[ 1, 2, 3 ]

// 정규표현식 리터럴
/ab+c/

// 함수 리터럴
function() {}

7가지의 데이터 타입 (6개 원시 타입 + 1개의 객체 타입)

자바스크립트의 모든 값은 데이터 타입을 갖는다. 자바스크립트는 7가지 데이터 타입을 제공한다.

  • 원시 타입 (primitive data type)
    • number
    • string
    • boolean
    • null
    • undefined
    • symbol (New in ECMAScript 6)
  • 객체 타입 (Object data type)
    • object

타입 지정은 안함요 (매우 동적으로 작동함)

자바스크립트는 C나 Java외는 다르게 변수를 선언할 때 데이터 타입을 미리 지정하지 않는다.

→ 다시 말해, 변수에 할당된 값의 타입에 의해 동적으로 변수의 타입이 결정된다. 이를 동적 타이핑이라 하며 자바스크립트가 다른 프로그래밍 언어와 구별되는 특징 중 하나이다.

// Number
var num1 = 1001;
var num2 = 10.50;

// String
var string1 = 'Hello';
var string2 = "World";

// Boolean
var bool = true;

// null
var foo = null;

// undefined
var bar;

// Object
var obj = { name: 'Lee', gender: 'male' };

// Array
var array = [ 1, 2, 3 ];

// function
var foo = function() {};

연산자

연산자(Operator)는 하나 이상의 표현식을 대상으로 산술, 할당, 비교, 논리, 타입 연산 등을 수행해 하나의 값을 만든다. 이때 연산의 대상을 피연산자(Operand)라 한다.

자바스크립트는 암묵적 타입 강제 변환을 통해 연산을 수행한다.

// 산술 연산자
var area = 5 * 4; // 20

// 문자열 연결 연산자
var str = 'My name is ' + 'Lee'; // "My name is Lee"

// 할당 연산자
var color = 'red'; // "red"

// 비교 연산자
var foo = 3 > 5; // false

// 논리 연산자
var bar = (5 > 3) && (2 < 4);  // true

// 타입 연산자
var type = typeof 'Hi'; // "string"

// 인스턴스 생성 연산자
var today = new Date(); // Sat Dec 01 2018 00:57:19 GMT+09

//암묵적 연산 허용

var foo = 1 + '10'; // '110'
var bar = 1 * '10'; // 10

주석

주석(Comment)은 작성된 코드의 의미를 설명하기 위해 사용한다.

코드는 읽기(이해하기) 쉬워야 한다.(가독성이 좋아야 한다) 여러분이 작성한 코드를 다른 누군가가 읽는다면 “아니, 이게 뭐하는 코드야?”라고 생각하는 순간이 있기 마련이다. 여러분이 해야 하는 일은 바로 그런 부분에 주석을 다는 것이다. (읽기 좋은 코드가 좋은 코드이다)

한줄 주석은 // 다음에 작성하며 여러 줄 주석은 /*과 */의 사이에 작성한다. 주석은 해석기(parser)가 무시하며 실행되지 않는다.

→ 그래도 최적의 코드는 주석없이 보는 코드임

프로그램(스크립트)은 컴퓨터(Client-side Javascript의 경우, 엄밀히 말하면 웹 브라우저)에 의해 단계별로 수행될 명령들의 집합이다.

각각의 명령을 문(statement)이라 하며 문이 실행되면 무슨 일인가가 일어나게 된다.

문은 리터럴, 연산자(Operator), 표현식(Expression), 키워드(Keyword) 등으로 구성되며 세미콜론( ; )으로 끝나야 한다.

var time = 10;
var greeting;

if (time < 10) {
  greeting = 'Good morning';
} else if (time < 20) {
  greeting = 'Good day';
} else {
  greeting = 'Good evening';
}

console.log(greeting);

블록 유효범위를 생성하지 않는 자바스크립트.

자바스크립트에서는 블록 유효범위(Block-level scope)를 생성하지 않는다. 함수 단위의 유효범위(Function-level scope)만이 생성된다.

함수가 아닌 블록({ }) 내부에서는 새로운 유효범위(Scope)가 생성되지 않았습니다.

아마 뒤에 다룰테지만..

함수 스코프는 매우 강력한 스코프

✅ 1. 함수 단위의 유효범위 (Function-level Scope)

자바스크립트에서 var 키워드로 선언된 변수는 오직 함수 단위에서만 새로운 유효범위를 가진다.

→ 이 말은, if문, for문, while문 같은 블록({ }) 내부에서 선언해도 전역 또는 함수 스코프에 속한다는 의미!

쉽게말하면, 자유분방한 var을 가둘수 있다.

function secretFunction() {
  var secret = "This is hidden!";
  console.log(secret); // ✅ 정상 출력
}

secretFunction();
console.log(secret); // ❌ ReferenceError: secret is not defined

var은 밑에서도 설명하겠지만, 함수 스코프가 아닌 블록 스코프에서는 자유롭게 전역에서 쓸 수 있다.

그러나 함수 스코프는 매우 강력한 보호막이기에 var의 날뜀을 막는다.

자유분방한 var?

예시를 들어보면. 아래와 같은 코드에서

var message는 if문의 내부 블록에서 선언되었다.

근데 if문 외부 블록의 console.log()에서 정상작동하는 모습을 보임

function example() {
  if (true) {
    var message = "Hello!";
  }
  console.log(message); // 정상 출력: "Hello!"
}

example();

✔ var message = "Hello!";는 if 블록({ }) 안에서 선언되었지만,

함수(example) 전체에서 접근 가능함

✔ 즉, 자바스크립트는 블록을 스코프로 만들지 않고, 함수 단위에서만 스코프를 생성한다.

함수 스코프가 아니라면, 전역에서도 실행가능한 var 변수

if (true) {
  var globalMessage = "I am global!";
}

console.log(globalMessage); // 정상 출력: "I am global!"

→ var은 함수 뿐만 아니라, 이렇게 전역으로 실행을 하여도 정상적으로 출력이 됩니다.

var.. 좋은거 아닌가요 범위가 넓으니까

아닙니다

지금까지의 과정을 정리해보면..

✔ if 문 안에서 선언했지만 전역 범위(Global Scope)에 존재함

✔ 즉, var를 사용하면 블록 내부에 선언해도 전역 또는 함수 스코프에서 접근 가능

✔ 이 때문에 예기치 않은 변수 변경이나 오류 발생 가능성이 커짐

따라서 등장한 해결책 → let && const

if (true) {
  let blockMessage = "I am inside block!";
}

console.log(blockMessage); // ❌ ReferenceError: blockMessage is not defined

var처럼 자유분방하게 전역에서도 실행되는 것을 예방하겠다는 의지

✔ var는 블록({ })을 무시하고 여전히 전역 스코프에 존재

✔ 즉, 블록 내에서 선언해도 블록을 벗어나 접근 가능

함수와 블록의 차이?

자바스크립트에서 기본적으로 함수는 새로운 스코프를 만듭니다

하지만 블록({ })은 var를 사용할 경우 스코프를 만들지 않습니다.

✔ 함수 내에서 선언된 변수는 함수 스코프 내부에서만 접근 가능

✔ 함수 밖에서는 접근할 수 없기 때문에 오류 발생

거듭 강조하면

함수 스코프 (위) vs 블록 스코프 (아래)

function testScope() {
  var functionScoped = "I am inside function!";
}

console.log(functionScoped); // ❌ ReferenceError: functionScoped is not defined
{
  var noBlockScope = "I escape!";
}

console.log(noBlockScope); // 정상 출력: "I escape!"

블록 스코프를 썼지만 var을 활용했기에 쉴드를 뚫어버림.

모던 자바스크립트의 중점을 두면 var은 좋지 않은 선택임

당연히 const & let을 쓰는게 더욱 더 적절함.

1️⃣ var는 블록 스코프를 무시하고 함수 스코프를 따른다.

if (true) {
  var problemVar = "Oops!";
}
console.log(problemVar); // 정상 출력: "Oops!"

2️⃣ 호이스팅(Hoisting) 문제

var는 변수를 끌어올려(hoisting) 선언만 먼저 실행되고 초기화는 나중에 진행됨

console.log(testVar); // ❌ undefined (에러는 안 나지만 직관적이지 않음)
var testVar = "I am hoisted!";
console.log(testVar); // ✅ "I am hoisted!"

호이스팅은 또 먼가요 (이것도 아마 뒤에서 설명할거임)

쉽게말하면 실행 전에 변수 선언을 코드 최상단으로 호잇 하고 끌어올리는것!

변수 선언을 최상단으로 끌어올림 (호이스팅 시 초기화는 되지 않음)

위 코드의 내부동작은 아래와 같음

  1. var이 있네 위에서 선언해버려! → 초기화 안함(undefined)
  2. undefined
  3. 초기화는 원래 초기화했던 장소에서됌 → 이떄 testVar이라는 값 받음
  4. testVar이거임
var testVar; // 선언(hoisting)
console.log(testVar); // ✅ undefined
testVar = "I am hoisted!"; // 초기화
console.log(testVar); // ✅ "I am hoisted!"

어려운데요

그래서 var 쓰지말라는거임

반면 let, const도 호이스팅은 되는데..

선언과 초기화가 동시에 진행되므로 초기화 전에 접근하면 오류가 발생함

console.log(testLet); // ❌ ReferenceError: Cannot access 'testLet' before initialization
let testLet = "I am let!";
console.log(testLet); // ✅ "I am let!"

✔ let은 호이스팅되지만, 초기화 전에는 접근 불가능

✔ 즉, testLet이 끌어올려지긴 했지만, **“임시 사각지대(TDZ, Temporal Dead Zone)”**에 있으므로 오류 발생!

아니 그러면 호이스팅이 안된거잖아요

선언과 초기화가 동시에 이루어지지 않기 때문인거지, 호이스팅이 안된건 아닙니다.

그에따라 레퍼런스 에러가 발생할수도 있는거임 (let, const)에서

console.log(a); // ✅ undefined (에러 X)
var a = 10;
console.log(a); // ✅ 10
console.log(a); // ✅ undefined (에러 X)
var a = 10;
console.log(a); // ✅ 10

잘 이해가 안갈수있으니 더 직관적인 .. (호이스팅 된다고 !!)

선언과 초기화가 동시에 일어나지않을 뿐이라고!

✔ let이 호이스팅되지 않는다면 typeof c가 'undefined'를 반환해야 하지만, 실제로는 ReferenceError가 발생!

✔ 즉, 변수는 호이스팅되었지만 TDZ 때문에 사용할 수 없는 상태에 놓여 있는 것.

console.log(typeof c); // ❌ ReferenceError: Cannot access 'c' before initialization
let c = 30;

길이 좀 샜는데.. var 3번째 단점

3️⃣ 같은 변수명으로 중복 선언 가능 (오류 안 남)

var duplicate = "first";
var duplicate = "second"; // 🚨 오류 없이 덮어씌워짐
console.log(duplicate); // ✅ "second"

결론 4가지 포인트 + 3가지 호이스팅 포인트

✔ 자바스크립트는 원래 함수 단위 유효범위(Function-level Scope)만 존재

✔ {} 블록 내부에서 선언된 var 변수는 블록을 무시하고, 여전히 전역 또는 함수 스코프에 존재

ES6 이후 let**,** const가 도입되면서 블록 스코프(Block-level Scope)가 추가됨

✔ var 사용은 지양하고, let const를 사용하여 예기치 않은 오류를 방지하는 것이 좋다

✅ var**,** let**,** const 모두 호이스팅된다.

✅ 하지만 var는 선언만 호이스팅되고 undefined로 초기화되며, let const는 초기화 없이 TDZ**(임시 사각지대) 상태에 놓인다.**

✅ 따라서 let과 const를 사용하면 초기화 전에 변수에 접근하지 못하게 막아주므로 더욱 안전하다! 🚀