[JS] 4. 스코프 (Scope) & 실행 컨텍스트 (Execution Context)
스코프 (Scope) : 변수에 접근할 수 있는 범위
- 변수가 선언되었을 때, 선언 위치에 의해 해당 변수에 접근할 수 있는 코드의 영역이 결정
- 자바스크립트 엔진은 스코프를 통해 참조 대상 식별자를 찾아 변수를 식별
전역 스코프 (Global-level Scope) → 코드 어디에서나 참조 가능
- 전역에 선언된 전역 변수 (
Global variable) 는 전역 객체 (Global Object)의 프로퍼티
var global = 'global';
function main() {
var local = 'local';
console.log(global);
console.log(local);
}
console.log(global);
console.log(local); // Uncaught ReferenceError: local is not defined
함수 스코프 (Function-level Scope) → 함수 자신과 하위 함수에서만 참조 가능
(function () {
var local = 'local';
console.log(local); // 'local'
})();
console.log(typeof local); // 'undefined'
블록 스코프 (Block-level Scope) → 블록 내에서만 참조 가능
{
let blockScoped = 'block scoped';
console.log(blockScoped); // 'block scoped'
}
console.log(typeof blockScoped); // 'undefined'
모듈 스코프 (Module-level Scope) → 모듈 내에서만 참조 가능
// module1.js
export const moduleScoped = 'module scoped';
// module2.js
import { moduleScoped } from './module1.js';
console.log(moduleScoped); // 'module scoped'
렉시컬 스코프 (Lexical-level Scope) → 변수 스코프가 코드 작성 문맥에 결정
function outer() {
const lexical = 'lexical scope';
function inner() {
console.log(lexical); // 'lexical scope'
}
inner();
}
outer();
암묵적 전역 (Implicit Global) → 변수 선언 없이 값 할당 시, 객체 프로퍼티로 등록
function implicitGlobalExample() {
implicitVar = 'implicit global'; // 암묵적 전역 변수
}
implicitGlobalExample();
console.log(implicitVar); // 'implicit global'
실행 컨텍스트 (Execution Context) : 실행할 코드에 제공할 환경 정보들을 모아놓은 객체
- 변수 객체 (Variable Object) : : 변수, 함수 선언, 함수의 인수 정보를 담고 있는 객체
- 활성 객체 : 변수 객체의 초기 상태는 활성 객체로, arguments 객체만 포함
- 함수 선언 : 모든 함수 선언은 변수 객체에 즉시 추가
- 변수 선언 : 변수 선언은 undefined로 초기화되며, 코드 실행 시 값이 할당
- 스코프 체인 (Scope Chain) : 현재 실행 중인 코드의 스코프(변수 접근 범위)를 나타내는 리스트
- 현재 스코프 : 실행 중인 함수의 변수 객체
- 외부 스코프 : 외부 함수들의 변수 객체를 참조
- 현재 스코프 : 실행 중인 함수의 변수 객체
- this 바인딩 (this Binding) : 실행 컨텍스트 내에서 this가 가리키는 객체를 정의
- 전역 컨텍스트 : 전역 객체 (window 객체 등)를 가리킴
- 함수 컨텍스트 : 함수 호출 방식에 따라 this의 값이 결정
var gg = 1; let bb = 2;
function f1(x, y) {
var gg = 11; let bb = 22;
console.log('f1>', gg, bb, zz, f2, f2.length);
f2('first');
{
const xx = 99;
let lll = 0;
f2('nest-first');
var zz = 88;
function f2(t) { console.log(t, 'nested', xx, zz, lll); }
}
function f2(t, u) { console.log(t, 'inner', xx, zz); }
function f2(t, u, v) { console.log(t, 'inner2', xx, zz); }
var zz = 800;
f2('second');
}
function f2(g) {
console.log(g, 'global f2>', gg, bb, xx, kk);
}
let xx = 9;
if (gg > 0) { var kk = 33; var yy = 9; }
f1(1,2); console.log(kk, yy);
f2('third');
(1) 전역 코드 평가 단계
var gg; let bb;
function f1(x, y) { ... } // Function Object
function f2(g) { ... } // Function Object
let xx;
var kk;
(2) 전역 코드 실행 단계
var gg = 1; let bb = 2;
let xx = 9;
if (gg > 0) { ... }
f1(1,2); console.log(kk, yy);
f2('third');
(3) 전역 코드 실행 단계 (if문 코드 블록 실행 및 종료)
if (gg > 0) { // var gg = 1;
var kk = 33; var yy = 9;
}
(4) 함수 코드 평가 단계 : f1(1,2) 호출
f1(1,2);
function f1(x, y) {
var gg; let bb;
{ ... }
// function f2(t, u) { ... } // Function Object
function f2(t, u, v) { ... } // Function Object
var zz;
}
(5) 함수 코드 평가 단계 내부의 블록 코드 평가 단계 : { ... }
{
const xx;
let lll;
var zz;
function f2(t) { ... } // Function Object
}
(6) 함수 코드 실행 단계 : f1(1,2) 실행
f1(1,2);
function f1(x, y) {
gg = 11; bb = 22;
console.log('f1>', gg, bb, zz, f2, f2.length); // f1> 11, 22, undefined, f2, 3
f2('first');
{ ... }
zz = 800;
f2('second');
}
(7) 함수 코드 실행 단계 : f1(1,2) 실행 → 함수 코드 평가 단계 : f2('first') 호출
f1(1,2);
function f1(x, y) {
f2('first');
function f2(t, u, v) { console.log(t, 'inner2', xx, zz); };
}
(8) 함수 코드 실행 단계 : f1(1,2) 실행 → 함수 코드 실행 단계 : f2('first') 실행
f1(1,2);
function f1(x, y) {
gg = 11; bb = 22;
f2('first'); // first inner2 9 undefined
function f2(t, u, v) { console.log(t, 'inner2', xx, zz); };
}
(9) 함수 코드 실행 단계 : f1(1,2) 실행 → 함수 코드 평가 단계 : f2('nest-first') 호출
function f1(x, y) {
gg = 11; bb = 22;
{
xx = 99;
lll = 0;
f2('nest-first');
function f2(t) { console.log(t, 'nested', xx, zz, lll); }
}
...
}
(10) 함수 코드 실행 단계 : f1(1,2) 실행 → 함수 코드 실행 단계 : f2('nest-first') 실행
function f1(x, y) {
gg = 11; bb = 22;
{
xx = 99;
lll = 0;
f2('nest-first'); // nest-first nested 99 undefined 0
function f2(t) { console.log(t, 'nested', xx, zz, lll); }
}
...
}
(11) 함수 코드 실행 단계 : f1(1,2) 실행 → 함수 코드 평가 단계 : f2('second') 호출
function f1(x, y) {
gg = 11; bb = 22;
{
const xx = 99;
lll = 0;
zz = 88;
function f2(t) { console.log(t, 'nested', xx, zz, lll); }
}
f2('second');
}
(12) 함수 코드 실행 단계 : f1(1,2) 실행 → 함수 코드 실행 단계 : f2('second') 실행
function f1(x, y) {
gg = 11; bb = 22;
{
const xx = 99;
lll = 0;
zz = 88;
function f2(t) { console.log(t, 'nested', xx, zz, lll); }
}
f2('second'); // second nested 99 800 0
}
(13) 전역 코드 실행 단계 (console.log 문 실행 및 종료)
gg = 1; bb = 2;
console.log(kk, yy); // 33 9
(14) 전역 코드 실행 단계 → 함수 코드 평가 단계 : f2('third') 평가
gg = 1; bb = 2;
function f2(g) {
console.log(g, 'global f2>', gg, bb, xx, kk);
}
let xx = 9;
if (gg > 0) { var kk = 33; var yy = 9; }
f2('third');
(15) 전역 코드 실행 단계 → 함수 코드 평가 단계 : f2('third') 실행
gg = 1; bb = 2;
function f2(g) {
console.log(g, 'global f2>', gg, bb, xx, kk);
}
let xx = 9;
if (gg > 0) { var kk = 33; var yy = 9; }
f2('third'); // third global f2> 1 2 9 33
블록 코드 평가 및 실행
Global/Function평가할 때Block scope를 만나면,Block Lexical Environment를 생성한다.
(별도의ExecutionContext를 생성하지 않고 독립적인Lexical Environment만을 생성한다.)Block code를 평가하여 이Block에 대한DeclarativeEnvironmentRecord를 생성한다.const/let은notInitializedYet상태로DeclarativeEnvironmentRecord에 등록되고,var/function선언식 등은 부모Function scope의EnvironmentRecord에hoisting한다.
(이때function은<function object>가 아니라var와 같이undefined로 초기화한다.)Block이 끝나면, 평가 종료 후 상위scope code를 계속 평가한다.Block이 실행되면,Block Lexical Environment를 만들고, 부모ExecutionContext의 제어를 가져온다.Block내의Function선언식은 무조건Block최상단에hoisting되어 있으므로 먼저 실행한다.
(이 시점에 상위function-scope에 함수를 정의해,<function object>으로 인정된다!)- 그 외의 코드 (할당문 및 다른 함수 호출)를 한 줄씩 실행된다.
Block의 실행이 끝나면,ExecutionContext의 제어를 다시 부모로 돌려준다.Block은 조건에 따라 실행 여부가 결정된다. 평가 시 블럭 내의var/function은undefined로hoisting되고, 조건이 만족하여 호출 및 실행될 때에만Block Lexical Environment와EnvironmentRecord를 만들고<function object>로 정의된다.
엄격 모드 (Strict Mode) : JS가 묵인한 에러를 발생시켜 엄격한 오류 검사를 적용
- 선언하지 않은 식별자는 접근할 수 없음
- 암묵적 전역 (
Implicit Global)은 허용하지 않음 Delete로 선언된 변수, 함수, 매개변수를 삭제할 수 없음- 블록 내에서의 함수는 블록 스코프 (블록 내 함수는 볼록의
DeclarativeEnvironmentRecord에 존재) - 함수 내에서 매개변수의 이름이 동일해선 안됨
NaN,Infinite등의 전역 프로퍼티에 값을 할당해선 안됨
'use strict';
var gg = 1;
let bb = 2;
function f1(x, y) {
var gg = 11;
let bb = 22;
console.log('f1>', gg, bb, zz, f2, f2.length);
f2('* first');
{
const xx = 99;
f2('* nest-first');
var zz = 88;
function f2(t) {
console.log(t, '`nested`', xx, zz);
}
}
function f2(t, u) {
console.log(t, '`inner`', xx, zz);
}
function f2(t, u, v) {
console.log(t, '`inner2`', xx, zz);
}
var zz = 800;
console.log('gg:', gg);
f2('* second');
}
function f2(g) {
console.log(g, 'global f2>', gg, bb, xx, kk);
}
let xx = 9;
if (gg > 0) {
var kk = 33;
const yy = 9;
}
f1(1, 2);
console.log('kkkkk>>', kk);
f2('* third');
엄격 모드는 함수 단위로도 적용 가능 → 파일 단위 엄격 모드와 함수 단위 엄격 모드는 상충됨
클로저 (Closure) : 함수가 특정 스코프에 접근할 수 있도록 의도적으로 그 스코프에서 정의한 것
- 상위 스코프의 식별자를 참조하는 하위 스코프 (함수, 메소드)가 외부에서 참조되어 상위 스코프보다 오래 살아있는 것
let user;
{
const private = {
id: 1,
name: 'John'
}
user = private;
}
user.age = 30;
console.log(user); // { id: 1, name: 'John', age: 30 }
function func(x) {
let clo = function (y) {
console.log(y);
}
return clo(x);
}
func('a'); // a
클로저를 활용하여 외부변수로 인해 오염될 수 있는 비순수함수 (
Impure Function)를, 함수 내부로 전달된 인수에게만 의존하여 반환값을 만드는 순수함수 (Pure Function)로 바꿀 수 있다.// 비순수함수 let cnt1 = 0; function func1(x) { cnt1 += 1; return cnt1; } console.log(func1()); // 1 console.log(func1()); // 2 console.log(func1()); // 3 // → 순수함수 function func2() { let cnt2 = 0; return function temp() { cnt2 += 1; return cnt2; }; } const counter1 = func2(); const counter2 = func2(); console.log(counter1()); // 1 console.log(counter1()); // 2 console.log(counter2()); // 1