지난 글에서 React의 useState는 왜 const로 사용하는지 알아보며, 변수에 대해 정리해보았다.
이 과정에서 스코프(Scope)에 대해 알아보게 되었는데, 제대로 알고 넘어가지 않았던 개념에 대해 상세히 알아보고 보다 친숙하게 용어를 익히기 위해 포스팅을 작성한다.
스코프(Scope)는 변수나 함수가 유효한 범위를 의미한다.
즉, 특정 코드에서 변수에 접근하거나 사용할 수 있는 규칙과 범위라고 볼 수 있다.
이 Scope를 눈으로 확인할 수 있는데, 개발자 도구의 Source에서 JS 코드를 확인하게 되면 쉽게 Scope의 구조를 확인할 수 있다.
위와 같이 Scope 안에는 Local
, Closure
, Global
, CallStack
과 같은 요소가 포함되어있음을 볼 수 있다.
이번에 알아볼 개념은 Local, Global, Closure와 같은 Scope
의 개념이지만, JavaScript의 동작원리에 포함되는 Call Stack
과 Execution Context
의 개념도 함께 이해해야 심도 깊은 이해를 가져갈 수 있다.
A
call stack
is a mechanism for an interpreter (like the JavaScript interpreter in a web browser) to keep track of its place in a script that calls multiple functions — what function is currently being run and what functions are called from within that function, etc.
호출 스택
은 여러 함수들을 호출하는 스크립트에서 해당 위치를 추적하는 인터프리터 (웹 브라우저의 JavaScript 인터프리터같은)를 위한 메커니즘입니다. 현재 어떤 함수가 실행중인지, 그 함수 내에서 어떤 함수가 호출되어야 하는지, 등을 제어합니다.
Call Stack(호출 스택)은 Stack의 자료 구조를 가지며, LIFO(Last In, First Out) 방식으로 작동된다. 실행되는 우선순위와 실행되고 있는 함수 등을 제어하는 기능을 한다.
function multiply(x, y) {
return x * y;
}
function printSquare(x) {
var s = multiply(x, x);
console.log(s);
}
printSquare(5);
위 코드가 실행될 때, 호출 스택의 동작 단계는 아래와 같다.
Call Stack
은 코드가 실행되면서 생성되는 Execution Context
를 저장하는 자료구조다. 엔진
이 처음 script를 실행할 때, Global Execution Context
를 생성하고 이를 Call Stack
에 push한다.
그 후 엔진이 함수를 호출할 때 마다 함수를 위한 Execution Context
를 생성하고 이를 Call Stack
에 push 한다.
Execution Context(실행 컨텍스트)
는 JavaScript 코드의 일부가 실행되는 환경을 의미한다. 실행 컨텍스트는 변수, 함수, 객체가 어떻게 관리되고 실행되는지 정의하고 있다.
window
또는 global
)를 포함.eval()
함수로 코드를 실행할 때 생성.let
, const
, var
로 선언된 변수를 포함.this
키워드가 무엇을 참조하는지 정의.noVarTagVar = "I am global"; //변수의 종류 선언 없이 이름만 선언하게 되면 전역 변수로 저장
let globalVar = "I am global"; // 전역 변수
function printGlobalVar() {
console.log(globalVar); // 접근 가능
}
printGlobalVar(); // "I am global"
{ }
) 또는 함수 내부에서 선언된 변수는 해당 블록이나 함수 내부에서만 유효.var
로 선언된 변수는 함수 내부에서만 유효.function localScopeExample() {
var localVar = "I am local";
console.log(localVar); // 접근 가능
}
// console.log(localVar); // ReferenceError: localVar is not defined
let
과 const
는 블록 스코프를 따르며, { }
안에서만 유효.if (true) {
let blockScopedVar = "I am block-scoped";
console.log(blockScopedVar); // 접근 가능
}
// console.log(blockScopedVar); // ReferenceError: blockScopedVar is not defined
ReferenceError
발생.let a = "global";
function outer() {
let b = "outer";
function inner() {
let c = "inner";
console.log(a); // "global" (전역 스코프에서 찾음)
console.log(b); // "outer" (상위 스코프에서 찾음)
console.log(c); // "inner" (현재 스코프에서 찾음)
}
inner();
}
outer();
선언할 때의 코드 구조
에 따라 스코프가 결정
된다. Static Scope(정적 스코프)
라고 불리기도 한다.함수가 선언된 위치가 스코프를 결정
함.let globalVar = "global";
function outer() {
let outerVar = "outer";
function inner() {
console.log(globalVar); // "global"
console.log(outerVar); // "outer"
}
return inner;
}
const innerFunction = outer();
innerFunction(); // "global", "outer" (선언된 위치 기준으로 스코프 결정)
Dynamic Scope(동적 스코프)
라는 개념도 존재하는데, 만약 동적 스코프처럼 동작했다면 아래와 같이 동작했을 것이다.
let globalVar = "I'm global";
function outer() {
let outerVar = "I'm outer";
function inner() {
console.log(globalVar); // 호출된 위치 기준으로 결정
console.log(outerVar); // 호출된 위치 기준으로 결정
}
return inner;
}
function anotherFunction() {
let globalVar = "I'm dynamic";
let outerVar = "I'm dynamic outer";
const dynamicFunc = outer();
dynamicFunc(); // inner 함수 호출
}
anotherFunction();
실행 결과
I'm dynamic
I'm dynamic outer
JavaScript는 렉시컬 스코프를 사용해 코드 작성 시점에 변수의 선언 위치에 따라 변수가 유효한 범위를 고정한다. 이를 통해 코드를 더 직관적이고, 예측 가능하게 만든다.
A
closure
is the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment). In other words, a closure gives a function access to its outer scope. In JavaScript, closures are created every time a function is created, at function creation time.
클로저
는 주변 상태(어휘적 환경)에 대한 참조와 함께 묶인(포함된) 함수의 조합입니다. 즉, 클로저는 내부 함수에서 외부 함수의 범위에 대한 접근을 제공합니다. JavaScript에서 클로저는 함수 생성 시 함수가 생성될 때마다 생성됩니다.
function outer() {
let count = 0; // 외부 변수
return function inner() { // 내부 함수
count++; // 외부 변수를 사용
console.log(count); // 외부 변수를 출력
};
}
const closureFunction = outer(); // outer 실행 -> inner 반환
closureFunction(); // 1
closureFunction(); // 2
closureFunction(); // 3
위와 같이 구성하게 되면, outer라는 함수에 대한 참조를 기억하며 기존 값을 유지하며 증가시킬 수 있다.
폐쇄성
을 갖는다.모듈화
라고 한다. 클로저를 통해 데이터와 메소드를 묶어다닐 수 있기에 클로저는 모듈화에 유리하다.클로저 모듈 패턴
을 사용해 객체에 담아 여러 개의 함수를 리턴하도록 만들어 정보의 접근을 제한할 수 있는데, 이를 캡슐화
라고 한다.https://yozm.wishket.com/magazine/detail/2886/
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures