reachability를 이용한 메모리 관리 : 자바스크립트 엔진 내에선 가비지 컬렉터(garbage collector)가 끊임없이 모든 객체를 모니터링하고, 도달할 수 없는 객체는 삭제합니다.
클로저(Closure)는 외부 변수를 기억하고 이 외부 변수에 접근할 수 있는 함수, 자바스크립트에서는 모든 함수가 자연스럽게 클로저가 된다.
’변수’는 특수 내부 객체인 환경 레코드의 프로퍼티일 뿐입니다. '변수를 가져오거나 변경’하는 것은 '환경 레코드의 프로퍼티를 가져오거나 변경’함을 의미합니다.
함수는 특별한 프로퍼티 [[Environment]]에 저장된 정보를 이용해 렉시컬 환경을 참조함. 그런데!!! new Function을 이용해 함수를 만들면 함수의 [[Environment]] 프로퍼티가 현재 렉시컬 환경이 아닌 전역 렉시컬 환경을 참조하게 됩니다.
: 인수로 받은 함수의 행동을 변경시켜주는 함수를 Decorator라고 한다.
function slow(x) {
// CPU 집약적인 작업이 여기에 올 수 있습니다.
alert(`slow(${x})을/를 호출함`);
return x;
}
function cachingDecorator(func) {
let cache = new Map();
return function(x) {
if (cache.has(x)) { // cache에 해당 키가 있으면
return cache.get(x); // 대응하는 값을 cache에서 읽어옵니다.
}
let result = func(x); // 그렇지 않은 경우엔 func를 호출하고,
cache.set(x, result); // 그 결과를 캐싱(저장)합니다.
return result;
};
}
slow = cachingDecorator(slow);
alert( slow(1) ); // slow(1)이 저장되었습니다.
alert( "다시 호출: " + slow(1) ); // 동일한 결과
alert( slow(2) ); // slow(2)가 저장되었습니다.
alert( "다시 호출: " + slow(2) ); // 윗줄과 동일한 결과
객체에서 사용할 경우 래퍼가 기존 함수 func(x)를 호출하면 this가 undefined가 되기 때문에 에러가 발생 👇
// worker.slow에 캐싱 기능을 추가해봅시다.
let worker = {
someMethod() {
return 1;
},
slow(x) {
// CPU 집약적인 작업이라 가정
alert(`slow(${x})을/를 호출함`);
return x * this.someMethod();
}
};
// 이전과 동일한 코드
function cachingDecorator(func) {
let cache = new Map();
return function(x) {
if (cache.has(x)) {
return cache.get(x);
}
let result = func(x);
cache.set(x, result);
return result;
};
}
alert( worker.slow(1) ); // 기존 메서드는 잘 동작합니다.
worker.slow = cachingDecorator(worker.slow); // 캐싱 데코레이터 적용
alert( worker.slow(2) ); // 에러 발생!, Error: Cannot read property 'someMethod' of undefined
해결방법
func.call(context, ...args): this를 명시적으로 고정해 함수를 호출할 수 있게 해주는 특별한 내장 함수 메서드
let worker = {
someMethod() {
return 1;
},
slow(x) {
alert(`slow(${x})을/를 호출함`);
return x * this.someMethod();
}
};
function cachingDecorator(func) {
let cache = new Map();
return function(x) {
if (cache.has(x)) {
return cache.get(x);
}
let result = func.call(this, x); // 이젠 'this'가 제대로 전달됩니다.
cache.set(x, result);
return result;
};
}
worker.slow = cachingDecorator(worker.slow); // 캐싱 데코레이터 적용
alert( worker.slow(2) ); // 제대로 동작합니다.
alert( worker.slow(2) ); // 제대로 동작합니다. 다만, 원본 함수가 호출되지 않고 캐시 된 값이 출력됩니다.
여기서 func.call(this, ...arguments) 대신, func.apply(this, arguments)를 사용할 수도 있음.
func.call(context, ...args); // 전개 문법을 사용해 인수가 담긴 배열을 전달하는 것과
func.apply(context, args); // call을 사용하는 것은 동일합니다.