자바스크립트 활성 객체와 함수
· 약 7분
활성 객체
- Activation Object
- 함수가 호출되면 활성 객체가 만들어진다. 이는 숨겨진 데이터 구조로 호출된 함수의 반환 주소와 실행에 필요한 정보를 저장하고 호출된 함수에 바인딩한다.
- C와 같은 언어에서는 활성 객체가 스택에 저장되고 함수가 종료되고 반환되면 스택에서 제거하지만 자바스크립트에서는 활성 객체를 다른 객체와 동일하게 힙에 저장한다.
- 함수가 종료된다고 활성 객체를 자동으로 비활성화하지 않는다.
- 활성 객체는 해당 객체에 대한 참조가 있는 한 계속 살아있으며 다른 객체와 마찬가지로 가비지 컬렉터에 의해 처리된다.
활성 객체 내부 정보
- 함수 객체에 대한 참조
- caller 함수의 활성 객체에 대한 참조 (이 참조는 return 이 실행흐름을 caller 로 돌릴 때 사용된다.)
- 함수 호출이 끝난 뒤 실행을 재개하기 위해 필요한 정보 (대개 함수 호출문 바로 다음 명령어의 주소)
- 파라미터에 의해 초기화되는 함수 매개변수
undefined
로 초기화된 함수 변수들- 함수가 복잡한 표현식을 계산하기 위해 임시로 사용하는 변수들
- 함수 객체가 메소드로서 호출되었을 때 사용할 수 있는
this
참조
함수
함수 객체
- 함수 객체는 다른 일반적인 객체와 마찬가지로 속성을 갖고 있으며 변경 가능하다.
- 공유되는 변경 가능한 함수 객체가 취약점으로 사용될 수 있다. (프로토타입 오염 등)
- 함수 객체는
prototype
이란 속성을 가지고 있고, prototype은Object.prototype
에 대한 delegation link와constructor
속성을 가진 객체의 참조를 가진다.constructor
속성은 함수 객체에 대한 역참조를 가지고 있다.
- 함수 객체는
Function.prototype
에 대한 델리게이션 링크를 가지고 있어apply
와call
을 상속받는다. - 함수 객체에는 두 가지 숨겨진 속성이 있는데 다음과 같다.
- 함수 실행 코드에 대한 참조
- 함수 객체가 생성되는 시점에 활성화된 활성 객체에 대한 참조 클로져를 사용할 수 있게 해준다.
함수 호출
- 파라미터 표현식 계산
- 함수의 매개변수와 변수를 저장할 수 있는 충분한 크기의 활성 객체 생성
- 호출된 함수 객체에 대한 참조를 새로운 활성 객체에 저장
- 전달받은 파라미터를 새로운 활성 객체의 매개변수에 저장
- 빠진 파라미터는 undefined로 간주, 남는 파라미터는 버림
- 활성 객체의 모든 변수 값을 undefined로 할당
- 함수 호출 명령어의 바로 다음 명령어를 활성 객체의 next instruction 필드 값으로 할당
- 새로운 활성 객체의 caller 필드 값에 현재 활성 객체를 할당
- 실제 호출 스택이 아닌 활성 객체의 연결된 목록이다.
- 새로운 활성 객체를 현재 활성 객체로 지정
- 호출된 함수 실행
꼬리 재귀
- 파라미터 표현식 계산
- 현재 활성 객체가 충분히 크다면 (일반적으로 충분히 큼)
- 현재 활성 객체를 새로운 활성 객체로 사용
- 아닌 경우
- 함수 매개변수와 변수를 저장할 수 있는 충분한 크기의 활성 객체 생성
- 새로운 활성 객체의 caller 필드 값에 현재 활성 객체 저장
- 새로운 활성 객체를 현재 활성 객체로 지정
- 전달받은 파라미터를 새로운 활성 객체의 매개변수에 저장
- 빠진 파라미터는 undefined로 간주, 남는 파라미터는 버림
- 활성 객체의 모든 변수 값을 undefined로 지정
- 호출된 함수를 실행
활성 객체를 재사용하므로 재귀 함수 호출을 반복문만큼 빠르게 처리 가능하다.
try
블록의 꼬리 호출은 최적화되지 않는다.
- try는 실행 흐름을 caller의 catch문으로 변경해야하는데, 이러면 활성 객체가 최적화 될 수 없음
- 내부에서 새로운 변수를 가지고 있다면 활성 객체가 최적화 될 수 없음
꼬리 호출 예시
; recursion
call func
return
; tail recursion
jump func
(function loop() {
// do some
if (done) {
return;
}
// do more
return loop();
})();
// ❌
function factorial(n) {
if (n < 2) {
return 1;
}
return n * factorial(n - 1);
}
// ❌
const value = func();
return value;
// ❌
// eslint-disable-next-line no-unreachable
return function () {};
// ✔️
function factorial2(n, result = 1) {
if (n < 2) {
return result;
}
return factorial2(n - 1, n * result);
}
// ✔️
// eslint-disable-next-line no-unreachable
return (function () {})();