본문 바로가기
Javascript/javascript Core

[Javascript 강의] 8강 스코프

by 카리3 2021. 9. 25.

스코프

스코프(scope)는 유효 범위이다. 스코프는 변수 그리고 함수와 깊은 관련이 있다. 함수의 매개변수는 함수 몸체 내부에서만 참조할 수 있고 함수 몸체 외부에서는 참조할 수 없다.

function add(x, y){
 //매개변수는 함수 몸체 내부에서만 참조할 수 있다.
 //즉, 매개변수의 스코프는 함수 몸체 내부다
 console.log(x, y); //2 5
 return x + y;
}

add(2, 5);

//매개변수는 함수 몸체 내부에서만 참조할 수 있다.
console.log(x, y); //ReferenceError: x is not defined
var var = 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

//변수 x는 스코프가 다른 별개의 변수다
var x = 'global';

function foo(){
 var x = 'local';
 console.log(x);
}

foo();

console.log(x);

모든 식별자는 자신이 선언된 위치에 의해 다른 코드가 식별자 자신을 참조할 수 있는 유효 범위가 결정된다. 이를 스코프라 한다. 즉, 스코프는 식별자가 유효한 범위를 말한다.

코드가 어디서 실행되며 주변에 어떤 코드가 있는지를 렉시컬 환경(lexical environment)이라고 부른다. 즉 코드의 문맥(context)는 렉시컬 환경으로 이루어진다. 이를 규현한 것이 실행 컨텍스트(excution context)이며, 모든 컨텍스트에서 평가되고 실행된다.

//var 키워드로 선언된 변수는 같은 스코프 내에서 중복 선언이 허용된다. 
//이는 의도치 않게 변수값이 재할당되어 변경되는 부작용을 발생시킨다.
function foo(){
 var x = 1;
 //var 키워드로 선언된 변수는 같은 스코프 내에서 중복 선언을 허용한다.
 //아래 변수 선언문은 자바스크립트 엔진에 의해 var 키워드가 없는 것처럼 동작한다.
 var x = 2;
 console.log(x); //2
}
foo();

//하지만 let이나 const 키워드로 선언된 변수는 같은 스코프 내에서 중복 선언을 허용하지 않는다.
function bar(){
 let x = 1;
 //let이나 const 키워드로 선언된 변수는 같은 스코프 내에서 중복 선언을 허용하지 않는다.
 let x = 2; //SyntaxError: Identifier 'x' has already been declared
}
bar();

스코프의 종류

구분 설명 스코프 변수
전역 코드의 가장 바깥 영역 전역 스코프 전역변수
지역 함수 몸체 내부 지역 스코프 지역변수
var x = 'global x';
var y = 'global y';

function outer(){
 var z = 'outer's local z';
 
 console.log(x); //global x
 console.log(y); //global y
 console.log(z); //outer's local z
 
 function inner(){
  var x = 'inner's local x';
  
  console.log(x); //inner's local x
  console.log(y); //global y
  console.log(z); //outer's local z
 }
 inner();
}

outer();
console.log(x); //global x
console.log(z); //ReferenceError: z is not Defined

스코프 체인

전역 스코프, outer 지역 스코프, inner 지역 스코프. 이러한 계층 구조를 스코프 체인이라고 한다. 변수를 참조할 때 자바스크립트 엔진은 스코프 체인을 통해 변수를 참조하는 코드의 소코프에서 시작하여 상위 스코프 방향으로 이동하며 선언된 변수를 검색한다. 

스코프 체인은 실행 컨텍스트의 렉시컬 환경을 단방향으로 연결한 것이다. 전역 렉시컬 환경은 코드가 로드되면 곧바로 생성되고 함수의 렉시컬 환경은 함수가 호출되면 곧바로 생성된다. 

함수 레벨 스코프

지역스코프는 코드블록이 아닌 함수에 의해서만 지역스코프가 생성된다. var 키워드로 선언된 변수는 오로지 함수의 코드 블록(함수 몸체)만을 지역 스코프로 인정한다. 이러한 특성을 함수 레벨 스코프라 한다. let, const 키워드는 블록 레벨 스코프를 지원한다.

렉시컬 스코프

1. 동적스코프 : 함수를 어디서 호출했는지에 따라 함수의 상위 스코프를 결정한다.

2. 렉시컬스코프: 함수를 어디서 정의했는지에 따라 함수의 상위 스코프를 결정한다.

자바스크립트는 렉시컬스코프를 따른다.

var x = 1;

function foo(){
 var x = 10;
 bar();
}

function bar(){
 console.log(x);
}

foo(); //1
bar(); //1

 

전역 변수의 문제점

전역변수의 무분별한 사용은 위험하다. 전역 변수를 반드시 사용해야 할 이유를 찾지 못한다면 지역 변수를 사용해야 한다.  지역변수의 생명주기는 함수의 생명 주기와 대부분 일치한다. 

전역객체: 브라우저에서는 window 객체를 의미한다. 전역객체는 표준 빌트인 객체(Object, String, Number, Function...), 호스트객체(Web API), var 키워드로 선언한 전역 변수와 전역 함수를 프로퍼티로 갖는다.

전역변수의 문제점
1)암묵적 결합(implict coupling) : 변수의 유효범위가 크면 클수록 코드의 가독성은 나빠지고 의도치 
  않게 상태가 변경될수 있는 위험성도 높아진다.
2)긴 생명 주기: 메모리 리소스도 오랜 기간 소비한다. var 키워드는 중복 선언을 허용하므로 생명 주기가 
  긴 전역 변수는 변수 이름이 중복될 가능성이 있다.
3)스코프 체인 상에서 종점에 존재: 전역 변수의 검색 속도가 가장 느리다.
4)네임스페이스 오염: 자바스크립트의 가장 큰 문제점 중 하나는 파일이 분리되어 있다 해도 하나의 
  전역 스코프를 공유한다는 것이다. 따라서 다른 파일 내에서 동일한 이름으로 명명된 전역 변수나 
  전역 함수가 같은 스코프 내에 존재할 경우 예상치 못한 결과를 가져올 수 있다.

전역 변수의 사용을 억제하는 방법
전역 변수를 반드시 사용해야 할 이유를 찾지 못한다면 지역 변수를 사용해야 한다. 
변수의 스코프는 좁을 수록 좋다.

1. 즉시 실행함수
모든 코드를 즉시 실행 함수로 감싸면 모든 변수는 즉시 실행 함수의 지역변수가 된다.

(function(){
  var foo = 10;
  //...
}());
console.log(foo); //ReferenceError: foo is not defined

2. 네임스페이스 객체
네임스페이스를 분리해서 식별자 충돌을 방지하는 효과는 있으나 네임스페이스 객체 자체가 전역 변수에 
할당되므로 그다지 유용해 보이지는 않는다.

var MYAPP = {}; //전역 네임스페이스 객체
MYAPP.name = 'Kim';
console.log(MYAPP.name ); //Kim

3. 모듈 패턴
모듈 패턴은 클래스를 모방해서 관련이 있는 변수와 함수를 모아 즉시 실행 함수로 감싸 하나의 모듈을 만든다. 
모듈 패턴은 자바스크립트의 강력한 기능인 클로저를 기반으로 동작한다. 
모듈 패턴의 특징은 전역 변수의 억제는 물론 캡슐화까지 구현할 수 있다는 것이다.

var Conuter = (function(){
 var num = 0;  //private 변수
 
 //외부로 공개할 변수나 메서드
 return {
   increase(){
    return ++num;
   },
   decrese(){
    return --num;
   }
 };
}());

console.log(Counter.num); //undefined
console.log(Counter.increase()); //1

4. ES6 모듈
ES6 모듈은 파일 자체의 독자적인 모듈 스코프를 제공한다.

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

 

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

var 키워드로 선언한 변수의 문제점
1. 변수 중복 선언 허용
2. 함수 레벨 스코프
var 키워드로 선언한 변수는 오로지 함수의 코드 블록만을 지역 스코프로 인정한다.

var x = 1;

if(true){
 var x = 10;
}

console.log(x); //10

3. 변수 호이스팅
var 키워드로 변수를 선언하면 변수 호이스팅에 의해 변수 선언문이 스코프의 
선두로 끌어 올려진 것처럼 동작한다.
let 키워드
1. 변수 중복 선언 금지
2. 블록 레벨 스코프

let foo  = 1;
{
 let foo = 2;
 let bar = 3;
}

console.log(foo); //1
console.log(bar); //ReferenceError: bar is not defined

3. 변수 호이스팅 : let 키워드는 변수 호이스팅이 발생하지 않는다.
예제1)
console.log(foo); //ReferenceError: bar is not defined
console.log(boo); //undefined
let foo;
var boo;

예제2)
//선언단계/초기화 단계(변수 호이스팅)  foo === undefined
var foo;
foo = 1; //할당단계 foo === 1;
예제3)
//선언단계
//일시적 사각지대(TDZ) : ReferenceError
let foo; //초기화단계 foo === undefined
foo = 1; //할당단계 foo === 1

4. 전역 객체와 let
var 키워드로 선언한 전역 변수와 전역 함수, 그리고 선언하지 않은 변수에 값을 할당한 
암묵적 전역은 전역 객체 window의 프로퍼티가 된다. 
let 키워드로 선언한 전역 변수는 전역 객체의 프로퍼티가 아니다. 
window.foo와 같이 접근할 수 없다. let 전역 변수는 보이지 않는 개념적인 블록 내에 존재하게 된다.
const 키워드
상수를 선언하기 위해 사용한다.
1. 선언과 초기화 : 반드시 선언과 동시에 초기화해야 한다.
2. 재할당 금지
3. 상수 : 원시 값을 할당한 경우 변수 값을 변경할 수 없다.
4. const 키워드와 객체
 1) 객체를 할당한 경우 값을 변경할 수 있다.
 2) 객체 재할당은 금지된다.
결론
1. ES6를 사용한다면 var 키워드는 사용하지 않는다.
2. 재할당이 필요한 경우에 한정해 let 키워드를 사용한다. 이때 변수의 스코프는 최대한 좁게 만든다.
3. 변경이 발생하지 않고 읽기 전용으로 사용하는 원시 값과 객체에는 const 키워드를 사용한다.