[JS딥다이브] 7. 스코프

게코젤리·2023년 5월 11일

1. 스코프란?

  • 식별자(변수명, 함수명, 클래스명..)가 유효한 범위
  • 스코프 내에서 식별자는 유일, 다른 스코프에는 같은 식별자 사용 가능.(스코프는 네임스페이스)
  • var 키워드로 선언된 변수는 같은 스코프 내에서 중복 선언이 허용. 하지만 let이나 const의 경우 허용되지 않음.

2. 전역과 지역

  • 전역 : 코드의 가장 바깥 영역, 전역 스코프, 전역 스코프를 갖는 전역 변수는 어디서든지 참조 가능.
  • 지역 : 함수 몸체 내부, 지역 스코프, 지역 스코프를 갖는 지역 변수는 자신의 지역 스코프와 하위 지역 스코프에서 유효.

3. 스코프 체인

스코프가 함수의 중첩에 의해 계층적으로 연결되어 있는 것을 스코프 체인이라고 한다. 자바 스크립트는 스코프 체인을 통해 변수를 참조하는 코드의 스코프에서 상위 방향으로 이동하며 선언된 변수를 검색한다.

4. 함수 레벨 스코프

  • 블록 레벨 스코프 : 모든 코드블록이 만드는 지역 스코프 (let, const)
  • 함수 레벨 스코프 : 함수의 코드 블록 만을 지역 스코프로 인정 (var)

5. 렉시컬 스코프

var x = 1;

fucntion foo(){
	var x = 10;
	bar();
}
function bar(){
	console.log(x);
}
foo(); // 1
bar(); // 1
  • 렉시컬 스코프 : 함수를 어디서 호출했는지가(동적 스코프) 아니라 함수를 어디서 정의했는지에 따라 정적으로 함수의 상위 스코프를 결정.
  • bar();가 1이 나오는 것이 헷갈릴 수 있다. 이때 함수가 어디서 정의됐는지만 유념하면 된다. function bar(){}에서 참조할 수 있는 x는 var x = 1;뿐이다.

6. 전역 변수의 문제점

6-1. 변수의 생명주기

  • 변수 선언은 선언문이 어디에 있든 런타임 이전 단계에 먼저 실행된다고 했다. 그러나 이 설명은 전역 변수에만 한정된다. 함수 내부에서 선언한 변수는 함수가 호출된 직후, 함수 몸체의 코드가 순차적으로 실행되기 이전에 먼저 실행된다.
  • 함수가 종료되면(return) 함수 내부의 변수도 소멸되어 생명 주기가 종료된다.
  • 일반적으로 지역 변수의 생명 주기는 함수의 생명 주기와 일치한다.
  • 함수 몸체 내부에서 선언된 지역 변수의 생명 주기는 함수의 생명 주기와 대부분 일치하지만 지역 변수가 함수보다 오래 생존하는 경우도 있다.(클로저)
  • 전역 변수는 전역 객체 window, 즉 웹페이지를 닫기 전까지 유효하다. 전역 변수으 ㅣ생명 주기는 전역 객체의 생명 주기와 일치 한다.
var x = 'g'
function foo(){
    console.log(x);
    var x = 'l';
}
foo();// undefined 

위 코드에서 언뜻 foo();의 결과로 'g'를 출력할 것으로 보이지만 그렇지 않다. foo 내부에서 x가 선언됐기 때문에 런타임 이전에 x는 undefined로 초기화된 상태다. 이와 같이 변수 선언은 스코프의 선두로 끌어 올려진 것처럼 동작하는 자바스크립트의 특징을 호이스팅이라고 한다.

6-2. 전역 변수의 문제

  1. 암묵적 결합 : 모든 코드가 전역 변수를 참조하고 변경할 수 있게 되기 때문에 코드의 가독성이 떨어지고 의도치 않게 상태가 변경될 위험이 높아진다.
  2. 긴 생명 주기 : 전역 변수는 생명 주기가 길다. 메모리 리소스가 오래 소비되고 상태 변경 오류 발생 확률이 높아진다.
  3. 스코프 체인 상에서 종점 : 전역 변수는 가장 마지막에 검색된다. 즉, 검색 속도가 가장 느리다.
  4. 네임 스페이스 오염 : 다른 파일 내에서 동일한 이름으로 명명된 전역 변수나 전역 함수가 있을 경우 오류를 초래할 가능성.

6-3. 전역 변수를 대체할 방법

  1. 즉시 실행 함수
(function (){
  var foo = 10;
})();
console.log(foo)
  1. 네임스페이스 객체
var MYAPP = {};
MYAPP.name = 'Lee';
  1. 모듈 패턴(클로저)
  2. ES6 모듈
<script type="module"...></script>

7. let, const 키워드와 블록 스코프

var 키워드로 선언한 변수는 함수 스코프
let, const 키워드로 선언한 변수는 블록 레벨 스코프

7-1. let, const와 호이스팅

console.log(a); // referenceError: a is not defined
let a;
console.log(a); // undefined
a = 1;
console.log(a); // 1

let으로 선언한 변수는 var와 달리'선언'과 '초기화'가 분리되어 진행된다. 런타임 이전에 선언 단계가 먼저 실행되고 초기화 단계는 런타임에 실행되기 때문에 초기화가 진행되기 전에 변수에 접근하려고 하면 위와 같이 참조 에러가 발생한다. 이때 스코프의 시작 지점부터 초기화 시작 지점까지 변수를 참조할 수 없는 구간을 TDZ라고 부른다.
이는 얼핏 let이 var 키워드처럼 호이스팅이 발생하지 않는 것처럼 보이지만 사실 let도 호이스팅이 일어난다. 아래 코드를 확인해 보자.

let foo = 1;
{
	console.log(foo) // referenceError : Cannot access 'a' before initialization
 
	let foo = 2;
}

let 키워드가 호이스팅이 발생하지 않는다면 console.log(foo)에서 1이 출력되어야 하지만 실제로는 참조 에러가 발생한다. 이는 let 키워드에도 호이스팅이 적용되기 때문에 블록 스코프 내에서 (let foo = 2) 선언만 되었고 초기화가 아직 이루어지지 않은 상태(TDZ)의 foo를 참조하려 했기 때문이다.

const a; // referenceError: Missing initializer in const declaration
const b = 2;
{
	console.log(b); // referenceError: Cannot access 'b' before initialization
	const b = 1;  
}

const는 반드시 선언과 동시에 초기화해야한다. 그러나 이것은 코드 작성 단계에서의 요구사항이고 const 키워드로 선언한 변수도 let과 마찬가지로 선언(런타임 이전)과 초기화(런타임)가 분리되어 실행되며 따라서 호이스팅, TDZ 또한 존재한다.

  • 유의할 것은 const 키워드는 재할당을 금지할 뿐, '불변'을 의미하지는 않는다는 것이다. 새로운 값을 직접 재할당하는 것은 불가능 하지만 객체 내부를 변경하는 것은 가능하다.

📖 모던자바스크립트 딥다이브 13장 스코프(189p)
📖 모던자바스크립트 딥다이브 14장 전역 변수의 문제점(201p)
📖 모던자바스크립트 딥다이브 15장 let, const 키워드와 블록 레벨 스코프(208p)

0개의 댓글