렉시컬 스코핑(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 value
scope
function declarations
✅ YES
actual function
Block(strict mode)
var variables
✅ YES
undefined
Function
let and const variables
🚫 NO (technically yes but not in practice)
<uninitialized>, TDZ
Block
function expressions and arrows
depends if using var or let/const
depends if using var or let/const
depends 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');
}