티스토리 뷰

스코프

스코프란 모든 식별자(변수 이름, 함수 이름, 클래스 이름 등)는 자신이 선언된 위치에 의해 다른 코드가 자신을 참조할 수 있는(접근할 수 있는) 유효범위가 결정된다. 스코프는 식별자가 유효한 범위이다.

위 그림은 같은 이름의 서로 다른 test.txt 파일이다. 서로 다른 폴더에 있기 때문에 같은 같은 이름을 가질 수 있다. 만약 한 폴더 내에서 새로운 파일을 만들어 중복된 이름을 사용한다면 덮어쓰기가 될 것이다.

testFolder1의 test.txt의 유효범위는 testFolder1이다. 자신의 유효범위 내에서 같은 이름으로 새로운 파일을 저장하려고 한다면 대체가 될 것이고 자신의 유효범위가 아닌 다른 곳에서 같은 이름을 쓰면 영향을 받지 않을 것이다.

변수도 이처럼 영향력이 있는 유효범위, 스코프가 있다.

let user = 'giraffe';

function tall() {
  let friend = 'elephant'; 
  console.log(`${user} and ${friend} are tall!`);
} // giraffe and elephant are tall!

function stripes() {
  let friend = 'zebra'; 
  console.log(`${user} and ${friend} have stripes!`);
} // giraffe and zebra have stripes!

변수 user은 가장 바깥의 공간부터 다른 함수 내부까지, 전역에 영향을 끼친다. 전역 스코프를 가진다.
변수 friend는 함수 내부, 지역적으로 영향을 끼친다. 지역 스코프를 가진다. 다른 지역이기 때문에 변수 이름이 같아도 독립적으로 기능을 수행할 수 있다.

다음으로 이어질 내용은 스코프의 몇 가지 규칙이다.

바깥쪽 스코프에서 선언한 변수는 안쪽 스코프에서 사용 가능하다. 안쪽 스코프에서 선언한 변수는 바깥쪽 스코프에서 사용할 수 없다.

var y = "outer";

function foo() {
  var z = "inner";
  console.log(y); // outer;
  console.log(z); //  inner;
}

foo();
console.log(y); // outer;
console.log(z); // ReferenceError: z is not defined

스코프는 중첩이 가능하다.

var x = "global";

function foo() {
  var x = "local";
  console.log("foo x:", x); // foo x: local

  function deep() {
    console.log("deep x:", x); // deep x: local
  }
  deep();
}

foo();
console.log("global x:", x); // global x: global

지역변수가 전역변수보다 더 높은 우선순위를 가진다.

var x = "global";


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

foo();
console.log(x); // global

자바스크립트 엔진은 같은 이름의 두 개의 변수 중에서 어떤 변수를 참조해야 할 지를 결정해야 한다. 이것을 식별자 결정이라고 한다. foo()함수 내에서 지역변수 x가 따로 있으므로 지역변수가 전역변수보다 더 높은 우선순위를 가지는 규칙에 따라 'local'로 결정될 것이다.

 


 

스코프 체인

함수는 중첩이 될 수 있으므로 스코프도 중첩이 될 수 있다. 러시아 인형 마트료시카처럼 스코프가 계층적 구조를 가질 수 있다. 이 때 외부 함수의 지역 스코프를 상위 스코프 라고 한다(지금까지는 바깥쪽 스코프라고 했다). 모든 스코프는 하나의 계층적 구조로 연결되며, 모든 지역 스코프의 최상위 스코프는 전역스코프이다. 이렇게 계층적으로 연결된 것을 스코프 체인이라고 한다.

변수를 참조할 때 자바스크립트 엔진은 스코프 체인을 통해 지금 변수를 사용할 스코프에서 시작해서 상위 스코프 방향으로 이동하며 변수를 검색한다. 상위 스코프에서 유효한 변수는 하위 스코프에서 자유롭게 참조할 수 있지만 그 반대는 불가능하다. 그렇기 때문에 상단의 첫번째 규칙이 있는 것이다.

스코프 체인은 실행컨텍스트의 렉시컬 환경을 단방향으로 연결한 것이다. 스코프 체인은 실체로 존재한다. 실행컨텍스트에 대한 글에서 다루겠다.

 


 

스코프의 종류

블록 스코프

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

if(true) {
  let j = 0;
  console.log(j);
}

let greeting = name => {
  return `hello ${name}`;
} // 화살표 함수는 함수 스코프가 아닌 블록 스코프이다.

중괄호{}로 둘러싼 범위가 블록 스코프가 된다. C나 자바 등 대부분의 프로그래밍 언어는 함수 몸체 뿐만 아니라 코드 블록(if, for, while, try/catch 등)이 지역스코프를 만든다. 자바스크립트는 함수 스코프만 지원하다가 ES6부터 let, const와 함께 블록 스코프를 도입했다. (그러나 var로 선언한 변수는 블록 스코프가 아닌 함수 스코프를 가진다.)

함수 스코프

function greeting(name) {
  return `hello ${name}`;
}

const goodbye = function (name) {
  return `goodbye ${name}`;
}

함수선언문과 함수표현식으로 선언된 함수는 함수 스코프를 만든다.

렉시컬 스코프

var x = 1;

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

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

foo(); // ?
bar(); // ?

전역에서 호출한 bar()함수는 쉽게 예측이 가능하다. 바깥쪽에서 선언한 x인 1이 출력될 것이다. foo()의 결과는? foo()의 결과는 bar 함수의 상위 스코프가 무엇인지에 따라 결정된다. 선택지는 두가지이다.

  1. 함수를 어디서 호출했는지에 따라 상위 스코프를 결정한다. (동적 스코프)
  2. 함수를 어디서 정의했는지에 따라 상위 스코프를 결정한다. (정적 스코프, 렉시컬 스코프)

자바스크립트를 비롯한 대부분의 언어는 렉시컬 스코프를 갖는다. 함수의 상위 스코프는 언제나 자신이 선언된 스코프이다. 위 예제에서는 bar함수가 함수선언문으로 정의되었기 때문에 함수 전체가 호이스팅되어 foo함수에서 호출 가능하고, 전역 스코프를 기억하여 상위 스코프로 가진다. 그래서 foo()의 결과는 1이다.

렉시컬 스코프는 클로저와도 연관이 깊다. 클로저가 무엇일까?
https://hahagarden.tistory.com/77

 

JS 클로저, 클로저의 활용 사례

클로저 클로저는 함수와 그 함수의 주변환경과의 조합이다. 함수와 그 함수의 주변환경과의 조합. 이 말에 따르면 모든 함수를 그 주변환경과 조합하면 되기 때문에 모든 함수에 대해 클로저라

hahagarden.tistory.com

 

반응형
댓글