[JavaScript] 스코프

Jun·2022년 5월 15일

JavaScript

목록 보기
8/13
post-thumbnail

학습 목표

  • 스코프의 의미와 적용 범위를 이해한다.
  • 스코프의 주요 규칙을 이해한다.
  • 전역 스코프와 지역 스코프의 차이를 이해한다.
  • block scope와 function scope의 차이를 이해한다.
  • 변수 선언 키워드(let, const, var)와 스코프와의 관계를 설명할 수 있다.
  • 전역 객체가 무엇인지 설명할 수 있다

스코프(scope)란?

변수(variable)의 유효 범위를 뜻한다.

현재 실행되는 컨텍스트라고도 설명 가능하며, 컨텍스트는 표현식이 "표현"되거나 참조될 수 있음을 뜻한다. 만약 변수 또는 다른 표현식이 "해당 스코프"내에 있지 않다면 사용할 수 없다.

  • 스코프의 규칙
    1. 안쪽 스코프에서 바깥쪽 스코프로는 접근할 수 있지만 그 반대는 불가능하다.
    2. 스코프는 중첩이 가능하다. 스코프는 마치 중첩된 울타리와 같다.
    3. 지역 변수는 전역 변수보다 더 높은 우선순위를 가진다.
var scope = 'global scope';

function foo () {
  var scope = 'function scope';
  var name = 'jun';
  console.log(scope);
}

foo(); // ?
console.log(scope); // ?
console.log(name); // ?
  • foo()
    함수 foo는 변수 scope 를 출력하는 함수이다. 하지만 scope 는 전역에서 한번, 지역에서 한번 선언되어있다. 이 같은 경우 우선순위는 지역변수가 더 높으므로 'function scope' 가 출력되게 된다.

  • console.log(scope)
    변수 scope 를 출력하는 코드이다. 이 코드는 함수 밖에서 작성되었고 그렇기에 지역변수를 출력하는 것이 아닌 전역변수를 출력하게된다.
    그러므로 'global scope' 가 출력되게 된다.

  • console.log(name)
    변수 name 을 출력하는 코드이다. 하지만 namefoo 함수 내에서 선언되었기 때문에 지역변수이고, 전역변수엔 존재하지 않는다. 그렇기에 스코프의 규칙에 따라 ReferenceError: name is not defined 라는 오류가 출력된다.

스코프의 구분

자바스크립트에서 스코프를 구분해 보면 다음과 같이 2가지로 나뉜다.

  • 전역 스코프 (Global scope)
    코드 어디에서든지 참조할 수 있다.
  • 지역 스코프 (Local scope or Function-level scope)
    함수 코드 블록이 만든 스코프로 함수 자신과 하위 함수에서만 참조할 수 있다.

변수 또한 스코프를 갖는다. 변수의 관점에서 스코프를 구분하면 2가지로 나뉜다.

  • 전역 변수 (Global variable)
    전역에서 선언된 변수이며 어디에든 참조할 수 있다.
  • 지역 변수 (Local variable)
    지역(함수) 내에서 선언된 변수이며 그 지역과 그 지역의 하부 지역에서만 참조할 수 있다.

스코프의 종류

  • 블록 스코프 (block scope)
    코드 블록({...})을 기준으로 범위를 구분한다.
if (true) {
  // this is block scope
}

for (let i = 0; i < 10; i++) {
  // this is block scope
}

{
  // this is block scope
}
  • 함수 스코프 (function scope)
    function 키워드가 등장하는 함수 선언식 및 함수 표현식에서 만들어진다.
function foo() {
  // this is function scope
}

let bar = function () {
  // this is function scope
}

❗️ 여기서 주의할 점은 화살표 함수블록 스코프로 취급된다.

let foobar = () => {
  // this is block scope
}

varlet , const

다음 코드를 보자

for (let i = 0; i < 5; i++) {
  console.log(i); // 다섯번 반복
}
console.log('final i: ', i); // ReferenceError

블록 스코프 내에서 선언한 i 가 스코프 바깥에서 호출되었기 때문에 ReferencError 오류가 발생한다.

for (var i = 0; i < 5; i++) {
  console.log(i); // 다섯번 반복
}
console.log('final i: ', i); // output : 5

변수 i 는 블록 스코프 안에서 선언되었는데 스코프 바깥에서 사용할 수 있었다.

이와 같이 var 키워드로 선언한 변수는 블록 스코프를 무시하고, 함수 스코프만 따른다.
이 때, 화살표 함수의 블록 스코프는 무시하지 않는다.

또한 var 키워드는 let 과 달리 재선언이 가능하다

var num = 1;
var num = 2; // 재선언 가능

let num2 = 1;
let num2 = 2; // SyntaxError: Identifier 'num2' has already been declared

const 키워드 또한 let 과 비슷하나 값의 재할당이 불가능하다. 변하지 않는 값, 즉 상수(constant)를 정의할 때 사용한다.

정리해서 보자면 다음과 같다.

letconstvar
유효 범위블록 스코프 및 함수 스코프블록 스코프 및 함수 스코프함수 스코프
값 재할당가능불가능가능
재선언불가능불가능가능

렉시컬 스코프

let name = 'jun';
function foo() {
  let name = 'JUN';
  bar();
}

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

foo(); // output : 'jun'
bar(); // output : 'jun'

위 코드의 실행 결과는 함수 bar 의 상위 스코프가 무엇인지에 따라 결정된다. 다음과 같이 두가지 패턴을 예측할 수 있다

  1. 함수를 어디서 호출하였는가?
  2. 함수를 어디서 선언하였는가?

첫번째 방식을 따른다면 bar 의 상위 스코프는 함수 foo 와 전역일것이다.
두번째 방식을 따른다면 bar 의 상위 스코프는 전역일것이다.

첫번째 방식을 동적 스코프(Dynamic scope) 라고 하며
두번째 방식을 렉시컬 스코프(Lexical scope) 또는 정적 스코프(Static scope) 라고 한다.

JavaScript의 경우 렉시컬 스코프를 따르기 때문에 선언한 곳에 따라 스코프가 정해진다. 전역에서 선언된 bar 의 상위 스코프는 전역 스코프가 되어 name 의 값이 'jun'이 된다.

암묵적 전역

let num1 = 1; // 전역 변수

function foo () {
  num2 = 2; // 선언되지 않은 식별자
  console.log(num1 + num2);
}

foo(); // output : 3

위 코드에서 num2 는 선언하지 않은 식별자이다.
하지만 오류 없이 num2 는 선언된 변수처럼 동작하여 더하기 연산이 실행되었다.

선언하지 않은 식별자에 값을 할당하면 전역 객체의 프로퍼티가 된다.
num2 = 2window.num2 = 2 로 해석하여 프로퍼티를 동적 생성한다.

결국 num2 는 전역 객체의 프로퍼티가 되어 마치 전역 변수처럼 동작한다.
이러한 현상을 암묵적 전역(implicit global)이라 한다.

참고 : Scope - PoiemaWeb

profile
FrontEnd Engineer를 목표로 공부합니다.

0개의 댓글