7 분 소요

스코프 (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

블록 코드 평가 및 실행

  1. Global/Function 평가할 때 Block scope를 만나면, Block Lexical Environment를 생성한다.
    (별도의 ExecutionContext를 생성하지 않고 독립적인 Lexical Environment만을 생성한다.)
  2. Block code를 평가하여 이 Block에 대한 DeclarativeEnvironmentRecord를 생성한다.
  3. const/letnotInitializedYet 상태로 DeclarativeEnvironmentRecord에 등록되고, var/function 선언식 등은 부모 Function scopeEnvironmentRecordhoisting한다.
    (이때 function<function object>가 아니라 var와 같이 undefined로 초기화한다.)
  4. Block이 끝나면, 평가 종료 후 상위 scope code를 계속 평가한다.
  5. Block이 실행되면, Block Lexical Environment를 만들고, 부모 ExecutionContext의 제어를 가져온다.
  6. Block 내의 Function 선언식은 무조건 Block 최상단에 hoisting되어 있으므로 먼저 실행한다.
    (이 시점에 상위 function-scope에 함수를 정의해, <function object>으로 인정된다!)
  7. 그 외의 코드 (할당문 및 다른 함수 호출)를 한 줄씩 실행된다.
  8. Block의 실행이 끝나면, ExecutionContext의 제어를 다시 부모로 돌려준다.
  9. Block은 조건에 따라 실행 여부가 결정된다. 평가 시 블럭 내의 var/functionundefinedhoisting되고, 조건이 만족하여 호출 및 실행될 때에만 Block Lexical EnvironmentEnvironmentRecord를 만들고 <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

태그:

업데이트: