전역 스코프

11hertz·2024년 3월 5일

YDKJSY

목록 보기
8/9
post-thumbnail

4.1 전역 스코프를 배워야 하는 이유

브라우저에서 실행되는 애플리케이션은 주로 세 가지 방법을 사용해 파일을 하나로 모으고 실행
1) 별도 모듈 번들러 사용 없이 ES 모듈을 바로 사용하는 경우
파일을 각자 하나씩 로딩하며 로딩 후에는 import 문에 있는 다른 모듈을 참조

2) 구축 과정에 번들러가 관여하는 경우
파일 전체가 합쳐져서 브라우저와 JS 엔진에 전달되며, 브라우저, JS 엔진은 하나의 커다란 파일만 처리
애플리케이션 하나가 단일 파일에 모여 있는 경우라도 파일 내 코드 조각 일부에서 다른 코드 조각을 참조할 때 사용할 이름 등록 또는 타 코드 조각에 접근 시 적용할 메커니즘 필요
번들러는 다양한 빌드 환경을 설정할 수 있도록 하며, 파일 내용 전체를 래퍼 함수(wrapper function)나 유니버설 모듈(Universal Module) 등을 사용해 하나의 스코프 안에 묶음
각 코드 조각은 다른 코드 조각에서 자신을 접근할 수 있도록 하는 지역 변수를 공유 스코프 안에 스스로 등록

3) 전역 스코프를 활용하는 방법
모든 코드 조각을 아우리는 하나의 스코프가 없는 경우라면 코드 조각들이 협업할 수 있는 유일한 방법은 전역 스코프를 활용하는 것
개별 파일이 공유하는 유일한 리소스는 전역 스코프이며, 각 파일의 최상위 스코프에 선언된 변수들은 전역 스코프의 전역 변수가 됨
전역 스코프는 런타임에 프로그램을 구성하는 조각들이 어디에 있는지, 각 코드 조각이 어떤 방식으로 다른 코드 조각에 접근해 협력하는지에 관여하는 것 뿐만 아니라 아래와 같은 경우에도 사용

  • JS 내장 기능을 사용할 때
    원싯값 : undefined, null, Infinity, NaN
    네이티브 객체 : Date(), Object(), String() 등
    전역 함수: eval(), parseInt() 등
    네임스페이스: Math, Atomics, JSON
    JS와 협력 관계인 기술: Intl, WebAssembly
  • 특정 호스팅 환경에서 제공하는 내장 기능을 사용할 때
    console과 연관 메서드
    DOM(window, document 등)
    타이머(setTimeout() 등)
    웹 API(navigator, history, geolocation, webRTC 등)위와 같은 경우 외에도 전역에는 프로그램을 풍성하게 만드는 다양한 기능 존재

4.2 전역 스코프의 위치

전역 스코프는 함수나 블록 안이 아닌 파일 가장 바깥쪽에 위치한다고 생각하기 쉽지만, 실상은 그리 간단하지 않음

4.2.1 브라우저의 창, windows 객체

전역 스코프가 처리되는 환경 중 가장 순수한 환경은 브라우저에서 단독으로 .js파일을 로드할 때
브라우저는 코드 침입을 최소화 하고 전역 스코프가 작동할 때 간섭도 최대한 하지 않음
식별자가 전역 스코프에 선언된다는 말은 전역 객체(브라우저에서 대개 window 객체)의 프로퍼티를 통해 해당 식별자에 접근할 수 있다는 말과 같음

var studentName = '카일';
function hello() {
	console.log(`${ studentName } 님, 안녕하세요!`);
}
window.hello();
// 카일 님, 안녕하세요!

JS 명세서에 따르면 예시의 외부 스코프가 글로벌 스코프이고 studentName은 전역 변수가 됨

그러나 이러한 작동 방식이 모든 JS 호스트 환경에서 보장되지 않음

전역을 가리는 전역

안쪽 스코프에 선언된 변수는 바깥쪽 스코프에 선언된 이름이 같은 변수를 가리고 접근을 막음
전역 변수와 이름이 같은 전역 프로퍼티는 섀도잉과 다른 방식으로 작동
전역 스코프에서는 전역 객체의 프로퍼티가 전역 변수에 의해 가려짐
let으로 전역에 변수를 선언하면 전역 변수가 추가되지만, 전역 객체의 프로퍼티가 추가되지는 않음

전역 객체에 있는 프로퍼티와 전역 스코프에 등록된 식별자가 다르게 작동하도록 코드를 작성하는 것은 좋지 않음
나중에 코드를 읽게 될 사람이 실수할 가능성이 높음

DOM 전역 변수

DOM 요소에 id 속성을 추가하면 전역 변수가 자동으로 생기고, 이 변수를 통해 해당 DOM 요소에 접근 가능
DOM id 속성값이 유효한 렉시컬 이름인 경우에는 새로운 렉시컬 변수가 생성되면서 전역 변수를 통해 해당 DOM에 접근 가능
id 속성값이 유효하지 않은 렉시컬 이름이면 오직 전역 객체(window[...])를 통해서만 DOM 요소에 접근 가능

id 속성이 있는 DOM 요소에 대응하는 변수를 자동으로 전역에 등록하는 것은 브라우저 환경에서 오래전부터 지원하던 기능이나, 자동으로 등록된 전역 변수는 되도록 사용하지 않는 것을 추천

window.name의 정체

window.name은 브라우저가 전역에 미리 정의해놓은 전역 객체의 프로퍼티
언뜻 보면 전역 변수처럼 작동하나 '일반적인 전역 변수'와는 다르게 작동

var name = 42;
console.log(name, typeof name);
// "42" string

예시처럼 var를 사용해서 전역에 변수를 선언하면 이미 선언되어 있는 전역 객체 프로퍼티 name을 가리지 않음
전역 스코프에 name이라는 이름을 가진 프로퍼티가 있기 때문에 var 선언이 무시되고 숫자 42는 window.name의 값이 됨
(let name으로 변수를 선언했다면 별도의 전역 변수 name이 생기면서 window.name이 가려짐)

name에 숫자 42를 할당했지만 값을 읽으면 문자열 "42"가 나오는 이유는,
name 프로퍼티는 window 객체에 사전에 정의된 getter이자 setter이기 때문
또한 setter에는 어떤 값을 넣든 문자열로 변환시킨다는 규칙이 있음

브라우저에서 번들러를 거쳐 독립된 파일 형태로 실행되는 JS는 DOM 전역 변수, window.name과 가은 드문 케이스를 제외하고 전역 스코프에서 가장 순수하게 작동

4.2.2 웹 워커

웹 워커는 브라우저에서 돌아가는 JS의 작동 방식을 바꿔주는 웹 플랫폼 확장 기능
JS 파일을 JS 프로그램이 돌아가고 있는 스레드가 아닌 (운영체제가 알아서 만드는) 별도의 스레드에서 돌아갈 수 있게 해줌
웹 워커를 사용하는 프로그램은 별도의 스레드에서 실행되기 때문에 레이스 컨디션(race condition)이나 기타 경쟁 상태를 막거나 피하려는 목적으로 메인 애플리케이션 스레드와 통신이 제한
웹 워커는 완전히 별개의 프로그램으로 취급되므로 메인 JS 프로그램과 전역 스코프를 공유하지 않음
웹 워커에서는 DOM에 접근할 수 없으므로 전역 스코프에 접근할 수 있게 하는 window 사용 불가
웹 워커에서 전역 객체를 참조하려면 일반적으로 self 사용
메인 JS 프로그램과 마찬가지로 var와 function 선언은 self처럼 저역 객체에 미러링 프로퍼티를 생성하지만, let을 비롯한 그 이외의 선언은 미러링 프로퍼티를 생성하지 않음

웹 워커에서의 전역 스코프는 일반 JS 프로그램의 전역 스코프와 작동 방식이 거의 같음

4.2.3 개발자 도구와 콘솔, REPL

콘솔이나 REPL을 사용해 가장 바깥 스코프에 문을 입력했을 때, 해당 문이 실제 전역 스코프에서 처리되는 것처럼 보일 수 있지만 실제로는 그렇지 않음
콘솔, REPL은 전역 스코프 작동 방식을 유사하게 모방하여 입력받은 문을 처리
콘솔과 REPL은 에뮬레이터이기 때문에 JS 엔진에서 전역 스코프가 작동하는 방식을 완벽하게 모방하지 못함
개발자 도구와 콘솔, REPL은 개발자 편의를 우선시하며, 이 말은 JS 명세서와는 다르게 프로그램이 작동할 수 있다는 것을 의미

개발자 도구는 개발자 편의를 위해 만든 도구이기 때문에 개발자 활동에 최적화
실제 JS 프로그램 컨텍스트를 결정 혹은 검증하려는 목적으로는 JS의 미묘한 동작을 재현할 수 없어 적합하지 않음

4.2.4 ES 모듈

모듈 패턴은 ES6에서 공식 지원 시작
ES 모듈의 두드러지는 특징은 파일 내 최상위 레벨 스코프 작동 방식
모듈 내에서 최상위 레벨인 가장 바깥 스코프에 변수를 선언하더라도 전역 변수가 되지 않으며, 모듈 범위 스코프의 변수가 됨
모듈 범위 스코프는 모듈 전역 스코프라고 표현하기도 함
모듈에서는 모듈이 아닌 JS 파일의 최상위 레벨에 선언을 할 때처럼 최상위 레벨의 선언을 프로퍼티로 추가할 수 있는 모듈 범위 스코프 객체를 지원하지 않음
전역 변수가 없다거나 전역 변수에 접근할 수 없다는 뜻은 아니며, 최상위 레벨에서 변수를 선언하면 전역 변수가 생성되지 않는다는 의미
모듈 최상위 레벨 스코프에서는 모듈 내 모든 콘텐츠가 함수에 래핑된 것처럼 묶여서 처리되며, 이 묶음은 전역 스코프의 하위 스코프가 됨
전역 스코프 내 모든 변수는 모듈 스코프 내에서 렉시컬 식별자를 통해 접근 가능
ES 모듈 패턴에서는 현재 모듈 작동에 필요한 모든 모듈을 import하는 전역 스코프에 대한 의존도 최소화 권장
ES 모듈 패턴을 사용하면 전역 스코프나 전역 스코프 객체를 사용하는 빈도가 감소

4.2.5 Node.js

Node.js의 특징 중 하나는 엔트리 파일을 포함해 모든 JS파일을 모듈(ES 모듈 또는 CommonJS 모듈)로 처리한다는 것
브라우저에서 모듈이 아닌 파일을 로드할 때와 다르게 각 JS 파일이 자체 스코프를 갖도록 함
코드를 함수로 감싸며, var 및 function 선언이 전역 변수로 취급되는 것을 방지하고 선언들을 함수 스코프에 포함시키기 위함
Node.js에서 전역 변수를 정의하는 방법은 내장 전역 프로퍼티인 global에 프로퍼티를 추가하는 방법이 유일
global은 진짜 전역 스코프 객체에 접근할 수 있게 해주는 참조값으로 브라우저 환경의 window와 유사
식별자 global은 JS가 아니고 Node.js에 정의되어 있다는 사실을 기억해야 함

4.3 globalThis

ES2020에서 전역 스코프 객체 참조가 globalThis로 표준화
globalThis를 사용하면 전역 스코프 객체를 참조할 수 있고, 다른 방법 대다수를 대체 가능
JS 환경이 globalThis를 지원하지 않는다면 폴리필을 사용해 호스트 환경에 상관없이 안전한 방법으로 전역 스코프 객체를 참조할 수 있음

4.4 정리

코드를 모듈 단위로 쪼개는 개발 방식이 자리잡으면서 전역 네임스페이스에 식별자를 저장하는 방식은 많이 쓰지 않게 됨
하지만 전역 스코프는 모든 JS 프로그램에 존재하고 중요한 역할을 함
작성한 코드가 브라우저를 넘어 영역을 확대해감에 따라 호스트 환경별로 전역 스코프와 전역 스코프 객체가 어떤 차이를 보이는지 확실히 아는 것이 중요해짐

profile
Practice Makes Perfect!

0개의 댓글