프로그래밍/HTML, CSS, JavaScript

[JavaScript] var, let, const와 스코프 개념정리

싯타마 2022. 11. 4. 17:59

 스코프는 모든 식별자(변수 이름, 함수 이름, 클래스 이름 등)가 자신이 선언된 위치에 의해 다른 코드가 식별자 자신을 참조할 수 있는 유효 범위이다.

1. 스코프(Scope)

- 스코프는 모든 식별자(변수 이름, 함수 이름, 클래스 이름 등)가 자신이 선언된 위치에 의해 다른 코드가 식별자 자신을 참조할 수 있는 유효 범위이다. 


- 모든 스코프는 하나의 계층적 구조로 연결되며, 모든 지역 스코프의 최상위 스코프는 전역 스코프다. 계층적으로 연결된 것을 스코프체인(Scope Chain)이라고 한다.


- 변수를 참조할 때 자바스크립트 엔진은 스코프 체인을 통해 변수를 참조하는 코드의 스코프에서 시작하여 상위 스코프 방향으로 이동하며 선언된 변수를 검색한다.


- 따라서 상위 스코프에서 선언한 변수를 하위 스코프에서도 참조 할 수 있다.

 

2. 렉시컬 환경(Lexical Environment)

- 스코프 체인은 실행 컨텍스트의 렉시컬 환경을 단방향으로 연결한 것이다.

 

- 자바스크립트 엔진은 코드를 실행하기에 앞서 스코프 체인과 같은 자료구조인 렉시컬 환경을 실제로 생성한다.

 

- 변수 선언이 실행되면 변수 식별자가 이 자료구조에 키로 등록되고 변수 할당이 일어나면 자료구조의 변수 식별자에 해당하는 값을 변경한다.

 

- 따라서 자바스크립트 엔진은 상위 스코프 방향으로 이동하기 때문에 하위 스코프로 내려가면서 식별자를 검색하는 일이 없다.


- 이는 상위스코프에서 유효한 변수는 하위 스코프에서 자유자재로 사용이 가능하지만, 하위 스코프에서 유효한 변수를 상위 스코프에서 참조할 수 없음을 의미한다.

 

3. 스코프 종류

1) 동적 스코프(Dynamic Scope)
- 함수를 어디서 호출했는지에 따라 함수의 상위 스코프를 결정한다.

 

2) 정적 스코프(Static Scope)=렉시컬 스코프(Lexical Scope)

- 함수를 어디서 정의했는지에 따라 함수의 상위 스코프를 결정한다.

- 함수의 호출된 위치는 상위 스코프 결정에 영향을 끼치지 못한다.

- 함수의 상위 스코프는 언제나 자신이 정의된 스코프이다.

* 자바스크립트는 렉시컬 스코프(정적 스코프)를 따른다.

 

3) 전역 스코프(Global Scope)

- 변수가 전역에서 선언되어 코드 가장 바깥 영역인 전역에 스코프를 갖는 것이다.

 

4) 지역 스코프(Local Scope)

- 변수가 함수 몸체 내부에서 선언되서 지역 내에서의 스코프를 갖는 것이다.

 

5) 함수레벨 스코프(Function Level Scope)

- 코드 블록(if, for, while 등등)이 아닌 오직 함수 의해서 지역 스코프가 생성되는 것이다.

 

6) 블록 레벨 스코프(Block Level Scope)

- 코드블록(if, for, while, {})이 생성될 때마다 새로운 스코프가 형성되는 것이다.

- 원래 자바스크립트는 함수 스코프를 따르지만, ES6부터 let과 const를 사용해서 블록 레벨 스코프를 형성 할 수 있게 되었다.

 

4. 변수의 생명 주기 

- 메모리 공간이 확보된 시점부터 메모리 공간이 해제되어 가용 메모리풀에 반환되는 시점까지이다.


- 일반적으로는 함수가 종료하면 함수가 생성된 스코프도 소멸한다. 하지만 누군가가 스코프를 참조하고 있으면 스코프는 소멸하지 않고 생존하게 된다.

 

5. 호이스팅

- 변수 선언이 스코프의 선두로 끌어올려진 것처럼 동작하는 자바스크립트의 특징


- 호이스팅은 스코프를 단위로 동작한다. 전역변수의 호이스팅은 전역변수의 선언이 전역 스코프의 선두로 끌어올려진 것처럼 동작한다.

 

- 지역 변수의 호이스팅은 지역 변수의 선언이 지역 선두로 끌어 올려진 것처럼 동작한다. 따라서 지역변수는 함수 전체에서 유효하다

예제)

//변수선언 호이스팅에 의해 맨처음 num 변수가 선언된다.
// num에 아직 어떠한 것도 할당하지 않았기 때문에 undefined로 초기화 된다.
console.log(num); //undefined

// num 변수에 10 할당
num = 10;

// num 변수에 10이 할당되었기 때문에 10을 출력한다.
console.log(num); // 10

// 변수 선언은 런타임 이전에 자바스크립트 엔진에 의해 암묵적으로 실행된다.
var num;

 

6. var

- var은 함수의 함수만을 지역 스코프로 인정하는 함수 레벨 스코프이다.


- 따라서 if 문 안에 있는 var 변수는 함수레벨스코프만 인정하기 때문에 함수 밖에서 var 키워드로 선언된 변수는 코드 블록 내에서 선언되어도 전역 변수이다.

 
1) 전역 변수의 문제점


- 전역 변수를 선언하는 이유는 코드 어느 곳이든 참조하고 할당할 수 있는 변수를 사용하고자 하기 때문이다. 

 

- 이는 모든 코드가 전역 변수를 참조하고 변경할 수 있는 암묵적 결합을 허용하기 때문에 변수의 유효 범위가 크다면 코드의 가독성은 나빠지고 의도치 않게 상태가 변경될 수 있는 위험이 있다.


- 전역 변수는 생명 주기가 길다. 따라서 메모리 리소스도 오랜 기간 소비한다. 또한 var 키워드는 변수의 중복 선언을 허용 함으로 생명 주기가 긴 전역 변수는 변수 이름이 중복될 가능성이 있다.


- 전역 변수는 스코프 체인 상에서 종점에 존재하기 때문에 변수를 검색할 때 전역 변수가 가장 마지막에 검색된다. 그래서 변수 검색 속도가 가장 느리다.(타 변수보다 크지는 않다)


- 자바스크립트의 특징 중 하나는 파일이 분리되어도 하나의 전역 스코프를 공유한다. 따라서 다른 파일 내 동일한 이름으로 명명된 전역 변수나 전역 함수가 같은 스코프 내에 존재할 경우 원치 않을 결과가 나올 수 있다.


- 따라서 전역 변수의 무분별한 사용은 위험하고 지양하여야 한다. 전역 변수보다는 지역 변수를 사용하고 변수의 스코프는 좁을수록 좋다.

2) 전역 변수 제한하는 방법


- 즉시 실행 함수 사용하기: 모든 코드를 즉시 실행 함수로 감싼다면 모든 변수는 즉 실행 함수의 지역 변수가 된다.


- 전역에 네임스페이스 역할을 담당할 객체를 생성하고 전역 변수처럼 사용하고 싶은 변수를 프로퍼티로 추가하는 방법이다.


- 이는 식별자 충돌에는 효과적이지만 네임스페이스 자체가 전역 변수에 할당돼서 그다지 유용하지 않다.


- 모듈 패턴 만들기: 클래스를 모방해서 관련이 있는 변수와 함수를 모아서 즉시 실행 함수로 만들어서 하나의 모듈을 만드는 방법이다. 이는 클로저 기능을 통해 전역 변수를 제한하고 캡슐화도 구현하기 때문에 정보 은닉 효과를 낼 수 있다.

 

예제)

var 덧셈뺄셈 = (function() {
var a = 2;
var b = 1;

return {
sum() {
return a+b;
},
minus() {
return a-b;
}
};
}());

console.log(덧셈뺄셈.a); // undefined
console.log(덧셈뺄셈.sum()); // 3

 

 

- 위 객체는 외부에 노출하고 싶은 변수나 함수를 담아 반환한다.

 

- 이때 객체의 프로퍼티는 외부에 노출되는 퍼블릭 멤버이고 외부로 노출하고 싶지 않은 변수나 함수는 반환하는 객체에 추가하지 않으면 외부에서 접근할 수 없는 프라이빗 멤버가 된다.


- ES6모듈 사용하기 : ES6 모듈은 파일 자체의 독자적인 모듈 스코프를 제공한다.


7. ES6 let과 const

1) Let

- 변수 중복 선언을 하면 문법 에러가 발생한다.


- var과 달리 let으로 선언한 변수는 모든 코드 블록을 지역 스코프로 인정하는 블록 레벨 스코프이다.

예제)

let sayhi = "hi";

{
let sayhi = "hello";
let saybye = "bye";
}

console.log(sayhi); // hi
console.log(saybye); // ReferenceError: saybye is not defined

 

- let이 블록 레벨 스코프임으로 지역 변수 내에 있는 let은 전역 스코프에서는 탐색할 수 없다.


- sayhi는 전역 스코프 레벨에 있는 hi가 출력되고 지역 레벨에 있는 saybye는 정의되지 않았다고 오류가 발생한다.


- var 키워드와 달리 let은 변수 호이스팅이 발생하지 않는 것처럼 동작한다. 이는 let은 선언 단계와 초기화 단계가 분리되어 진행되기 때문이다.


- 그렇다고 호이스팅이 안 되는 건 아니다. let 또한 var과 마찬가지로 스코프 내에서 런타임 이전에 변수 선언이 호이스팅 돼서 먼저 진행된다. 하지만 var과 달리 선언된 변수가 초기화되기 전까지 변수를 참조할 수 없는 구간인 일시적 사각지대(Temporal Dead Zone)가 생기기 때문에 참조 에러(Reffence error)가 발생하는 것이다.

 


2) const 

- const는 상수를 선언하기 위해 사용한다.(반드시 상수만을 위해 사용하지는 않는다.) 

- 키워드로 선언한 변수는 반드시 선언과 동시에 초기화해야 한다. 그렇지 않으면 문법 오류가 발생한다.


예제)

const num // SynteaxError...
const num = 1 // undefined -> 오류안남

 

- const 또한 let과 마찬가지로 호이스팅이 발생하지 않는 것처럼 동작한다.(원리도 let과 동일하다.)


- let과의 차이점: var과 let은 재할당이 자유롭지만 const는 재할당이 금지된다. 이러한 특징 때문에 상수를 표현하는 데 사용된다.


- 상수도 값을 저장하기 위해 메모리 공간을 할당 함으로 변수라고 할 수  있지만 재할당은 하면 안 된다. 


- 일반적으로 상수라 하면 변하지 않는 값을 의미하지만  const는 새로운 값을 재할당을 금지할 뿐 불변하지는 않는다.

 

- const로 선언된 변수에 객체를 할당할 경우 값을 변경할 수 있다.

예제)

const user = {
name: "루피",
age: 10,
}

user.name = "뽀로로";

console.log(user) // {name: 뽀로로, age: 10}

 

 

요약
- var 신경 써줘야 할게 많아서 복잡하다.
- es6를 사용한다면 var 키워드 말고 const와 let을 사용한다.

- 변수를 선언하는 시점에는 재할당을 여부를 판단하기가 어렵고 객체도 재할당을 하는 경우가 적음으로 변수를 선언할 때에는 일단 const를 사용하고 추후에 재할당이 필요할 경우 let으로 수정하는 것을 추천하다.

 

* 이 글은 이웅모 저자의 2021, 모던 자바스크립트 Deep Dive 책을 참고하여 작성하였습니다.