자바스크립트 핵심 개념과 동작 원리[2] – 자바스크립트 스코프, 호이스팅, TDZ

스코프와 스코프 체인(Scope and Scope Chain)

스코프

  • 스코핑(Scoping): 변수가 organized & accessed 되는 규칙
  • 렉시컬 스코핑(Lexical Scoping): 렉시컬 스코핑은 함수와 코드 블럭({})을 어디에 선언했는지에 따라 스코프가 결정되는 방식이다.
  • 스코프(Scope): 변수가 선언된(declared) 공간(space) 혹은 환경(environment)
    • global scope, function scope, block scope가 있다.
  • 변수의 스코프(Scope of a variable): 변수가 접근 가능한 범위

스코프의 종류

  • 전역 스코프(global scope)
    • 함수나 코드 블록({})의 바깥 부분(top-level code 영역)에서 선언된 변수는 어디서든 접근할 수 있다.
  • 지역 스코프(function scope, local scope)
    • 함수 안에서 선언된 변수는 함수 안에서만 접근할 수 있다.
  • 블록 스코프(block scope(ES6))
    • 코드 블록({}) 안에서 선언된 변수는 블록 안에서만 접근할 수 있다.
    • let, const에만 적용된다.(var는 function scoped)
    • strict mode에서는 함수도 블록 스코프가 적용된다.
    • c.f. 파이썬에서는 if문, for문 등의 코드 블럭(indented)에서 선언된 변수들은 블럭 밖에서도 접근 가능하므로 헷갈릴 수 있다.

스코프 체인

  • 내부(innter)의 스코프는 바깥쪽(outer) 스코프의 변수와 함수들에 접근 가능하다.
    • e.g. function 안에 function이 들어가는 경우 등 스코프가 중첩되는 경우
  • Variable Lookup In Scope Chain이라고 표현한다.
    • Lookdown, Sideway 불가능
a(); // c

function a() {
  b();

  function b() {
    console.log(c());
  }
}

function c() {
  return 'c'
}
  • c 함수가 글로벌 스코프에 속하므로 스코프 체인에 의해 b 함수 안에서 c 함수를 호출할 수 있다.
    • c.f. 파이썬도 클로저(closure: 자신이 정의된 영역 밖의 변수를 참조하는 함수)를 지원하기 때문에 가능한 케이스
  • a 함수 안에서 b 함수가 정의되기 이전에 호출할 수 있는 것은 호이스팅(Hoisting) 때문이다.
    • c.f. 파이썬에서는 UnboundLocalError로 참조가 불가능한 케이스
  • 스코프 체인과 콜 스택을 헷갈려서 함수가 ‘실행’된 순서가 영향을 준다고 생각하지 말자.
    • 스코프 체인은 코드가 쓰여진 위치에 영향을 받는다.
const name = 'b'
function a() {
  const name = 'a';
  console.log(name);
}
a(); // a
  • 참조를 할 때 최대한 가까운 스코프 내에서 참조된 값을 사용하므로 ‘a’가 출력된다.
  • 각 스코프의 name 변수는 다른 스코프에 선언한 것이므로 새로운 변수를 선언하는 것이라고 취급된다. 따라서 이미 선언된 변수라고 SyntaxError가 나진 않는다.
    • 파이썬과 마찬가지로 함수에서 사용된 지역 변수가 함수를 포함한 모듈 영역을 더럽히지 못하도록 막는 의도
function a(n) {
  let name = 'a';
  console.log(name);
  if (n > 1) {
    let name = 'b';
  }
  console.log(name);
}
a(10); // a a
  • 하지만 JS는 if 코드 블럭 안에서 선언된 것도 새로운 변수로 취급하는 것이 파이썬과 다른 점이다.
  • 파이썬과 다르게 선언할 때 let, const, var 등 변수 종류를 붙여주므로 당연한건지도?
function a(n) {
  let name = 'a';
  console.log(name);
  if (n > 1) {
    name = 'b';
  }
  console.log(name);
}
a(10); // a b
  • 이 경우에는 if 블럭 밖에서 선언된 name을 변경하는 것으로 취급한다.
  • 스코핑 버그에 주의!

호이스팅과 TDZ(Hoisting and the TDZ)

호이스팅

  • 호이스팅(Hoisting): 자바스크립트에서는 변수가 선언되기 이전에 접근과 사용이 가능하다.
    • 코드가 실행되기 이전에 코드 내 변수 선언 부분을 전부 스캔해서 variable environment object가 생성하기 때문에 가능하다. (이전 글에서 공부한 Creation phase에 실행됨)
hoisted?initial valuescope
function declarations✅ YESactual functionBlock(strict mode)
var variables✅ YESundefinedFunction
let and const variables🚫 NO (technically yes but not in practice)<uninitialized>, TDZBlock
function expressions and arrowsdepends if using var or let/constdepends if using var or let/constdepends if using var or let/const

TDZ(Temporal Dead Zone)

const name = 'a'
if (name === 'a') {
  console.log(name)
  const number = 1
  console.log(1)
  const v = 'v'
}
  • 위 if 블록 스코프에서 스코프 시작 지점부터 v가 선언되기 이전까지의 코드를 v 변수의 TDZ라고 한다.
  • 변수가 선언되기 이전에 접근하려고 하면 TDZ에 있기 때문에 ReferenceError: Cannot access 'v' before initialization가 발생한다.
    • c.f. 반면 선언되지 않은 변수에 접근하려고 하면 ReferenceError: 'v' is not defined 발생

Why TDZ?

  • ES6에서 에러를 피하는데 도움을 주기 위해 만들어졌다.
    • 선언되기 이전에 변수에 접근하는 것은 피해야 한다.
  • const 변수의 적절한 사용을 돕기 위해 만들어졌다.
    • const는 let과 달리 재할당이 불가능하므로 선언과 동시에 할당되어야 한다.

Why Hoisting?

  • JS가 처음 만들어질 때 개발 효율성을 위해 함수가 선언되기 이전에 함수를 사용할 필요가 있었기 때문이다.
  • var 호이스팅은 함수 호이스팅을 위한 부산물일뿐…
console.log(addExpr(2,3)); // TypeError: undefined is not a function

var addExpr = function (a,b) {
  return a+b;
}

console.log(addDecl(2, 3)); // ReferenceError: Cannot access 'addDecl' before initialization

const addDecl = function (a, b) {
  return a + b;
}
  • var와 let, const를 사용하냐에 따라 다른 에러가 발생한다.
if (!numProducts) deleteShoppingCart();

var numProducts = 10;

function deleteShoppingCart() {
  console.log('All products deleted');
}
  • var는 호이스팅이 작동하므로 첫 줄의 코드에서는 numProducts 변수가 undefined(=falsy value)이므로 deleteShoppingCart() 함수가 호출된다.
  • 이런 예기치 못한 에러를 막기 위해 var 사용을 지양하는 것

global window object

  • var로 선언한 변수는 윈도우 객체에 프로퍼티로 들어가고, let/const는 들어가지 않음
    • x === window.x

Best Practice

  • var 사용하지 말 것
    • mostly const를 사용하고, 변해야하는 값인 경우에는 let
  • 각각의 스코프 맨 위에 변수 선언할 것
  • 호이스팅이 된다고 해도 함수 호출하는 코드를 함수 선언 전에 사용하지 않도록 할 것(버그를 미연에 방지)

Leave a Reply

Your email address will not be published. Required fields are marked *