[JavaScript Deep Dive] 14. 전역 변수의 문제점

소정·2024년 1월 7일
1
post-thumbnail

아무리 리액트가 멋져도 프론트엔드는 결국 자바스크립트지 ~ !
프엔을 준비하며 ,, 참담한 취업 시장과 어디까지 얼마만큼 공부해야할지 많은 고민과 걱정이 드는건 사실이다.

하지만 ! 우선 바닐라 자바스크립트 실력이 중요한건 확실하다.
바닐라 자스 천재를 향해 가보자고 ~!

CSS 과제하고 벌써 10시가 넘었지만, 다행히 오늘 14장이 매우 짧다 ~ (다행)


1. 변수의 생명 주기

1. 지역 변수의 생명 주기

변수는 아래의 3가지 단계를 거친다. 즉, 변수도 결국 생명 주기가 있다.
만약 변수에 생명 주기가 없다면, 한번 선언된 변수는 프로그램을 종료할 때까지 메모리 공간을 점유할 것이다.

  1. 선언
  2. 할당
  3. 소멸

📌 변수 선언의 실행 시점과 변수 호이스팅

4장에서 살펴봤듯이, 변수 선언은 선언문이 어디에 있든 상관없이 런타임 이전에 가장 먼저 실행된다.
하지만 이것은 전역 변수에 해당하는 설명이다.

  • 전역변수 : 어디에 있든 런타임 이전에 가장 먼저 실행되어 변수 선언

📌 '지역 변수'의 실행 시점과 변수 호이스팅

함수 내부에서 선언한 지역 변수는, 함수가 호출된 직후 함수 몸체 내 코드가 순차적으로 실행되기 이전에 !
자바스크립트 엔진에 의해 먼저 실행된다.

  • 지역변수 : 함수가 호출된 후, 함수 몸체 내 코드 중 가장 먼저 실행되어 변수 선언
var x = 'global';

function foo(){
  
  console.log(x); // 1️⃣ ??
  var x = 'local';
  console.log(x); // local
  
  return x;
}

console.log(x); // global

foo 함수를 호출하면, 함수 몸체의 다른 문들이 실행되기 이전에 !!
x 변수의 선언문이 자바스크립트 엔진에 의해 가장 먼저 실행되어 x 변수 선언 및 undefined로 초기화된다.

그 후 함수 몸체를 구성하는 문들이 순차적으로 실행되기 떄문에,
변수 할당문이 작성된 순서에 'local' 값이 할당된다.

함수가 종료되면, x 변수도 소멸되어 생명 주기가 종료된다.
즉, 함수 내부에서 선언된 지역 변수함수가 호출되어 실행되는 동안에만 유효하다.
지역 변수의 생명 주기는 함수의 생명 주기와 동일하다.

📌 '지역 변수'가 '함수'보다 오래 생존하는 경우

함수 몸체 내부에서 선언된 지역 변수함수의 생명 주기와 대부분 일치하지만, 특정한 경우가 있다.

  • 변수 하나의 값을 저장하기 위해 확보한 메모리 공간 자체, 또는 메모리 공간을 식별하기 위해 붙인 이름
  • 변수의 생명 주기 메모리 공간이 확보된 시점 ~ 메모리 공간이 해제되어 가용 메모리 풀에 반환되는 시점

함수 내부 지역변수는 함수가 생성한 스코프에 등록된다.
따라서 변수는 자신이 등록된 스코프가 소멸(메모리 해제)될 때까지 유효하다.

할당된 메모리 공간은 아무도 참조하지 않을 때 가비지 콜렉터에 의해 해제되어 가용 메모리 풀에 반환된다.
메모리 공간을 참조하고 있으면, 해제되지 않고 확보된 상태로 남아 있는 것 !
이것은 스코프도 마찬가지로, 누군가 스코프를 참고한다면 스코프는 소멸하지 않고 생존한다.

📌 스코프 단위로 동작되는 '호이스팅'

위의 예제에서 1️⃣ 시점에서 어떤 값이 찍힐까? 답은 undefined !
지역 변수는 해당 함수의 몸체 내의 문이 순차적으로 실행되기 이전에 선언되고 undefined가 할당된다.
즉, 해당 시점에 이미 지역 변수 x는 선언되어 undefined 값을 갖는다.

이처럼, 호이스팅스코프 단위로 동작된다.

  • 지역 변수의 호이스팅 지역 변수의 선언이 지역 스코프의 선두로 끌어 올려진 것처럼 동작
    = 함수 전체에서 유효
  • 전역 변수의 호이스팅 전역 변수의 선언이 전역 스코프의 선두로 끌어 올려진 것처럼 동작
    = 전역 전체에서 유효

호이스팅은 변수 선언이 스코프의 선두로 끌어 올려진 것처럼 동작하는 자바스크립트 고유의 특징이다.

2. 전역 변수의 생명 주기

전역 코드는 명시적인 호출 없이 실행된다.
즉, 실행을 위한 특별한 진입점이 있는 함수와 다르게, 전역 코드는 코드가 로드되자마자 해석되어 실행된다.

  • 함수 종료 함수 몸체의 마지막 문 또는 반환문이 실행된 후
  • 전역 코드 종료 반환문을 사용할 수 없어, 마지막 문이 실행된 후

📌 var 키워드로 선언한 '전역 변수'와 '전역 객체'

var 키워드로 선언한 전역 변수는 전역 객체의 프로퍼티가 된다.
그래서 전역 변수의 생명 주기전역 객체의 생명 주기와 일치한다.

전역 객체 코드가 실행되기 이전 단계에, 자스 엔진에 의해 어떤 객체보다도 먼저 생성되는 특수한 객체

  • 클라이언트 사이드 환경(브라우저) : window
  • 서버 사이드 환경(node.js): global
  • 전역 객체를 가리키는 식별자 : globalThis (ES11에서 통일됨)
  • 전역 객체의 프로퍼티 : 표준 빌트인 객체, 호스트 객체, var 키워드로 선언한 전역 변수, 전역 함수

(브라우저 환경 기준) 전역 객체는 window이므로,
var 키워드로 선언한 전역 변수는 전역 객체 window의 프로퍼티다.

전역 객체 window는 웹페이지를 닫기 전까지 유효하여,
var 키워드로 선언한 전역 변수는 웹 페이지를 닫을 때 까지 유효하다.
즉, var 키워드로 선언한 전역 변수의 생명 주기전역 객체의 생명 주기와 일치한다.


2. 전역 변수의 문제점

1. 암묵적 결합

전역 변수는, 코드 어디서든 참조하고 할당하기 위해서 사용한다.
이것은 모든 코드가 전역 변수를 참조하고 변경할 수 있는 암묵적 결합을 허용한다는 것과 같다.

변수의 유효 범위가 클수록 가독성이 떨어지고, 의도치 않게 상태가 변경될 수 있다.

2. 긴 생명 주기

  • 전역 변수 생명주기 길다 | 메모리 리소스를 오랜 기간 소비 | 변수를 변경할 시간, 기회가 많음
  • 지역 변수 생명주기 짧음 | 메모리 리소스를 짧은 기간 소비 | 변수를 변경할 시간, 기회가 적음

심지어 var 키워드로 선언한 변수는 변수의 중복 선언을 허용한다.
결국 생명 주기가 긴 전역 변수의 이름이 중복될 가능성이 있고, 의도치 않은 재할당이 발생할 수 있다.

3. 스코프 체인 상에서 종점에 존재

전역 변수는 스코프 체인 상 종점에 존재한다.
이는 변수 검색 시, 전역 변수가 가장 마지막에 검색된다는 것을 뜻한다.
즉, 전역 변수의 검색 속도가 가장 느리다.

검색 속도의 차이는 크진 않지만 속도의 차이는 분명히 존재한다.

4. 네임스페이스 오염

자바스크립트는 파일이 분리되어 있어도, 하나의 전역 스코프를 공유한다.
따라서, 다른 파일 내에서 동일한 이름으로 명명된 전역 변수나 전역 함수가 같은 스코프 내에 존재할 경우, 예상치 못한 결과가 발생할 수 있다.


3. 전역 변수의 사용을 억제하는 방법

전역 변수를 절대 사용하지 말라는 것은 아니지만,
반드시 사용해야 할 이유가 없다면 지역 변수를 사용해야 한다.

다음은 전역 변수 사용을 억제하는 방법이다.

1. 즉시 실행 함수

즉시 실행 함수는 함수를 정의하는 동시에 단 한번만 호출된다.
모든 코드를 즉시 실행 함수로 감싸면, 모든 변수는 즉시 실행 함수의 지역 변수가 된다.

전역 변수가 생성되지 않아, 라이브러리 등에서 자주 사용된다.

2. 네임스페이스 객체

네임스페이스 역할을 담당할 객체를 전역에 생성 후,
전역 변수처럼 사용하고 싶은 변수를 프로퍼티에 추가하는 방법이다.
네임스페이스 객체에 또 다른 네임스페이스 객체를 프로퍼티로 추가하여, 계층적으로 구성하는 것도 가능하다.

식별자 충돌을 방지할 순 있지만, 네임스페이스 객체 자체가 전역 변수에 할당되므로 그닥 유용하진 않다.

var MYAPP = {}; // 전역 네임스페이스 객체

MYAPP.name = 'sozzang';
console.log(MYAPP.name); // sozzang

MYAPP.person = { // 네임스페이스 객체를 프로퍼티로 추가
  address: 'Seoul'  
}

3. 모듈 패턴

모듈 패턴클로저를 기반으로 동작하는데, 24장에서 클로저를 자세히 살펴보기 전까지는 감만 잡아보자.

모듈 패턴은 클래스를 모방해서 '관련 있는 변수와 함수'를 모아 즉시 실행 함수로 감싸 하나의 모듈을 만든다.
모듈 패턴을 통해 전역 변수의 억제캡슐화까지 구현이 가능하다.

📌 캡슐화

캡슐화는 '객체의 상태를 나타내는 프로퍼티'와 '프로퍼티를 참조, 조작할 수 있는 동작인 메서드'를 하나로 묶는 것이다.
이때 캡슐화는 객체의 특정 프로퍼티나 메서드를 감출 목적으로 사용하기도 한다.

📌 정보 은닉

대부분의 객체 지향 프로그래밍 언어는,
클래스를 구성하는 멤버에 접근 제한자(public, private, protected 등)를 사용하여 공개 범위를 한정할 수 있다.
이를 통해, 클래스 외부에는 제한된 접근 권한을 제공하며, 원하지 않은 외부의 접근으로부터 내부를 보호할 수 있다.

  • public 외부에서 데이터 또는 메서드에 접근이 가능함
  • private 외부에서 데이터 또는 메서드에 접근이 불가능하고 내부에서만 접근 가능

하지만 자바스크립트접근 제한자를 제공하지 않는다.
그래서 자바스크립트에서 (한정적이긴 하지만) 정보 은닉을 위해 모듈 패턴을 사용한다.

4. ES6 모듈

  • ES6 모듈 파일 자체의 독자적인 모듈 스코프를 제공함

ES6 모듈을 사용하면, 전역 변수를 사용할 수 없다.
따라서 모듈 내에서 var 키워드로 선언한 변수는, 전역 변수도 window 객체의 프로퍼티도 아니다.

📌 사용 방법

script 태그에 type="module" 어트리뷰트를 추가한다.
모듈의 파일 확장자는 mjs가 권장된다.

<script type="module" src="app.mjs"></script>

📌 브라우저 호환성

  • 모던 브라우저 ES6 모듈 사용 가능
  • 구형 브라우저 ES6 모듈 사용 불가능

사용 가능한 모던 브라우저에서도, 트랜스파일링이나 번들링이 필요하다.
따라서 아직까지는 ES6 모듈 기능보단, Webpack 등의 모듈 번들러를 일반적으로 사용한다.

이는 48장 모듈과 49장 Webpack을 이용한 개발 환경 구축에서 자세히 알아보자.
49장까지 있다는 사실에 가슴이 조금 답답해진다 ... 천천히 공부해보자 !


오늘은 전역 변수의 문제점에 대해 살펴보았다.
사실 이전에는 다른 코드 블록에서 두 번 이상 사용되면, 최대한 전역 변수로 사용하려고 노력했었다.
그래서 전역 변수가 상당히 많았다 ... ! 특별한 이유가 없다면 전역 변수 사용을 제한하자 !

그리고 ES6 모듈에 대해서도 살짝 다뤘다.
웹 최적화를 위해 요 ES6 모듈로 변경하려고 노력했었는데, 이미 짜여진 코드를 바꾸기가 많이 어려웠었다.
아직은 살~짝 다룬거지만 ! 꼭 끝까지 다이빙을 성공해서 모듈도 잘 뿌셔야겠다 !

profile
" 퍼블리셔에서 프론트엔드로 Level up 중 ... 💨 "

0개의 댓글