본문 바로가기

WEB/JavaScript

[JS] 함수 스코프(Scope)에 대해 좀 더 알아보자

스코프(Scope, 유효범위)란?

자바스크립트에서 스코프를 번역할때 보통 유효범위라고 번역한다. 어떤 변수들이 어느 시점까지 유효한지를 정의한다.스코프엔 두 가지 종류가 있다.

전역 스코프 

자바스크립트 프로그램을 시작 후, 어떤 함수도 호출하지 않았을 때, 실행 흐름은 전역 스코프에 있다.
중괄호 {}의 밖을 전역 스코프라고 하고, 전역 스코프에서 선언된 것들을 전역 변수라고 한다. 전역 변수를 많이 이용한다면 부작용이 발생한다.

const name = 'Kwon'
const age = 21;

function displayName() {
	console.log(`My name is ${name}`); 
} 
function displayAge() {
	console.log(`I was born in ${new Date().getFullYear() - this.age}`); 
} 

displayName(); // My name is Kwon displayAge(); // I was born in 2001

위 코드와 같이 블록 밖에서 선언된 변수나 객체를 전역 스코프라고 한다.

하지만 전역변수를 최소화해야한다.

코드가 길어질걸 염두하고 코딩을 해야한다. 만약 코드가 10000줄 정도 된다면 같은 이름의 변수를 선언할때 필시 오류를 출력하고 자칫 변경하려하면 코드내에서 여러번 사용한 전역변수가 변경되는 것이니 로직상 오류가 생길 수 있다.

즉, 전역 스코프는 자바스크립트 코드내에서 항상 유효하고 다른 함수내에서도 유효하다. 그러므로 주의가 필요하다.

지역 스코프 (Local Scope)

중괄호 {} 안을 지역 스코프라고 한다. 전역 스코프에 선언된 변수가 전역 변수이므로, 지역 스코프에 선언된 변수는 지역 변수라고 이해해도 좋다.
지역 스코프에는 함수 스코프, 블록 스코프 두 가지가 있다.

함수 스코프 (Function scope)

다음은 함수 스코프를 보겠다. 함수 스코프는 ES6이전(선언에 let, const는 없고 var만 사용하던 시절)까지 자바스크립트가 따르던 스코프라고 한다. 
함수스코프는 함수 내부에서 선언한 변수는 지역 변수이며 함수 외부에서 선언한 변수는 모두 전역 변수로 취급한다.

var x = 0;
{
  var x = 1;
  console.log(x); // 1
}
console.log(x);   // 1

let y = 0;
{
  let y = 1;
  console.log(y); // 1
}
console.log(y);   // 0

let으로 선언한 변수 y를 찍는 실행 결과는 당연하게 보였지만, var로 선언한 x를 찍는 두 번째 console.log 의 실행 결과는 순간적으로 헷갈릴 때가 있다. var은 함수 스코프를 따르기 때문에 첫 번째로 선언된 x와 두 번째로 선언된 x 모두 메인 함수의 전역 공간에 선언되어 있는 것 이다. 두 번째로 선언된 x가 값을 덮어 씌운 것.

 

var은 ES6 등장 이후 더이상 쓰지 않으며 변수에 저장된 값이 변경되는 값이라면 let을, 저장만하고 사용하는 값이라면 const를 이용해 선언한다.

스코프 체인(Scope Chain)이란

자바스크립트 엔진은 식별자를 찾을 때 일단 자신이 속한 스코프에서 찾고 그 스코프에 식별자가 없으면 상위 스코프에서 다시 찾아 나간다. 

이 현상을 스코프 체인 이라고 하며 스코프가 중첩되어있는 모든 상황에서 발생한다.

var x = 1;

function foo(){
    console.log(x); // -> 현재 자신의 스코프(foo 함수 내부)에 x가 없지만 상위 스코프인 전역에서 x를 찾는다.
}

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

렉시컬 스코프(Lexical Scope)

만약 함수가 중첩되어 있을 때, 앞서 설명한 스코프 체인때문에 내부 함수에 찾는 식별자가 없다면 상위 스코프에서 식별자를 찾아 나간다. 아래 예시 코드의 실행 결과를 예측해보자.

function outer(){
    var x = 1;

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

    inner();
}

outer(); // 1

outer();의 실행 결과는 1이다. outer 함수 내부에서 inner 함수를 호출하는데, inner함수에는 x가 없기 때문에 상위 스코프인 outer함수에서 x를 찾는다(스코프 체인).

 

여기서 inner함수의 상위 스코프가 outer함수라는 것은 어떻게 결정된 것일까? inner함수가 outer함수 내부에 선언되어 있기 때문일까 아니면 inner함수를 호출한 곳이 outer함수 내부이기 때문일까

함수의 상위 스코프를 결정하는데에는 두 가지 방법이 있다. 첫 번째 방법은 동적 스코프 함수가 어디서 호출했는지에 따라 상위 스코프를 결정하고, 두 번째 방법은 렉시컬 스코프 함수가 어디서 선언되었는지에 따라 상위 스코프를 결정한다. 아래의 예시에서 동적 스코프라면 foo()의 실행 결과는 10이고, 렉시컬 스코프라면 foo()의 실행 결과는 1이다.

 

var x = 1;

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

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

foo(); // 10? 1?
bar(); // 1

실행해보면 렉시컬 스코프를 따른다는 것을 확인할 수 있다.

bar함수를 호출한 곳은 foo함수 내부였지만, bar함수가 선언된 곳은 전역이기 때문에 x는 전역에 선언된 1의 값을 가진다.

즉, 상위 스코프를 결정하는데에 자바스크립트는 렉시컬 스코프를 따른다.

렉시컬 스코프란 자신이 선언된 위치를 기준으로 상위 스코프를 결정한다. foo안에서 호출되었지만 bar는 foo 밖에서 선언되었기 때문에 bar는 전역 변수를 따른다.

블록스코프(Block Scope)

변수를 {} 괄호 안에 const, let 키워드로 선언했을 떄, {} 괄호 안에서만 이 변수에 접근할 수 있다.

아래 예제에서 hello는 {}괄호 안의 스코프를 갖는다.

{
  const hello = 'Hello CSS-Tricks Reader!'
  console.log(hello) // 'Hello CSS-Tricks Reader!'
}

console.log(hello) // Error, hello is not defined

 

정말 간단하다. {} 안에서만 변수가 유효하다.

 

leehwarang.github.io/2019/10/07/scope.html