Languages/JavaScript

[HUFS/GnuVil] #7 전역변수의 문제점, 블록 레벨 스코프

성중 2022. 10. 2. 16:32

전역변수의 문제점

var x = 'global';

function foo() {
  console.log(x); // ①
  var x = 'local';
}

foo();
console.log(x); // global

전역변수의 생명주기는 전역객체의 생명주기와 일치한다

 

전역변수는 다음과 같은 문제점들이 있다

  1. 암묵적 결합: 모든 코드가 전역 변수를 참조하고 변경할 수 있는 암묵적 결합(implicit coupling)을 허용하며, 유효 범위가 클수록 가독성이 나빠지고 의도치 않은 상태 변경의 위험도 커진다
  2. 긴 생명 주기: 긴 생명 주기동안 메모리 리소스를 오래 소비하며 그동안 의도치 않은 재할당이 이루어질 수 있다
  3. 스코프 체인 상에서 종점에 존재: 변수를 검색할 때, 전역 변수가 가장 마지막에 위치해 검색 속도가 가장 느리다
  4. 네임스페이스 오염: 자바스크립트는 파일이 분리되어 있다 해도 하나의 전역 스코프를 공유한다. 따라서 다른 파일 내에서 동일한 이름으로 명명된 전역 변수나 함수가 같은 스코프 내에 존재할 경우 오류를 발생시킬 수 있다

이를 해결하기 위해, 즉시 실행 함수 / 네임스페이스 객체 / 모듈 패턴 등 다양한 시도가 있었지만 ES6 이후 파일의 독자적인 모듈 스코프가 제공되었고, 이후 Webpack 등의 모듈 번들러를 사용하는 방식이 주로 사용되고 있다

 

블록 레벨 스코프

var x = 1;

if (true) {
  // x는 전역 변수다. 이미 선언된 전역 변수 x가 있으므로 x 변수는 중복 선언된다.
  // 이는 의도치 않게 변수값이 변경되는 부작용을 발생시킨다.
  var x = 10;
}

console.log(x); // 10

var 키워드는 함수의 코드 블록만을 지역 스코프로 인정한다 (함수 레벨 스코프)

 

var foo = 123;
// var 키워드로 선언된 변수는 같은 스코프 내에서 중복 선언을 허용한다.
// 아래 변수 선언문은 자바스크립트 엔진에 의해 var 키워드가 없는 것처럼 동작한다.
var foo = 456;

let bar = 123;
// let이나 const 키워드로 선언된 변수는 같은 스코프 내에서 중복 선언을 허용하지 않는다.
let bar = 456; // SyntaxError: Identifier 'bar' has already been declared

let과 const 키워드는 같은 스코프 내의 중복 선언을 허용하지 않는다

 

let foo = 1; // 전역 변수

{
  let foo = 2; // 지역 변수
  let bar = 3; // 지역 변수
}

console.log(foo); // 1
console.log(bar); // ReferenceError: bar is not defined

또한 var 키워드와 달리 모든 코드 블록을 지역 스코프로 인정한다 (블록 레벨 스코프)

 

// 런타임 이전에 선언 단계가 실행된다. 아직 변수가 초기화되지 않았다.
// 초기화 이전의 일시적 사각 지대에서는 변수를 참조할 수 없다.
console.log(foo); // ReferenceError: foo is not defined

let foo; // 변수 선언문에서 초기화 단계가 실행된다.
console.log(foo); // undefined

foo = 1; // 할당문에서 할당 단계가 실행된다.
console.log(foo); // 1

let 키워드는 선언 단계와 초기화 단계가 분리되며, 변수 호이스팅이 발생은 하지만 일시적 사각지대(Temporal Dead Zone, TDZ)로 인해 선언 전 참조가 방어된다

 

let 키워드 변수의 생명주기

// 이 예제는 브라우저 환경에서 실행해야 한다.

// 전역 변수
var x = 1;
// 암묵적 전역
y = 2;
// 전역 함수
function foo() {}

// var 키워드로 선언한 전역 변수는 전역 객체 window의 프로퍼티다.
console.log(window.x); // 1
// 전역 객체 window의 프로퍼티는 전역 변수처럼 사용할 수 있다.
console.log(x); // 1

// 암묵적 전역은 전역 객체 window의 프로퍼티다.
console.log(window.y); // 2
console.log(y); // 2

// 함수 선언문으로 정의한 전역 함수는 전역 객체 window의 프로퍼티다.
console.log(window.foo); // ƒ foo() {}
// 전역 객체 window의 프로퍼티는 전역 변수처럼 사용할 수 있다.
console.log(foo); // ƒ foo() {}

선언하지 않은 변수에 값을 할당한 암묵적 전역은 전역 객체 window의 프로퍼티가 된다

 

const foo; // SyntaxError: Missing initializer in const declaration

const 키워드로 선언한 변수는 반드시 선언과 동시에 초기화해줘야 한다

* let 키워드와 마찬가지로 TDZ의 영향을 받는다

 

const foo = 1;
foo = 2; // TypeError: Assignment to constant variable.

const는 상수 값이므로 let 키워드와 달리 재할당이 금지된다

 

const person = {
  name: 'Lee'
};

// 객체는 변경 가능한 값이다. 따라서 재할당없이 변경이 가능하다.
person.name = 'Kim';

console.log(person); // {name: "Kim"}

하지만 const 키워드로 선언된 변수에 객체를 할당한 경우 값을 변경할 수 있다

* const 키워드는 재할당을 금지할 뿐, 불변을 의미하지는 않는다

 

정리하면, ES6를 사용하는 경우 var는 사용하지 말아야 하며, 재할당이 필요한 경우에 한정해 스코프를 최대한 좁게 만들어 let 키워드를 사용하고, 나머지의 경우 const 키워드를 사용한다!

 

 내용은 위키북스의 '모던 자바스크립트 Deep Dive' 바탕으로 작성되었습니다.