2023. 3. 30. 14:36ㆍ개발 관련 책 읽기/모던 자바스크립트 Deep Dive
✅ 아래 내용들에 대해서 알아보자
- 스코프
- 스코프 종류
- 전역 변수
- 전역 변수의 생명주기와 문제점
스코프
스코프(유효 범위)는 변수, 함수와 깊은 관련이 있으므로 주의 깊게 봐야 할 것 중 하나이다. var 키워드로 선언한 변수와 let/const 키워드로 선언한 변수의 스코프도 다르게 동작한다.
변수는 자신이 선언된 위치에 따라 유효 범위, 즉 다른 코드가 변수 자신을 참조할 수 있는 범위가 결정된다. 변수뿐 아니라 모든 식별자는 자신이 선언된 위치에 의해 자신을 참조할 수 있는 유효 범위가 결정된다. 이를 스코프라 한다.
var var1=1;
if(true){
var var2=2; //코드 블록 내 선언 변수
if(true){
var var3=3; //중첩된 코드 블록 내에서 선언한 변수
}
}
function foo(){
var var4=4; //함수 내 선언한 변수
function bar(){
var var5=5; //중첩된 함수 내에서 선언한 변수
}
}
console.log(var1); //1
console.log(var2); //2
console.log(var3); //3
console.log(var4); //ReferenceError: var4 is not defined
console.log(var5); //ReferenceError: var5 is not defined
아래 코드를 보면 코드 가장 바깥 영역과 foo2 함수 내부에 동일한 x라는 변수를 선언했다.
이때 JS엔진은 이름이 같은 두 변수중 어떤 변수를 참조해야 할 것인지 정해야 하는데 이를 식별자 결정이라 고한다.
JS엔진은 코드를 실행할 때 코드의 문맥(context)을 고려한다. "코드가 어디서 실행되며 주변에 어떤 코드가 있는지"를 확인하는데 이를 렉시컬 환경이라고 한다. 이를 구현한 것이 실행 콘텍스트이며, 모든 코드는 실행 콘텍스트에서 평가되고 실행된다.
따라서, 스코프를 통해 변수 이름의 충돌을 방지할 수 있고 동일한 이름의 변수를 사용할 수 있게 된다.
var x='global';
function foo2() {
var x = 'local';
console.log(x); //local
}
foo2();
console.log(x); //global
//var, let/const 차이
function f(){
var x=1;
//var 키워드로 선언된 변수는 같은 스코프 내에서 중복 선언이 가능하다.
//아래 변수는 JS 엔진에 의해 var 키워드가 없는 것처럼 동작한다(x=2;)
var x=2;
}
function f2(){
//let 이나 const 키워드로 선언된 변수는 같은 스코프 내에서 중복 선언이 불가능하다!
//SyntaxError: Identifier 'x' has already been declared
let x=1;
let x=2;
const y=1;
const y=2;
}
스코프 종류
구분 | 설명 | 스코프 | 변수 |
전역 | - 코드의 가장 바깥 영역 - 어디서든 참조 가능 - 전역 스코프에 선언된 변수와 함수는 모두 전역 객체인 window 프로퍼티로 등록된다. |
전역 스코프 | 전역 변수 |
지역 | - 함수 몸체 내부 - 자신의 지역 스코프와 하위 지역 스코프에서 유효하다(inner에서 outer 참조 가능) |
지역 스코프 | 지역 변수 |
스코프 체인
JS에서는 스코프 체인(Scope Chain)을 이용하여 변수와 함수를 검색한다. 스코프 체인은 변수와 함수를 검색할 때 현재 스코프에서 없을 경우, 상위 스코프를 검색하는 방식이다. 스코프 체인은 함수가 중첩될 경우 내부 -> 외부 함수의 변수나 함수를 접근할 수 있도록 한다.
- JS 엔진은 스코프 체인을 통해 변수를 참조하여 상위 스코프 방향으로 선언된 변수를 검색한다. 이를 통해 상위 스코프에서 선언한 변수를 하위 스코프에서 참조할 수 있다.
- 스코프 체인은 아래 그림과 같이 스코프 체인을 물리적으로 렉시컬 환경을 실제로 생성한다. 변수 선언이 실행되면 변수 식별자가 이 자료구조(렉시컬 환경)에 key로 등록되고, 변수 할당이 일어나면 이 자료구조의 변수 식별자에 해당하는 값을 변경한다. 검색도 렉시컬 환경에서 이뤄진다.
- 하위 -> 상위 스코프로 참조가 가능하지만, 상위 -> 하위스코프로는 참조를 할 수 없다
함수 레벨 스코프
var 키워드로 선언된 변수는 오로지 함수의 코드 블록(함수 몸체)만을 지역 스코프로 인정한다. 이러한 특성을 함수 레벨 스코프라고 한다.
var 키워드로 선언된 변수는 블록 레벨 스코프를 인정하기 않기 때문에 전역변수로 인식하게 된다. (아래 코드 참고)
var x=1;
if(true){
/**
* var 키워드로 선언된 변수는 함수 코드 블록만을 지역 스코프로 인정한다.
* 함수 밖에서 var 키워드로 선언된 변수는 코드 블록 내에서 선언되었다 할지라도 모두 전역 변수다.
* 따라서 x는 전역 변수다. 이미 선언된 전역 변수 x가 있으므로, 중복 선언된다.
* 이는 의도치 않게 변수 값이 변경되는 부작용을 발생시킨다.(1 -> 10으로 값 재할당)
*/
var x=10;
}
console.log(x); //10
렉시컬 스코프
아래 코드의 bar2 함수의 상위 스코프가 무엇일까? 두 가지 패턴을 예측할 수 있다.
1. 함수를 어디서 호출했는지에 따라 함수의 상위 스코프를 결정한다. -> bar 함수의 상위 스코프는 foo 함수의 지역 스코프와 전역 스코피일 것이다.
2. 함수를 어디서 정의했는지에 따라 함수의 상위 스코프를 결정한다. -> bar 함수의 상위 스코프는 전역 스코프일 것이다
첫 번째 방식을 동적 스코프라고 한다. 함수가 호출되는 시점에 동적으로 상위 스코프를 결정해야 하기 때문에 동적 스코프라고 한다.
두 번째 방식을 렉시컬 스코프 또는 정적 스코프라 한다. 동적 스코프 방식처럼 상위 스코프가 동적으로 변하지 않고 함수 정의가 평가되는 시점에 정적으로 결정되기 때문에 정적 스코프라 부른다. 자바스크립트를 비롯한 대부분 프로그래밍 언어는 정적 스코프를 따른다.
다시 코드로 돌아와서 보면 JS는 정적 스코프를 따르므로 bar2 함수는 전역에서 정의된 함수이다. 따라서 bar2 함수는 전역 코드가 실행되기 전에 먼저 평가되어 함수 객체를 생성하기에 상위 스코프를 전역 스코프로 인식하며 값이 1이 출력되는 것을 확인할 수 있다.
var x2=1;
function foo2(){
var x2=10;
bar2();
}
function bar2(){
console.log(x2);
}
foo2(); //1
bar2(); //1
전역 변수
전역 변수의 무분별한 사용은 위험하다. 전역 변수의 생명 주기는 애플리케이션의 생명 주기와 같다. 하지만 함수 내부에 선언된 지역 변수는 함수가 호출되면 생성되고 종료되면 소멸한다. 즉, 지역 변수의 생명 주기는 함수의 생명 주기와 일치한다.
일반적으로 함수가 종료되면 함수가 생성한 스코프도 소멸한다. 하지만 누군가 스코프를 참고하고 있다면 스코프는 해제되지 않고 생존하게 된다.
아래 코드를 보면 foo 함수 내부에서 x를 출력하면 undefined가 나오게 된다. 전역 변수 x를 참조한 것이 아니라 지역 변수 x를 참조해 값을 출력한다. 즉, 지역 변수는 함수 전체에서 유효하다. 단 변수 할당문이 실행되기 이전까지 undefined값을 갖게 된다.
var x= 'global';
function foo(){
console.log(x); //지역변수 호이스팅으로 인한 지역 변수 x 참조, undefined
var x='local';
}
foo();
console.log(x); //전역변수 x, global
호이스팅은 스코프를 단위로 동작한다. 전역 변수 호이스팅은 전역 변수의 선언이 전역 스코프의 선두로 끌어올려진 것처럼 동작한다.
따라서 전역 변수는 전역 전체에서 유효하게 된다.
지역 변수의 호이스팅은 지역 변수의 선언이 지역 스코프의 선두로 끌어올려진 것처럼 동작한다. 따라서 지역 변수는 함수 전체에서 유효하다.
즉, 호이스팅은 변수 선언이 스코프의 선두로 끌어 올려진 것처럼 동작하는 JS의 고유의 특징을 말한다!
전역 변수의 생명 주기
var키워드로 선언한 전역 변수는 전역 객체의 프로퍼티가 된다. 이는 전역 변수의 생명 주기가 전역 객체의 생명 주기와 일치한다는 것을 말한다.
전역 객체는 코드가 실행되기 이전 단계에 JS 엔진에 의해 어떤 객체보다도 먼저 생성되는 특수한 객체다.
클라이언트 사이드 환경(브라우저)에서는 windows, 서버 사이드 환경(node.js)에서는 global 객체를 의미함
전역 변수의 문제점
- 모든 코드가 전역 변수를 참조하고 변경할 수 있는 암묵적 결합을 허용하는 것이다. 이로 인해 상태가 변경되고 코드의 가독성은 나빠진다.
- 전역 변수는 생명 주기가 길다. 따라서 메모리 리소스도 오래 기간 소비한다.
- 전역 변수는 스코프 체인 상 종점에 존재한다. 이는 변수를 검색할 때 전역 변수가 가장 마지막에 검색된다는 것을 말한다. 즉, 전역 변수의 검색 속도가 가장 느리다.
- JS의 가장 큰 문제점 중 하나는 파일이 분리되어 있다 해도 하나의 전역 스코프를 공유한다는 것이다. 이로 인해 예상치 못한 결과를 가져올 수 있다.
모듈 패턴
모듈 패턴은 클래스를 모방해서 관련이 있는 변수와 함수를 모아 즉시 실행 함수로 감싸 하나의 모듈을 만든다.
모듈 패턴은 전역 변수의 억제는 물론 캡슐화(객체의 상태를 나타내는 프로퍼티와 메서드를 하나로 묶는 것)까지 가능하다. -> 은닉화
JS에서는 public, private, protected 등의 접근 제한자를 제공하지 않는다. 모듈 패턴을 통해 private / public을 구분해 은닉화를 구현한다
//모듈 패턴
var Counter=(function (){
//private 변수
var num=0;
return{
increase(){
return ++num;
},
decrease(){
return --num;
}
};
}());
//private 변수는 외부로 노출되지 않는다.(은닉화)
console.log(Counter.num) //undefined
console.log(Counter.increase()); //1
console.log(Counter.increase()); //2
console.log(Counter.decrease()); //1
console.log(Counter.decrease()); //0
'개발 관련 책 읽기 > 모던 자바스크립트 Deep Dive' 카테고리의 다른 글
모던 자바스크립트 Deep Dive - 9 (0) | 2023.04.10 |
---|---|
모던 자바스크립트 Deep Dive - 8 (0) | 2023.03.31 |
모던 자바스크립트 Deep Dive - 6 (0) | 2023.03.29 |
모던 자바스크립트 Deep Dive - 5 (0) | 2023.03.28 |
모던 자바스크립트 Deep Dive - 4 (0) | 2023.03.24 |