Scope는 자바스크립트에서 변수를 관리할 때 쓰이는 중요한 개념이다. 자바스크립트로 코드를 짤 때, 변수의 scope를 이해하는 것은 필수적이다. 이 글에서는 이러한 자바스크립트 scope에 대한 개념을 설명하고, 이해하고자 한다.

 

Scope?

스코프는, 쉽게 말해 변수가 허용되는 범위를 말한다. 코드를 통해 살펴보자. 

 

const message = 'Hello';
console.log(message); // 'Hello'

 

message라는 변수를 선언하고 로그를 찍어보면 당연하게 'Hello'가 출력이 되는 것을 볼 수 있다. 그렇다면 message 선언부를 if문 안에 넣고 로그를 찍으면 어떻게 될까? 

 

if (true) {
  const message = 'Hello';
}
console.log(message); // ReferenceError: message is not defined

 

위처럼 자바스크립트는 message라는 변수가 정의되어 있지 않다고 에러를 출력한다. 왜 이렇게 되는 것일까? 이유는, if라는 하나의 blockmessage변수에 scope를 만든 것이다. 이렇게 되면 message 변수는 오직 생성된 scope내에서만 접근이 가능하다.

 

 

즉, 변수에 대한 접근은 scope에 따라 결정이 된다. 해당되는 scope내에서는 변수 접근이 자유롭지만, scope밖에서는 접근이 불가능하다. 

 

Block scope

자바스크립트에서 letconst를 사용하여 변수를 선언하면 block scope로 정의된다.

 

if (true) {
  // "if" block scope
  const message = 'Hello';
  console.log(message); // 'Hello'
}
console.log(message); // throws ReferenceError

 

첫 번째 console.log는 message가 정의된 if scope 내에 있기 때문에 'Hello'를 출력하고, 두 번째 console.log는 scope범위 밖에 있기 때문에 에러를 출력한다. if뿐만 아니라 for, while문 또한 scope를 생성한다. 

 

for (const color of ['green', 'red', 'blue']) {
  // "for" block scope
  const message = 'Hi';
  console.log(color);   // 'green', 'red', 'blue'
  console.log(message); // 'Hi', 'Hi', 'Hi'
}
console.log(color);   // throws ReferenceError
console.log(message); // throws ReferenceError

 

colormessage는 for문 내에서 선언되었으므로 for 스코프 내에 있다.

 

while (/* condition */) {
  // "while" block scope
  const message = 'Hi';
  console.log(message); // 'Hi'
}
console.log(message); // => throws ReferenceError

 

자바스크립트에서는 그냥 중괄호로 된 코드 블럭을 선언할 수 있다. 이렇게 선언된 코드 블럭 또한 스코프를 생성한다.

 

{
  // block scope
  const message = 'Hello';
  console.log(message); // 'Hello'
}
console.log(message); // throws ReferenceError

 

위의 예시들을 통해 letconstblock scope라는 것을 알았다. 그렇다면 var의 경우는 어떨까?

 

if (true) {
  // "if" block scope
  var count = 0;
  console.log(count); // 0
}
console.log(count); // 0

 

countif block scope에서 선언되었다. 따라서 첫 번째 console.log에서 count가 출력이 되는 것은 당연한 것 같다. 하지만 결과를 보면 scope밖에서 count를 로그에 찍었을 때에도 출력이 되었다. 그 이유는 varletconst와는 달리 Function scope이기 때문이다.

 

Function scope

자바스크립트에서 함수는 var, let, const로 선언된 변수에 scope를 생성한다.

 

function run() {
  // "run" function scope
  var message = 'Run, Forrest, Run!';
  console.log(message); // 'Run, Forrest, Run!'
}

run();
console.log(message); // throws ReferenceError

 

messagerun이 만든 function scope에 들어가 있다. 따라서 두 번째 console.log처럼 함수 밖에서의 접근은 불가능하다. 똑같이, letconst로 선언한 변수도 scope를 생성한다. 그리고 심지어 선언된 function또한 해당된다.

 

function run() {
  // "run" function scope
  const two = 2;
  let count = 0;
  function run2() {}

  console.log(two);   // 2
  console.log(count); // 0
  console.log(run2);  // function
}

run();
console.log(two);   // throws ReferenceError
console.log(count); // throws ReferenceError
console.log(run2);  // throws ReferenceError

 

Nested scope

Scope들은 서로 중첩될 수 있다. 아래 예시에서는 run() 함수가 scope를 생성하고, 그 안에서 if가 또 다른 scope를 생성한다.

 

function run() {
  // "run" function scope
  const message = 'Run, Forrest, Run!';

  if (true) {
    // "if" code block scope
    const friend = 'Bubba';
    console.log(message); // 'Run, Forrest, Run!'
  }

  console.log(friend); // throws ReferenceError
}

run();

 

위와 같이 if스코프는 run()스코프내에 중첩되어 있다. scope가 다른 scope를 포함하고 있으면 포함된 scope는 inner scope, 포함하고 있는 스코프를 outer scope라고 한다. 위 코드의 경우 ifinner scope, run()outer scope이다. 

 

 

그렇다면 중첩된 scope에서 변수 접근은 어떻게 될까? 위 사진과 같이 inner scopeouter scope의 변수들에 접근이 가능하다는 것을 기억하면 된다.

 

Global scope

Global scope는 가장 바깥에 있는 스코프이다. Global scope의 변수들은 어떠한 inner scope들도(Local scope) 접근이 가능하다.

 

<script src="myScript.js"></script>

 

// myScript.js

// "global" scope
let counter = 1;

 

위의 counter는 해당 웹 페이지에 작성된 어떠한 곳에 위치한 자바스크립트에서 접근이 가능하다. Global scope는 자바스크립트의 호스트(browser, Node)가 특정 함수나 기능들을 global variables로서 제공할 수 있게 해준다. 예를 들면, browser에서 windowdocument가 global variables로 제공된다. 또한 Node에서 global variables인 process객체에 접근할 수 있다. 

 

Lexical scope

function outerFunc() {
  // the outer scope
  let outerVar = 'I am from outside!';

  function innerFunc() {
    // the inner scope
    console.log(outerVar); // 'I am from outside!'
  }

  return innerFunc;
}

const inner = outerFunc();
inner();

 

위의 코드에서 마지막 줄인 inner()를 보자. 자바스크립트는 innerFunc()내에서의 outerVarouterFunc()outerVar인 것을 어떻게 알까? Lexical scope때문이다. Lexical scope는 변수의 접근성을 중첩된 function scope의 정적인 위치에 따라 결정하겠다는 의미이다. 즉, inner function scope에서 outer function scope의 변수에 접근할 수 있다.

 

참고

Dmitri Pavlutin의 'JavaScript Scope Explained in Simple Words'

 

 


생강강

,