함수 범위, 블록 범위, 렉시컬(lexical) 범위

33-js-concepts를 스터디하며 정리한 포스트 입니다.

범위 (Scope)란?

프로그램 언어에서 범위 (Scope)는 변수의 접근 가능과, 생존 기간을 뜻한다.
범위 (Scope)는 크게 전역 범위(Global Scope), 지역 범위(Local Scope)가 존재한다.
전역 범위(Global Scope)는 코드 전체에서 참조 가능한 것을 의미하고
지역 범위(Local Scope)는 정해진 코드 부분에서만 참조 가능한 것을 의미한다.

자바스크립트에서의 범위의 특징

  • 변수의 범위는 함수 단위다.
  • 스크립트 실행시 함수 범위의 변수 선언호이스팅이 이루어진다.
  • 스크립트 실행시 변수 관리렉시컬 영역을 기준으로 한다.
  • 스크립트 실행시 변수 검색스코프 체인을 이용한다.

함수 범위 (Function Scope)

함수 범위(Function Scope)는 함수 내부에서 변수를 선언 했을 경우에 해당한다.
함수 내부에서 선언된 변수함수 내부에서만 접근가능하다.

var variable = 1

function functionScopeTest() {
  if (true) {
    var inCondition = 2
  }

  console.log(inCondition) // 2;

  return inCondition
}

c, c++과 같은 프로그래밍 언어는 변수의 유효 범위가 블록 범위다.
따라서 if문에서 선언된 inCondition변수를 참조할 수 없어 에러가 발생할 것이다.
하지만 자바스크립트는 변수의 범위함수 단위이기 때문에 문제가 발생하지 않는다.

중복 가능한 변수 선언

자바스크립트에서는 var키워드를 사용해 변수를 선언할 경우 중복된 변수명 사용이 가능하다.

var duplicatedVariable = 1

function duplicatedVariableTest() {
  var duplicatedVariable = 2
  var duplicatedVariable = 3

  console.log('duplicatedVariable :', duplicatedVariable)
}

duplicatedVariableTest() // duplicatedVariable : 3

실질적으로 참조되는 변수값은 가장 가까운 범위변수참조하게 된다.

var키워드 없이 선언 가능한 변수

또한 변수를 선언할 때 varlet같은 키워드 없이 변수 선언이 가능하다.
키워드가 없이 선언된 변수는 window객체의 변수로 전역 변수로 사용 가능하다.

function noKeywordVariableTestOne() {
  noKeywordVariable = 1

  console.log('noKeywordVariable (1) :', noKeywordVariable)
}

function noKeywordVariableTestTwo() {
  console.log('noKeywordVariable (2) :', noKeywordVariable)
}

noKeywordVariableTestOne() //noKeywordVariable (1) : 1
noKeywordVariableTestTwo() // noKeywordVariable (2) : 1
console.log(window.noKeywordVariable) // 1

window.noKeywordVariable과 같이 접근이 가능한 것을 확인할 수 있다.

호이스팅 (Hoisting)

호이스팅(Hoisting)이라는 단어를 직역하면 끌어올리기, 들어 올려 나르기라는 뜻이다.
자바스크립트에서의 호이스팅변수 선언문끌어올리는 역할을 한다.

function hoistingTest() {
  console.log('variable :', variable) // variable : undefined

  var variable = 1
  console.log('variable :', variable) // variable : 1
}

위의 코드에서 hostingTest함수 내부의 첫번째 콘솔 출력 전에는 varibale이 선언되지 않았다.
자바스크립트에서는 위와 같은 코드를 아래와 같이 호이스팅을 통해 변수 선언문을 위로 올린다.

function hoistingTest() {
  var variable
  console.log('variable :', variable) // variable : undefined

  variable = 1
  console.log('variable :', variable) // variable : 1
}

렉시컬 범위 (Lexical Scope)

렉시컬 범위함수의 실행 시 범위를 함수 정의 단계의 범위로 참조하는 특성이다.
아래 코드에서 lexicalScopeTestOne함수의 첫번째 console.log에서 variable을 참조하려한다.
첫번째 출력에는 variable이 아직 선언되지 않았지만 호이스팅되어 undefined가 된다.
그 후 variable"local"이라는 값이 대입되어 두번째 출력문은 정상적으로 출력이 된다.
여기서 참조한 variable은 함수 내부에서 정의된 값이다.

var variable = 'global'

function lexicalScopeTestOne() {
  console.log(variable) // undefined

  var variable = 'local'

  console.log(variable) // local
}

function lexicalScopeTestTwo() {
  console.log(variable) // global
}

lexicalScopeTestOne()
lexicalScopeTestTwo()

lexicalScopeTestTwo함수는 전역에 선언되어 있다.
lexicalScopeTestTwo함수가 정의된 시점의 상위 범위는 전역 범위다.
또한 내부에서 variable이 선언되지 않았으므로 호이스팅이 발생하지 않고 전역 변수를 참조한다.

스코프 체인 (Scope Chain)

자바스크립트 함수 객체유효 범위를 나타내는 스코프[[Scope]] 프로퍼티로 존재한다.
[[Scope]]는 함수 객체에서 연결리스트 형식으로 관리된다.
각각의 함수는 [[Scope]] 프로퍼티로 실행 컨텍스트의 스코프 체인을 참조하게 된다.
실행 컨텍스트는 함수가 실행 되는 순간 만들어 진다.
또 실행 컨텍스트는 실행된 함수의 [[Scope]]를 기반으로 새로운 스코프 체인을 만든다.

var variable = 'one'

function scopeChainTest() {
  var variable = 'two'

  function returnVariable() {
    return variable
  }

  console.log(returnVariable()) // two
}

scopeChainTest()

위의 코드의 실행 컨텍스트는 아래와 같다.

1

함수가 실행 될 때마다 [[Scope]]가 연결리스트 처럼 연결된다.
returnVariable에서는 아래와 같은 [[Scope]]를 가지게 된다.

2

실제 크롬 개발자 도구를 이용해 [[Scope]]를 확인해 보았다.

3

scopeChainTest함수에서는 [[Scope]]Global 전역 범위를 가지고 있다.
returnVariable함수에서는 0번째로 scopeChainTest 다음으로 Global을 가지고 있다.
따라서 returnVariable에서 먼저 참조하게 되는 변수는 scopeChainTest내부의 변수다.

var variable = 'one'

function returnVariable() {
  return variable
}

function scopeChainTest(func) {
  var variable = 'two'

  console.log(func()) // one
}

scopeChainTest(returnVariable)

바뀐 코드에서 returnVariable함수가 참조하는 [[Scope]]Global이 될 것이다.

4

개발자 도구에서 확인해 보면 두 함수 모드 Global만 범위로 가지고 있다.
두 함수 모두 전역 범위에서 정의되었기 때문에 자신의 변수 객체Global만 갖게 된다.

블록 범위 (Block Scope)

자바스크립트는 기본적으로 함수를 범위로 한다.
하지만 letconst키워드를 사용할 경우 블록 범위를 갖는다.

function usingVarKeyword() {
  for (var i = 0; i < 3; i++) {}
  console.log(i) // 3
}

function usingLetKeyword() {
  for (let i = 0; i < 3; i++) {}
  console.log(i) // ERROR!
}

위와 같이 for-loop내부에서 각각 varlet키워드를 이용해 i를 선언했다.
var키워드를 사용한 함수에서는 i의 값이 3으로 출력되었다.
그러나 let키워드를 사용한 함수에서는 i가 정의되지 않았다는 에러가 발생했다.

5

letconst키워드를 사용할 경우 괄호의 범위 안에서만 접근이 가능하다.


Written by@Minsu Kim
Software Engineer at KakaoPay Corp.