모.자 DeepDive 10 _ 스코프

📌 스코프

스코프(scope)란?

모든 식별자(변수 이름, 함수 이름, 클래스 이름 등)는 자신이 선언된 위치에 따라 다른 코드가 자신을 사용할 수 있는 범위가 정해진다. 이를 유효범위라고 한다.
하지만 단순히 식별자를 사용할 수 있는 유효범위를 넘어서 프로그램시에 좀더 유연한 프로그래밍을 할 수 있도록 도와주는데, 이름이 동일한 변수를 두 번 생성했다고 가정했을 때, 스코프는 코드의 문맥에 따라 해당 변수의 값을 지정해 준다. 만약 스코프가 이런 역할을 하지 않는다면, 프로그램을 통틀어 동일 변수 이름은 단 한 번밖에 쓸 수 없는 일이 생기게 된다. 프로그래머에게 변수이름 짓기란 쉬운듯 쉽지 않는 요소임이 분명하기에 스코프가 고마운 존재임에 틀림없다.(물론, 스코프를 잘 알고 있다는 전제하에 말이다😁 )

let x = 'global';
function aboutScope() {
  let x = 'local';
  console.log(x); // local
}
aboutScope();
console.log(x); // global

여기서 조금 의구심이 생길 수도 있다.
아니, 변수이름은 유일해서 단 한 번밖에 사용 못한다며서 왜 또 사용 가능하대?🤔
.
.
.
맞다. 변수이름은 유일하게 사용한다. 하지만 그것의 범위가 스코프 내에서만이다.
유효범위를 벗어나서는 변수이름을 중복되게 사용 할 수 있다. 여러 폴더에 같은 이름으로 파일을 만들 수 있는 것처럼!
(참고로 여기서도 var와 let,const의 차이가 있는데 var사용을 지양하므로, var는 같은 스코프 내에서 중복 허용이 가능하다는 것을 알고만 있자.)

스코프의 종류

전역(global)과 지역(local) 두 가지가 있다.
전역(global)스코프는 보통 온 동네방네에서 다 사용 가능한 범위를 말한다.
지역(local)스코프는 코트블록이나 함수 안에서 선언되어 그 안에서만 사용 가능한 범위를 말한다. 이 때 전역과 지역 모두 생성된 변수가 있다고 하면, 지역 안에서는 내부에서 생성된 변수를 가져다 사용하게 되는데 이는 자바스크립트 엔진이 스코프체인을 통해 어느 변수를 참조할지를 결정하기 때문이다.

스코프 체인

함수 내부에 함수가 존재하는 중첩 함수가 있는 것처럼 스코프도 함수의 중첩에 의해 단계적인 구조를 가질 수 있다.
스코프는 전역변수 <- 중첩함수 외부의 지역변수 <- 중첩함수 내부의 지역변수 이 순으로 체인처럼 연결이 되는데, 이를 '스코프체인(scope chain)'이라 한다. 이렇게 생성된 스코프 체인을 자바스크립트 엔진이 변수 참조시에 이용하게 된다.

자바스크립트 엔진은 사용시 제일 가까운 쪽에 있는 변수를 참조하고 검색을 마치게 된다. 전역변수와 같은 이름의 함수 내부에 선언된 지역변수가 있을 경우, 함수 내부에서 이 변수를 사용하게 되면, 함수 내부에 생성된 변수를 참조하게 된다는 것이다. 이런 방향은 결국 전역과 지역 스코프의 차이 때문에 발생한는데, 전역은 어디서나 사용가능한 반면 지역은 일부 지역에서만 사용가능하기 때문에 지역에서 선언 된 변수를 먼저 찾아서 사용하는 것이다.

렉시컬 스코프

함수의 스코프는 함수를 어디서 호출했는지에 따라 상위 스코프가 결정되는 동적스코프가 있고,
함수를 어디서 정의했는지에 따라 상위 스코프를 결정하는 정적스코프(렉시컬스코프)가 있다.
자바스크립트는 함수를 어디서 정의했는지에 따라 상위 스코프를 결정하는 '렉시컬스코프'이다. 따라서 함수가 호출된 위치는 상위 스코프 결정에 어떠한 영양도 주지 않는다.

let x = 1;
function foo() {
  let x = 10;
  bar();
}
function bar(){
  console.log(x);
}
foo(); // 1
bar(); // 1

위의 bar함수는 전역에서 정의 되었다. 정의되었기 때문에 코드 실행시 먼저 평가되어 함수 객체를 생성하게 되고, 이 때, 전역변수인 x를 상위스코프로 인식하여 그 값을 기억하게 된다. 따라서 호출을 어디서 했냐에 관계없이 기억하고 있는 상위스코프인 x의 값을 출력하게 된다. (이는 클로저와 깊은 관계가 있다.)

📌 전역변수의 문제점

전역변수가 문제가 되는 큰 이유는 바로 긴 생명주기에 있다.
생명주기란 생성되고 소멸되기까지의 주기를 의미하는데, 전역변수는 전역객채(브라우제에서는 window, node.js에서는 globalThis)의 생명주기와 일치한다.

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

생명주기가 길다는 것이 대체 왜 문제가 될까?🤔
.
.
.
바로 그만큼 본래 선언한 값의 변동확룔이 커진다는 것이다.
여기저기서 전역변수를 가져다 사용하면 그 변수의 값을 추적하는데 어려움이 생긴다. 이는 가독성과 직결되며 개발자의 의도와는 다르게 변수가 사용될 확률이 높아진다.

그리고 약간의 차이지만 변수를 찾는 검색속도가 지역변수에 비해 느린다. 이는 스코프체인에서 가장 위쪽에 위치하기 때문이다. (변수를 찾는 순서는 중첩함수내부 지역변수 -> 외부함수 지역변수 -> 전역변수 순이라 했다.)

이런 문제점에도 전역변수를 사용하고 싶다는 사람!!🖐
이렇게 사용하길 바란다.

  • 즉시 실행함수를 통한 변수 선언: 전역변수를 즉시실행함수의 지역변수로 할당한다.
(function () {
  let zeke = 100;
  let min = 30;
  // .... 변수선언ing
}());
  • 네임스페이스 객체 이용: 사용할 변수들을 하나의 객체에 담아놓고 사용한다.
let MYVARS = {};
MYVARS.name = 'zeke';
console.log(MYVARS.name); // zeke
  • 모듈패턴: 클래스를 모방하는 방법으로, 변수와 함수를 모아 즉시 실행 함수로 감싸 하나의 모듈을 만드는 방법이다.(클로저 기능을 통해 전역변수 억제 가능)

  • ES6모듈: 파일 자체의 독자적인 모듈 스코프롤 제공한다.

//사용시
<script type="module" src="name.mjs"></scirpt>

이게 아니라면 함수 내부에 지역변수 선언해서 사용하는게 캡짱임!😁

📌 let, const 키워드와 블록레벨 스코프

var 키워드(라고 쓰고 문제아 라고 읽는다)

var 키워드로 선언된 변수는 같은 스코프 내에서 중복선언이 가능하다. 중복으로 선언시 초기화문(var x = 100)이 있는 변수는 그래도 할당되고, 없는 선언문은 무시된다.
또한 var키워드는 함수의 코드블록만을 지역 스코프로 인정하기 땜문에 if문, for문 안이나 기타 {}문 안에 선언되었더라도 모두 전역변수가 된다. 그리고 고질적인 문제점인 변수호이스팅은 다음을 참고하길 바란다. 변수호이스팅

let 키워드

let 키워드로 이름이 같은 변수를 중복 선언하면 SyntaxError(문법에러)가 발생한다. 이는 let이나 const로 선언한 변수는 같은 스코프 내에서 중복 선언을 허용하지 않기 때문이다. let키워드는 선언과 할당(초기화)이 구분되서 진행되기 때문에 var처럼 선언과 할당을 동시에 하지 않아 선언문 이전에 사용할 수 없다. 즉, 변수 호이스팅이 일어나지 않는것처럼 동작한다. 실제로는 자바스크립트는 모든 선언을 호이스팅 한다. 하지만 ES6에서 도입된 let, const, class를 사용한 선언문은 호이스팅이 발생하지 않는 것처럼 동작할 뿐이다.

let n = 100;
{
  console.log(zeke);//ReferenceError: Cannot access 'n' before initialization
  let n = 50;
}

const 키워드

const는 let과 거의 비슷한 특성을 가지고 있다.
블록레벨 스코프 라는 것과, 변수 호이스팅이 발생하지 않는 것처럼 동작한다는것이다.
다만, 다른점은 const는 상수 선언을 위해 사용하고 재당할당이 금지 된다는점이다.

const zeke = 100;
zeke = 50 // TypeError: Assignment to constant variable.

🤔 그럼 상수는 무엇일까?
변수와 반대되는 개념으로 재할당이 금지된 변수를 말한다. 상수는 변하지 않는 값에 대해 적극적으로 사용을 권장한다. 이는 가독성과 유지보수에 뛰어나기 때문이다. 일반적으로 상수는 대문자로 선언해 상수라고 강하게 부각시켜준다. 여러단어의 결합은 언더스코어(_)를 이용한다.

일반적으로 const로 선언한건 재할당이 불가능 하지만 유일하게 객체를 할당한 경우 값의 변경이 가능하다. 이는 객체 자체를 재할당 하지는 못하지만 객체의 프로퍼티를 생성, 삭제, 변경이 가능하다는 말이다.

블록레벨 스코프

var키워드의 함수레벨 스코프와는 다르게 모든 코드 블록(함수, if문, for문, while문, try/catch문 등)을 지역스코프로 인정한다. 이를 블록레벨 스코프라 한다. let 키워드는 블록레벨 스코프를 따른다.

변수키워드 사용 tip!!

변수 선언에는 기본적으로 const를 사용하고 let은 재할당에 필요한 경웨 한정해 사용하는 것이 좋다. const를 사용하면 의도치 않은 재할당을 방지해 좀 더 안전하기 때문이다.

📌 변수 키워드를 사용할 때 일단 const를 사용하자!! 만약 재할당이 필요하다고 판단 될 경우 그 때 let으로 변경해도 늦지 않다!!!


이 시리즈의 모든 글은 모던 자바스크립트 Deep Dive를 읽고 작성된 글이며, 인용글은 모두 이 책의 내용을 인용했습니다.

좋은 웹페이지 즐겨찾기