자바스크립트 활성 객체와 함수

활성 객체

  • Activation Object
  • 함수가 호출되면 활성 객체가 만들어진다. 이는 숨겨진 데이터 구조로 호출된 함수의 반환 주소와 실행에 필요한 정보를 저장하고 호출된 함수에 바인딩한다.
  • C와 같은 언어에서는 활성 객체가 스택에 저장되고 함수가 종료되고 반환되면 스택에서 제거하지만 자바스크립트에서는 활성 객체를 다른 객체와 동일하게 힙에 저장한다.
  • 함수가 종료된다고 활성 객체를 자동으로 비활성화하지 않는다.
  • 활성 객체는 해당 객체에 대한 참조가 있는 한 계속 살아있으며 다른 객체와 마찬가지로 가비지 컬렉터에 의해 처리된다.

활성 객체 내부 정보

  • 함수 객체에 대한 참조
  • caller 함수의 활성 객체에 대한 참조 (이 참조는 return 이 실행흐름을 caller 로 돌릴 때 사용된다.)
  • 함수 호출이 끝난 뒤 실행을 재개하기 위해 필요한 정보 (대개 함수 호출문 바로 다음 명령어의 주소)
  • 파라미터에 의해 초기화되는 함수 매개변수
  • undefined로 초기화된 함수 변수들
  • 함수가 복잡한 표현식을 계산하기 위해 임시로 사용하는 변수들
  • 함수 객체가 메소드로서 호출되었을 때 사용할 수 있는 this 참조

함수

함수 객체

  • 함수 객체는 다른 일반적인 객체와 마찬가지로 속성을 갖고 있으며 변경 가능하다.
  • 공유되는 변경 가능한 함수 객체가 취약점으로 사용될 수 있다. (프로토타입 오염 등)
  • 함수 객체는 prototype 이란 속성을 가지고 있고, prototype은 Object.prototype에 대한 delegation link와 constructor 속성을 가진 객체의 참조를 가진다.
    • constructor 속성은 함수 객체에 대한 역참조를 가지고 있다.
  • 함수 객체는 Function.prototype 에 대한 델리게이션 링크를 가지고 있어 applycall을 상속받는다.
  • 함수 객체에는 두 가지 숨겨진 속성이 있는데 다음과 같다.
    • 함수 실행 코드에 대한 참조
    • 함수 객체가 생성되는 시점에 활성화된 활성 객체에 대한 참조 클로져를 사용할 수 있게 해준다.

함수 호출

  • 파라미터 표현식 계산
  • 함수의 매개변수와 변수를 저장할 수 있는 충분한 크기의 활성 객체 생성
  • 호출된 함수 객체에 대한 참조를 새로운 활성 객체에 저장
  • 전달받은 파라미터를 새로운 활성 객체의 매개변수에 저장
    • 빠진 파라미터는 undefined로 간주, 남는 파라미터는 버림
  • 활성 객체의 모든 변수 값을 undefined로 할당
  • 함수 호출 명령어의 바로 다음 명령어를 활성 객체의 next instruction 필드 값으로 할당
  • 새로운 활성 객체의 caller 필드 값에 현재 활성 객체를 할당
    • 실제 호출 스택이 아닌 활성 객체의 연결된 목록이다.
  • 새로운 활성 객체를 현재 활성 객체로 지정
  • 호출된 함수 실행

꼬리 재귀

  • 파라미터 표현식 계산
  • 현재 활성 객체가 충분히 크다면 (일반적으로 충분히 큼)
    • 현재 활성 객체를 새로운 활성 객체로 사용
  • 아닌 경우
    • 함수 매개변수와 변수를 저장할 수 있는 충분한 크기의 활성 객체 생성
    • 새로운 활성 객체의 caller 필드 값에 현재 활성 객체 저장
    • 새로운 활성 객체를 현재 활성 객체로 지정
  • 전달받은 파라미터를 새로운 활성 객체의 매개변수에 저장
    • 빠진 파라미터는 undefined로 간주, 남는 파라미터는 버림
  • 활성 객체의 모든 변수 값을 undefined로 지정
  • 호출된 함수를 실행

활성 객체를 재사용하므로 재귀 함수 호출을 반복문만큼 빠르게 처리 가능하다.
try 블록의 꼬리 호출은 최적화되지 않는다.

  • try는 실행 흐름을 caller의 catch문으로 변경해야하는데, 이러면 활성 객체가 최적화 될 수 없음
  • 내부에서 새로운 변수를 가지고 있다면 활성 객체가 최적화 될 수 없음

꼬리 호출 예시

1
2
3
4
5
6
; recursion
call func
return

; tail recursion
jump func
1
2
3
4
5
6
7
8
9
(function loop() {
// do some
if (done) {
return;
}

// do more
return loop();
}());
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// ❌
function factorial(n) {
if (n < 2) {
return 1;
}

return n * factorial(n - 1);
}

// ❌
const value = func();
return value;

// ❌
return function () {};

// ✔️
function factorial(n, result = 1) {
if (n < 2) {
return result;
}

return factorial(n - 1, n * result);
}
// ✔️
return (function () {}());