Pandaman Blog

[JS] Javascript의 Scope (스코프) 본문

Front end/Javascript

[JS] Javascript의 Scope (스코프)

oyg0420 2020. 2. 2. 16:34

Javascript Scope(스코프)

컴퓨터 프로그래밍에서 스코프는 변수가 유효성을 갖는 영역을 가리킨다.

Scope는 '범위'라는 의미로 사용될 수 있는데요, 즉 '변수에 접근할 수 있는 범위'를 의미합니다.

javascript의 Scope

자바스크립트의 Scope의 다음과 같이 구분할 수 있습니다.

1) 전역 스코프
2) 함수 스코프
3) 블록 스코프

전역스코프

전역에 변수를 선언하면 이 변수는 전역 스코프를 갖는 변수가 됩니다. 다시말해서, 블록밖({})이나 함수외부에 변수가 선언되었다면, 이 변수는 전역 스코프를 갖는다고 말합니다. 전역 스코프를 갖는 전역변수는 코드 어디에서든 참조가 가능합니다.
또한 var 키워드로 전역에 선언한 변수는 전역 객체(window)의 프로퍼티가 됩니다.

아래는 전역 변수를 선언하고 블록과 함수내부에서 전역 변수에 접근하는 예제입니다.

let g = 'it is global variable';

{
 alert(g);
}

function test() {
  alert(g);
}
test();

위의 코드를 실행하면 블록과 함수내부에서 전역변수 g에 접근할 수 있는것을 알 수 있습니다.

하지만 전역스코프에 선언된 변수들에게 의존하게 된다면 문제가 생길 가능성이 있습니다. 언제, 어디서 전역에 선언된 변수들이 변경되어 개발자의 의도대로 작동하지 않을 수 있습니다.

var name = '팬더맨';
var age = 31;

var name = '김철수';
function Pandaman() {
 alert(`i am ${name} and ${age}years old.`);
}
Pandaman();

전역변수 name팬더맨문자열이 할당되었고, 다시 name이 재선언되어 김철수라는 문자열이 할당되어 값이 변경되었습니다.

이처럼 동일 스코프에서 var 키워드를 사용할 경우 변수명이 동일해도 에러가 발생하지 않습니다. 그 이유는 var 키워드로 변수을 선언할때 호이스팅이 일어나 가장 위로 올라갑니다. 아래와 같이 표현할 수 있겠습니다.

var name;
name = '팬더맨';
name = '김철수';

따라서 Pandaman함수를 호출했을 때, i am 김철수 and 31yaers old.라는 알림이 나타날 것 입니다.

var키워드의 이러한 문제점을 예방하기 위해, ES6에서는 let, const 키워드가 도입되었습니다. constlet 키워드는 동일한 변수명으로 선언하면 에러가 발생합니다.

let name = '팬더맨';
let name = '김철수'; // Uncaught SyntaxError: Identifier 'name' has already been declared

함수 스코프

함수 내부에서 변수를 선언하면, 그 변수는 함수 내부에서만 접근할 수 있습니다. 함수 내부에서 선언된 변수가 갖는 스코프를 함수 스코프라고 합니다. 함수내부에서 var키워드로 선언한 변수는 함수 스코프를 갖습니다. 물론 블록에서 let, const키워드로 선언한 변수는 블록 스코프를 갖습니다.(참고로 함수도 블록을 갖습니다.)

function pandaman() {
  var name = 'pandamanLocal';
  alert(name);
}
pandaman();
alert(name);

pandaman함수내부에서 name이라는 이름의 변수를 선언하고 'pandamanLocal'문자열을 할당했습니다. pandaman를 호출하면 함수 내부의 'pandamanLocal'문자열을 담고 있는 name변수를 참조하는것을 확인할 수 있습니다. 그리고 전역에서 alert(name)를 실행하면 undefined 알림이 뜨는것을 확인할 수 있었습니다. 이처럼 함수 내부에서 선언한 변수는 함수 스코프를 갖는것을 알 수 있었습니다.

블록 스코프

ES6이전 즉 letconst 키워드가 도입되기 전에는 자바스크립트에서 블록 스코프가 존재하지 않았습니다. 하지만 letconst키워드가 도입되면서 자바스크립트에서도 블록 스코프라는 개념이 생겼습니다.
블록 스코프는 블록({})에서 let 또는 const로 변수를 선언한다면, 그 변수들은 블록({})내부에서만 접근 할 수 있습니다.

if(true){
  let test = 'it is block scope variable';
}
alert(test); // VM3423:4 Uncaught ReferenceError: test is not defined

위의 예제에서는 if문 블록에서 let으로 변수 test를 선언했을때 변수의 스코프를 확인하는 예제입니다. 블록외부에서 블록내부에 선언된 변수 test에 접근하지 못하고 에러는 발생시키는것을 확인할 수 있습니다.
그렇다면 블록에서 var로 선언한 변수는 블록 스코프를 갖지 못하는 것일까요? 아래 코드는 위의 코드에서 letvar로 변경한 코드입니다.

if(true){
  var test = 'it is block scope variable';
}
alert(test);

'it is block scope variable'알림이 뜨는것을 확인할 수 있었습니다. 블록내(함수블록제외)에서 var로 선언한 변수에 대해서는 블록 스코프를 갖지 않고 블록 외부의 스코프를 갖는다는 것을 알 수 있었습니다. 따라서 블록 스코프를 갖는 변수를 선언하기 위해서는 블록에서 let, const로 변수를 선언해야합니다.

호이스팅

변수 호이스팅

let을 도입하기전에는 var를 사용하여 변수를 선언했습니다.
var로 선언한 변수는 선언하기도 전에 사용 할 수 있습니다. 아래는 그 예입니다.

alert(x);
var x = 'var test';
alert(x);

(function() {
alert(name); // undefined;
var name = 'pandaman';
})();

위 처럼 작동하는 이유는 무엇일까요? var로 선언한 변수를 끌어올리는 Hoisting때문인데요, 함수내부에서나, 전역 스코프 전체에서 var로 선언된 변수를 맨위로 끌어올립니다.

하지만 let, constHoisting이 적용되지 않습니다. 동일한 코드에 varlet으로 변경하겠습니다.

x; // VM2868:1 Uncaught ReferenceError: Cannot access 'x' before initialization
alert(x);
let x = 'let test';
alert(x);

(function() {
alert(name); // undefined;
let name = 'pandaman';
})(); // VM1001:2 Uncaught ReferenceError: Cannot access 'name' before initialization

위 코드를 실행하면 예상한대로 에러가 발생하는것을 볼 수 있습니다.

Lexical Scope(어휘적 범위 지정)

Lexical Scope는 소스코드가 작성된 문맥에 의해 스코프가 결정된다고 합니다. 다시말해서 Lexical Scope는 함수를 호출할때 생기는것이 아니라, 선언할때 생깁니다. 예제를 통해서 알아보겠습니다.

var name = 'pandaman';

function getGlobalName() {
  alert(name);
}

function getName() {
  name = 'oyg0420';
  getGlobalName();
}

getName();

위와 같은 코스에서 getName()함수를 호출하면 과연 어떤 값이 찍힐까요? 바로 oyg0420입니다. getName를 호출하고 내부함수getGlobalName()를 호출하기 전에 전역변수name을 'oyg0420'으로 변경했기 때문입니다.

아래 코드에서는 위의 코드의 getName()함수의 name = 'oyg0420';var name = 'oyg0420';로 변경했습니다.

var name = 'pandaman';

function getGlobalName() {
  alert(name);
}

function getName() {
  var name = 'oyg0420';
  getGlobalName();
}

getName();

결과는 어떤 값이 나올까요? 바로 'pandaman'이 로그에 찍힙니다. 그 이유를 설명드리겠습니다. 위에서 스코프는 함수를 호출할때 생기는것이 아니라, 선언할때 생깁니다라고 말씀드렸습니다. getGlobalName()가 처음 선언될때, 함수내에 있는 변수 name은 자신의 스코프로 부터 가장 가까운곳에 있는 변수를 참조합니다. 그래서 가장 가까운 var name = 'pandaman';를 가리키게 되고 getName()의 지역변수 name을 가리키지 않는 이유입니다.

lexical scope는 보통 동적스코프와 많이 비교를 하는데요, 그 이유는 스코프를 결정하는 규칙이 다릅니다. 동적스코프는 함수가 호출될 때 스코프가 결정되고, lexical scope는 위에서 말했듯이, 선언될때 결정되기 때문입니다.

위의 코드를 동적 스코프 규칙을 따르면 어떻게 될까요?
먼저 getName()함수가 호출됩니다. 그리고 getName()함수의 내부함수 getGlobalName()가 실행하게 됩니다. getGlobalName()에는 name변수가 있는데요, 이 변수는 동적스코프규칙에 따르면 함수가 호출될때 스코프가 결정됩니다. 따라서 getGlobalName()가 호출될때 name변수는 자신의 스코프로 부터 가장 가까운곳에 있는 변수를 참조하여, getName()의 지역 변수를 참조하게 됩니다. 결론적으로 'oyg0420' 문자열이 출력되는것을 확인 할 수 있습니다.

다시한번 말씀드리지만, 동적스코프는 함수가 호출될 때 스코프가 결정되고, lexical scope는 선언될때 스코프가 결정됩니다. 스코프에 관련한 내용은 눈으로 보기만해서는 이해가 쉽지 않습니다. 여러 실험과 이해가 필요한 주제입니다.

읽어주셔서 감사합니다.

 

파트너스 활동을 통해 일정액의 수수료를 제공받을 수 있음

Comments