JavaScript 프로그램 평가와 실행 과정(3)

Beautify.log·2021년 11월 4일
0
post-thumbnail

저번 포스팅에서 우리는 자바스크립트가 프로그램을 평가하고 실행하는데 있어서 어떻게 동작하는지에 대해 살펴보았습니다.

이번 포스팅에서도 이어서 풀어보도록 하겠습니다.

  1. this
  2. 식별자의 결정: 유효 범위 체인
  3. 가비지 컬렉션

this 값에 대하여

함수가 호출되어 실행될 때 this의 값이 결정됩니다. 이 값은 함수가 호출되었을 때 그 함수가 속해있던 객체의 참조이며 실행 문맥에서의 ThisBinding 컴포넌트가 참조하는 객체가 됩니다.
예를 들어 아래와 같은 코드가 있다고 가정해 보겠습니다.

const jane = {
  name: "Jane",
  sayHello: function() {
    console.log(`Hello! ${this.name}.`);
  }
};

이 코드를 아래와 같이 실행해보면 예상했던 결과값이 나옵니다.

jane.sayHello();	// → Hello! Jane.

위의 코드에서는 함수를 jain.sayHello라는 이름으로 참조하여 실행하고 있으며 jain.sayHello가 속해있는 객체Object는 jain이라는 변수가 됩니다.
다시 말해서 sayHello 메서드가 호출되는 실행 문맥의 디스 바인딩 컴포넌트가 가리키는 객체는 jain이 됩니다. 따라서 this 값은 jain을 가리키므로 this.name"Jane"이 됩니다. jain.sayHello는 함수의 참조이므로 이 값을 다른 객체 프로퍼티에 대입하여 사용할 수 있습니다.

const alex = { name: "Alex" };
alex.sayHello = jain.sayHello;

alex.sayHello(); // → Hello! Alex

실행 문맥의 ThisBindingComponent가 가리키는 객체가 jain에서 alex가 되었습니다. 따라서 thisalex 객체를 가리키고 this.namealex가 되는 것입니다.

여기에서 알 수 있는 것은 함수는 객체에 묶여 있는 것이 아니라 객체 자체가 함수를 참조한다는 사실입니다.

일반적으로는 여러개의 객체가 함수 하나를 가리키는데 호출될 때의 상황에 따라서 this가 가리키는 값은 바뀝니다.

실행 문맥의 thisBindingComponent가 가리키는 객체가 무엇인지에 따라 this가 가리키는 객체가 바뀝니다.

  1. 코드 최상위 레벨의 this
    코드의 최상위 레벨이 있는 this는 전역 객체를 가리킵니다. 실행 문맥이 초기화 될 때 그 안의 ThisBindingComponent가 전역 환경을 가리키도록 초기화 됩니다.
console.log(this);	// Window

  1. 이벤트 처리기 안에 있는 this
    이벤트 처리기 안에 있는 this는 이벤트가 발생한 요소 객체(이벤트 처리기가 등록된 객체)를 가리킵니다.

  1. 생성자 함수 내부의 this
    사용자가 정의한 생성자 함수 안에 있는 this는 그 생성자로 생성한 객체를 가리킵니다.

  1. 생성자의 prototype 메서드 안에 있는 this
    생성자의 prototype 메서드 안에 있는 this는 해당 생성자로 생성한 객체를 가리킵니다.

  1. 직접 호출한 함수 안에 있는 this
    함수를 최상위 레벨 코드에서 호출하면 함수 안에 있는 this가 전역 객체를 가리키게 되고 이것은 f(); 코드 앞에 객체가 없기 때문에 thisBindingComponent가 전역 객체를 가리킵니다.
function f() { console.log(this); }
// 함수 f를 호출할 때 함수 앞에 아무것도 붙이지 않으면,
// 함수 f가 속한 실행 문맥의 thisBindingComponent가 globalObject를 가리킵니다.
f();	// Window

그러나 함수 앞에 어떤 객체를 붙여서 호출하게 되면 thisBindingComponent가 해당 객체를 가리키게 됩니다.

  1. 마지막으로 applycall 메서드로 호출한 함수 안에 있는 this입니다.
    함수 객체의 applycall 메서드를 사용하면 함수를 호출할 때 this가 가리키는 객체를 바꿀 수 있습니다.
    그 함수 객체가 실행되는 실행 문맥의 thisBindingComponent가 가리키는 객체를 명시적으로 설정할 수 있게 됩니다.

식별자 결정: 유효범위 체인

몇가지 용어에 대해 정의해 보겠습니다.

  1. 속박 변수 : 함수의 인수와 지역변수를 나타내는 말입니다.
  2. 자유 변수 : 속박변수 이외의 변수
  3. 닫힌 함수 : 속박 변수만 포함한 함수
  4. 열린 함수 : 자유 변수를 가지고 있는 함수

예를 들어,

const a = "A";
function myFunction() {
  const b = "B";
  function f() {
    const c = "C";
    console.log(a+b+c);
  }
  f();
}
myFunction();

위와 같은 코드가 있다면 c는 속박 변수, ab가 자유 변수가 됩니다. 또한 myFunction은 닫힌 함수, f는 열린 함수가 됩니다.

자 그렇다면 자바스크립트에서는 이 변수들이 선언된 위치를 어떻게 확인하고 찾아내고 계산할까요?

  1. 속박 변수 c의 의사코드
g_LexicalEnvironment: {
  DeclarativeEnvironmentRecord: {
    c: "C"
  },
  OuterLexicalEnvironmentReference: f_LexicalEnvironment
}

변수 c는 함수 f 안에서 선언된 속박 변수이므로 함수 f의 선언적 환경 레코드 안에서 찾을 수 있습니다.

  1. 자유 변수 b의 의사코드
f_LexicalEnvironment: {
  DeclarativeEnvironmentRecord: {
    c: "C"
  },
  OuterLexicalEnvironmentReference: myFunction_LexicalEnvironment
} // ... 아래로 연결
myFunction_LexicalEnvironment: {
  DeclarativeEnvironmentRecord: {
    b: "B"
  },
  OuterLexicalEnvironmentReference: global_LexicalEnvironment
}

변수 b는 함수 myFunction 안에서 선언되었으며 함수 f가 속한 선언적 환경 레코드에서는 찾을 수 없습니다. 그래서 실행 문맥 속에 있는 외부 렉시컬 환경 참조를 따라 함수 f를 호출한 함수인 myFunction이 속한 실행 문맥의 선언적 환경 레코드를 검색합니다. 변수 bmyFunction에 속해있으므로 식별자를 이것으로 결정하게 됩니다.
함수myFunction이 호출되면 그 안의 환경 레코드에 변수 b가 프로퍼티로 추가되고 이후에 함수 f의 선언문이 평가되어 환경 레코드가 생성됩니다.
이 때, 함수 f의 객체가 myFunction의 렉시컬 환경을 참조합니다.
이 참조를 통해 f안에서 변수 b를 사용할 수 있게 되는데, 이러한 과정을 거쳐 함수 f를 실행하는 시점에 변수 b의 위치를 외부 렉시컬 환경 참조를 따라 검색할 수 있게 됩니다.

  1. 자유변수 a
f_LexicalEnvironment: {
  DeclarativeEnvironmentRecord: {
    c: "C"
  },
  OuterLexicalEnvironmentReference: myFunction_LexicalEnvironment
} // ... 아래로 연결
myFunction_LexicalEnvironment: {
  DeclarativeEnvironmentRecord: {
    b: "B"
  },
  OuterLexicalEnvironmentReference: global_LexicalEnvironment
} // ... 아래로 연결
global_LexicalEnvironment: {
  ObjectEnvironmentRecord: {
    bindObject: {
      a: "A"
    }
  },
  OuterLexicalEnvironmentReference: null
}

변수 a는 함수 f의 바깥에서 선언된 자유변수입니다. 이러한 경우 af의 선언적 환경 레코드 안에서 찾을 수 없기 때문에 실행 문맥 속에 있는 외부 렉시컬 환경 참조를 따라 함수 f를 호출한 함수인 myFunction의 선언적 환경 레코드를 검색합니다. 그러나 이 안에서도 찾을 수 없기 때문에 한 단계 더 상위 레벨에서 검색하게 됩니다.
이제 이것을 식별자로 결정하게 됩니다.

이처럼 식별자를 결정할 때에는 현재의 유효범위 안에 없는 식별자를 찾는데 있어 바깥 범위로 호출자의 렉시컬 환경에 속한 외부 렉시컬 환경 참조를 찾아 따라가는 방식을 취하게 됩니다. 이러한 연결고리를 기존에는 Scope Chain이라고 불렀습니다.

가비지 컬렉션

프로그램에서 객체를 생성하면 메모리 공간이 동적으로 확보되는데 사용하지 않는 객체의 메모리 영역을 가비지 컬렉터가 해제해 줍니다.
이러한 mechanism을 가비지 컬렉션이라고 하고, 이 때 사용하지 않는 객체라고 하는 것은 다른 객체의 프로퍼티와 변수가 참조하지 않는 객체를 뜻합니다.

예를 들어 임의의 변수 q가 다음과 같은 객체를 갖고 있다고 가정해봅시다.

const q = { x: 1, y: 2 };

console.log(q);	// Object {x=1, y=2}

이렇게 출력이 되는 것을 확인하고 다시 qnull을 선언하여 참조값을 없애봅시다.

q = null;

console.log(q); // null

이 코드가 실행되면 객체는 어떠한 변수에서도 참조하지 않고 가비지 컬렉터가 이를 메모리에서 해제하게 됩니다.

포스팅 마치겠습니다.

출처

이소 히로시, 모던 자바스크립트 입문(길벗), 281-288.

profile
tried ? drinkCoffee : keepGoing;

0개의 댓글