전역 객체를 사용하면 어디서나 사용 가능한 변수나 함수를 만들 수 있다
alert('Hi');
window.alert('Hi');
전역 객체의 모든 프로퍼티는 아래와 같이 직접 접근할 수 있다
var gVar = 5;
alert(window.gVar); // 객체 window의 프로퍼티 gVar
브라우저에서 var
로 선언한 전역 함수나 전역 변수는 전역 객체의 프로퍼티가 된다 > 모듈을 사용하는 모던 자바스크립트는 이 방식을 지원하지 않으므로 실제로는 사용 ❌
let gLet = 6;
alert(window.gLet); // undefined
var
대신 let
을 사용하면 위 예시와는 달리 전역 객체를 통해 변수에 접근 불가능
// 모든 스크립트에서 현재 사용자에게 접근할 수 있게 전역객체에 추가함
window.currentUser = {
name: 'Jinju'
};
// 아래와 같은 방법으로 currentUser에 접근 가능
alert(currentUser.name); // Jinju
// 지역 변수 'currentUser'가 있다면
// 지역 변수와 충돌 없이 전역 객체 windwo에서 이를 명시적으로 가져올 수 있음
alert(window.currentUser.name);
function slow(x) {
// CPU 집약적인 코드
alert(`slow(${x}을/를 호출함)`);
return x;
}
// DECORATOR
function cachingDecorator(func) {
let cache = new Map();
return function(x) {// WRAPPER STARTS HERE
if(cache.has(x)) { // cache에 해당 key가 있으면
return cache.get(x); // 대응하는 값을 cache에서 읽어온다
}
let result = func(x); // 그렇지 않은 경우에는 func를 호출하고
cache.set(x, result); // 그 결과를 캐싱(저장)한다
return result;
}; // WRAPPER ENDS HERE
}
slow = cachingDecorator(slow);
alert(slow(1)); // slow(1)이 저장되었습니다
alert('다시 호출' + slow(1)); // 동일한 결과
alert(slow(2)); // slow(2)가 저장되었습니다
alert('다시 호출' + slow(2)); // 윗줄과 동일한 결과
slow
본문을 수정하는 것보다 독립된 래퍼 함수 cachingDecorator
를 사용할 때 생기는 이점cachingDecorator
를 재사용 할 수 있다 = 원하는 함수 어디든 cachingDecorator
를 적용할 수 있다slow
자체의 복잡성이 증가하지 않는다// worker.slow에 캐싱 기능 추가하기
let worker = {
someMethod() {
return 1;
},
slow(x) {
// CPU 집약적인 코드
alert(`slow(${x}을/를 호출함)`);
return x * this.someMethod(); // (1)
}
};
// 이전과 동일한 코드
function cachingDecorator(func) {
let cache = new Map();
return function(x) {
if(cache.has(x)) {
return cache.get(x);
}
let result = func(x); // (2)
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
(1)
줄에서 this.someMethod()
접근에 실패해서 에러가 발생했다
👉 (2)
줄에서 wrapper가 기존 함수 func(x)
를 호출하면 this
가 undefined
가 되기 때문
= wrapper가 기존 메서드 호출 결과를 전달하려 했으나 this
의 컨텍스트가 사라져서 에러 발생
이를 해결하기 위해서,
this
를 명시적으로 고정해 함수를 호출할 수 있게 해주는 내장 함수 메서드
func.call(context, arg1, arg2, ...)
메서드를 호출하면 메서드의 첫 번째 인수가 this
, 이어지는 인수가 func
의 인수가 된 후, func
이 호출된다
아래 함수와 메서드를 호출하면 거의 동일한 일이 발생한다
func(1, 2, 3);
func.call(obj, 1, 2, 3);
둘 다 인수로 1, 2, 3을 받지만 func.call
에서는 this
가 obj
로 고정된다는 점이다
wrapper 안에서 call
을 사용해 컨텍스트를 원본 함수로 전달하면 에러가 발생하지 않는다
let worker = {
someMethod() {
return 1;
},
slow(x) {
alert(`slow(${x}을/를 호출함)`);
return x * this.someMethod(); // (1)
}
};
function cachingDecorator(func) {
let cache = new Map();
return function(x) {
if(cache.has(x)) {
return cache.get(x);
}
let result = func.call(this, x); // !!
cache.set(x, result);
};
}
worker.slow = cachingDecorator(worker.slow); // 캐싱 데코레이터 적용
alert(worker.slow(2)); // 제대로 동작
alert(worker.slow(2)); // 제대로 동작하나 원본 함수가 호출되지 않고 캐시 된 값이 출력됨
this
가 전달되는 순서
1. 데코레이터 적용후 worker.slow
는 wrapper function(x) {...}
가 된다
2. worker.slow(2)
를 실행하면 wrapper는 2를 인수로 받고, this=worker
가 된다
3. 결과가 캐시되지 않은 상황이면 func.call(this, x)
에서 현재 this (=worker)
와 인수 (=2
)를 원본 메서드에 전달한다
복수 인수를 가진 메서드 캐싱하는 법
let worker = {
slow(min, max) {
return min + max; // CPU 많이 쓰는 작업이라고 가정
}
};
// 동일한 인수를 전달했을 때 호출 결과를 기억할 수 있어야 한다
worker.slow = cachingDecorator(worker.slow);
해결할 수 있는 방법들
(map, result)
쌍 저장은 cache.set(min)
으로, result
는 cache.get(min).get(max)
을 사용해서 얻는다map
의 key로 문자열 "min,max"
를 사용한다. 여러 값을 하나로 합치는 코드는 해싱함수에 구현해서 유연성을 높인다3번째 방법을 이용 + func.call(this, x)
를 func.call(this, ...arguments)
로 교체해서 코드를 수정해본다면
let worker = {
slow(min, max) {
alert(`slow(${min},${max})을/를 호출함`);
return min + max;
}
};
function cachingDecorator(func, hash) {
let cache = new Map();
return function() {
let key = hash(arguments); // (1)
if (cache.has(key)) {
return cache.get(key);
}
let result = func.call(this, ...arguments); // (2)
cache.set(key, result);
return result;
};
}
function hash(args) {
return args[0] + ',' + args[1];
}
worker.slow = cachingDecorator(worker.slow, hash);
alert( worker.slow(3, 5) ); // 제대로 동작
alert( "다시 호출: " + worker.slow(3, 5) ); // 동일한 결과 출력 (캐시된 결과)
개선 후 바뀐 점
👉 (1)
줄에서 hash
가 호출되면서 arguments
를 사용한 단일 키가 만들어짐. (3, 5)
> "3,5"
👉(2)
줄에서 func.call(this, ...arguments)
를 사용해서 컨텍스트(this
)와 래퍼가 가진 인수 전부 (...arguments
)를 기존함수에 전달
그런데 func.call(this, ...arguments)
대신 func.apply(this, arguments)
를 사용해도 된다
func.apply(context, args) // syntax
apply
는 func
의 this
를 context
로 고정해주고, 유사 배열 객체인 args
를 인수로 사용할 수 있게 해준다
call
과 apply
의 문법적 차이는 call
이 복수 인수를 따로 받는 대신 apply
는 인수를 유사 배열 객체로 받는다는 점
func.call(context, ...args); // 전개 문법을 사용해서 인수가 담긴 배열을 전달하는 것과
func.apply(context, args); // call을 사용하는 것은 동일
하지만 약간의 차이점은 존재한다
...
은 iterable args
을 분해해서 call
에 전달할 수 있도록 해준다apply
는 오직 유사 배열 형태의 args
만 받는다위 두 차이점을 빼면 두 메서드는 완전히 동일하게 동작하므로
👉 인수가 iterable 형태라면 call을, 유사배열 형태라면 apply를 사용한다