Scope

S

참고자료

스코프란 무엇인가?

스코프는 변수간의 충돌을 방지해주는 칸막이를 일컫는다. 간단한 예를 들어보자.

function hi() {
  var say = 'HI!';
  console.log(say);
}

function bye() {
  var say = 'BYE!';
  console.log(say);
}

hi(); // HI!
bye(); // BYE!

너무 당연하다. 같은 say변수를 사용하지만, 둘은 다른 변수이다.

다른 영역(scope)에 존재한다

js에서 스코프를 판단할때 주의할점

if (true) {
  var test1 = 'test1';
  if (true) {
    var test2 = 'test2';
  } else {
    var test3 = 'test3';
  }
}

function myFunction() {
  var test4 = 'test4';
}

console.log('test1 :', test1);
console.log('test2 :', test2);
console.log('test3 :', test3); // undefined
console.log('test4 :', test4); // ReferenceError: test4 is not defined

var를 사용하면 블럭({ ~~ })안에서 해당 변수의 스코프가 따로 생성되지 않고, 전역 스코프로 할당된다.

그런데 왜 test3undefined고, test4ReferenceError: test4 is not defined에러가 뜨는걸까?

이것은 JS엔진이 내 코드를 실행하는 과정을 이해하면 알수있다. 이 부분은 다른 글에서 다루도록 하겠다.

전역 스코프와 지역 스코프

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 :', x); // global x
console.log('z :', z); // ReferenceError: z is not defined

함수 몸체 내부에서만 유효한 범위를 지역 스코프라고 부른다. 다시말해서, 해당 스코프 안에 있는 변수들은 외부 스코프에서는 참조할 수 없다. 식별자(변수 등)들이 보호받는 영역 이라고 생각해도 좋을것 같다. 전역 스코프는 어떤 영역에서도 보이는, 접근 가능한 영역을 의미한다.

그런데, 위 코드를 보면 inner함수 안에서 outer함수의 스코프에 있는 z변수를 참조할 수 있는걸 볼 수 있다. 이것은 하위 스코프와 상위 스코프가 연결되어 있고, 이 길을 통해 JS엔진이 z변수를 찾아다니기 때문에 가능한 일이다. 이렇게 하위 스코프와 상위 스코프가 연결되어 있는 상태를 스코프 체인이라고 부른다.

스코프 체인

Scope Chain

이렇게 내부적으로 스코프끼리 연결되어 있기 때문에 inner함수 안에서 x, y, z를 볼 수 있는것이다. 하지만 반대로, 하위 스코프로 내려가서 변수를 찾지는 않는다. 그렇게 되면,,,코드 흐름이 매우 이상해질것이다.

"어라? 이 스코프에 있는 식별자가 아니네~ 위로 올라가서 찾아봐야 겠다!"

by. JS엔진

미리 말해두겠습니다

다음에 실행컨택스트를 보면 햇갈릴거에요. "스코프,,,없이 뭐 다 작동이 되는데? 스코프 개념이 실행컨택스트와 어떻게 연결 되는거지?" 맞아요. 사실, 스코프는 실행컨택스트에서 배울 렉시컬 환경을 의미해요. 스코프 체인은 이 렉시컬 환경을 단방향으로 연결한것이구요. 이름만 다르고, 가리키는건 똑같아요. 다만 여기서는 '함수안에서 정의한 식별자(변수 등)를 보호하기 위해 가림막이 있고, 이것이 해당 함수의 스코프이다!'. 이 개념을 이해하기 위해서 스코프라는 말이 필요한거에요. 실행컨택스트를 배우면 사실 스코프 라는 말도 필요 없어요. 그냥 JS엔진이 어떤식으로 식별자들을 등록하고 관리하고 찾는지 알게되요.

함수 레벨 스코프

JS의 var는 함수 레벨 스코프를 가진다. 다시 말해서, 함수 안에서 정의된 변수는 외부에서 그 변수를 참조할 수 없다.

블록 레벨 스코프

JS의 var는 블록 레벨 스코프에서는 보호받지 못한다. 다시 말해서, if (true) { var a = 10; }에서의 a{ } 밖에서도 참조 가능하다. 보통 다른 언어들은 블록 레벨 스코프를 따르는데, JS의 var는 함수 레벨 스코프를 따른다. 그래서 ES6부터는 블록레벨 스코프를 가지는 변수 식별자인 let, const가 등장했다!. 이제는 var는 거의 안쓰는 추세이다. 개발자의 실수가 발생하는 요인이 되기 때문이다.

for (var i = 0; i < 10; i += 1) {
  console.log(i);
}

console.log(i); // 10

i의 역할은 다 했는데, 아직 살아있다...이는 예상치 못한 오류를 만들 수 있다.

렉시컬 스코프

var x = 1;

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

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

foo(); // 1

(이젠 지겹다 렉시컬!)

JS에서는 이렇게 함수가 실행되는 시점을 기준으로 스코프를 결정하는것이 아니라, 함수가 정의되는 시점을 기준으로 스코프를 결정한다. 다시말해서, bar()를 호출할때 이 함수가 foo안에서 호출됬는지, 어쨋는지는 스코프를 결정하는데 아무런 영향을 끼치지 않는다. 이렇게 함수가 정의되는 시점을 기준으로 생성된(결정된) 스코프를 렉시컬 스코프라고 부른다. 그리고 이렇게 정적으로 스코프가 결정되기 때문에, 상위 스코프도 함수가 정의되는 시점을 기준으로 결정된다. 위 코드에서는 전역 스코프가 bar함수의 상위 스코프가 된다.

Add Comment