JavaScript는 함수를 다룰 때 탁월한 유연성을 제공합니다. 전달하거나 객체로 사용할 수 있으며 이제 이들 사이에 통화를 전달 하고 장식하는 방법을 살펴보겠습니다.
CPU를 많이 사용하지만 결과는 안정적인 slow(x)
함수가 있다고 가정해 보겠습니다. 즉, 동일한 x
에 대해 항상 동일한 결과를 반환합니다.
함수가 자주 호출되는 경우 재계산에 추가 시간을 낭비하지 않도록 결과를 캐시(기억)할 수 있습니다.
하지만 이 기능을 slow()
에 추가하는 대신 캐싱을 추가하는 래퍼 함수를 만들겠습니다. 앞으로 살펴보겠지만 그렇게 하면 많은 이점이 있습니다.
코드는 다음과 같습니다. 설명은 다음과 같습니다.
함수 느린(x) { // 여기에 CPU를 많이 사용하는 작업이 있을 수 있습니다. Alert(`${x}로 호출됨`); x를 반환; } 함수 cachingDecorator(func) { 캐시 = new Map(); 반환 함수(x) { if (cache.has(x)) { // 캐시에 해당 키가 있는 경우 캐시를 반환합니다.get(x); // 결과를 읽습니다. } 결과 = func(x); // 그렇지 않으면 func를 호출합니다. 캐시.세트(x, 결과); // 결과를 캐시(기억)합니다. 결과 반환; }; } 느림 = cachingDecorator(느림); 경고(느림(1) ); // Slow(1)이 캐시되고 결과가 반환됩니다. Alert( "다시: " + 느림(1) ); // 캐시에서 반환된 느린(1) 결과 경고(느림(2) ); // Slow(2)가 캐시되고 결과가 반환됩니다. 경고( "다시: " + 느림(2) ); // 캐시에서 반환된 느린(2) 결과
위의 코드에서 cachingDecorator
데코레이터 입니다. 즉, 다른 함수를 취하고 그 동작을 변경하는 특수 함수입니다.
아이디어는 어떤 함수에 대해서도 cachingDecorator
호출할 수 있고 캐싱 래퍼를 반환한다는 것입니다. 훌륭한 기능입니다. 이러한 기능을 사용할 수 있는 많은 함수가 있을 수 있고, 우리가 해야 할 일은 해당 함수에 cachingDecorator
적용하는 것뿐입니다.
메인 함수 코드에서 캐싱을 분리함으로써 메인 코드도 더 단순하게 유지합니다.
cachingDecorator(func)
의 결과는 func(x)
호출을 캐싱 논리로 "래핑"하는 "래퍼": function(x)
입니다.
외부 코드에서 래핑된 slow
함수는 여전히 동일한 작업을 수행합니다. 동작에 캐싱 측면이 추가되었습니다.
요약하자면, slow
코드 자체를 변경하는 대신 별도의 cachingDecorator
사용하면 여러 가지 이점이 있습니다.
cachingDecorator
는 재사용이 가능합니다. 다른 함수에 적용할 수 있습니다.
캐싱 논리는 별개이며, slow
자체의 복잡성을 증가시키지 않았습니다(있는 경우).
필요한 경우 여러 데코레이터를 결합할 수 있습니다(다른 데코레이터도 뒤따를 것입니다).
위에서 언급한 캐싱 데코레이터는 객체 메서드 작업에 적합하지 않습니다.
예를 들어 아래 코드에서 worker.slow()
데코레이션 이후 작동을 멈춥니다.
// 우리는 work.slow 캐싱을 만들 것입니다. 작업자 = { someMethod() { 1을 반환합니다. }, 느린(x) { // 여기 CPU를 많이 사용하는 무서운 작업이 있습니다. Alert("" + x로 호출됨); return x * this.someMethod(); // (*) } }; // 이전과 동일한 코드 함수 cachingDecorator(func) { 캐시 = new Map(); 반환 함수(x) { if (cache.has(x)) { 캐시를 반환합니다.get(x); } 결과 = func(x); // (**) 캐시.세트(x, 결과); 결과 반환; }; } 경고(worker.slow(1)); // 원래 방법이 작동합니다. 작업자.느린 = cachingDecorator(worker.slow); // 이제 캐싱을 수행합니다. 경고(worker.slow(2)); // 앗! 오류: 정의되지 않은 'someMethod' 속성을 읽을 수 없습니다.
this.someMethod
에 액세스하려고 시도하고 실패하는 줄 (*)
에서 오류가 발생합니다. 이유를 알 수 있나요?
그 이유는 래퍼가 (**)
줄에서 원래 함수를 func(x)
로 호출하기 때문입니다. 그리고 그렇게 호출하면 함수는 this = undefined
얻습니다.
다음을 실행하려고 하면 비슷한 증상이 나타납니다.
func = 작업자.느리게 하세요; 기능(2);
따라서 래퍼는 원래 메서드에 대한 호출을 전달하지만 컨텍스트 없이 this
. 따라서 오류가 발생했습니다.
문제를 해결해 봅시다.
this
명시적으로 설정하는 함수를 호출할 수 있는 특수 내장 함수 메서드 func.call(context, …args)가 있습니다.
구문은 다음과 같습니다.
func.call(컨텍스트, arg1, arg2, ...)
첫 번째 인수를 this
로 제공하고 다음 인수를 인수로 제공하여 func
실행합니다.
간단히 말해서, 이 두 호출은 거의 동일합니다.
기능(1, 2, 3); func.call(obj, 1, 2, 3)
둘 다 인수 1
, 2
및 3
을 사용하여 func
호출합니다. 유일한 차이점은 func.call
도 this
obj
로 설정한다는 것입니다.
예를 들어, 아래 코드에서는 다양한 객체의 컨텍스트에서 sayHi
호출합니다. sayHi.call(user)
this=user
제공하여 sayHi
실행하고 다음 줄에서는 this=admin
설정합니다.
함수 sayHi() { 경고(this.name); } 사용자 = { 이름: "John" }; let admin = { 이름: "관리자" }; // 호출을 사용하여 "this"로 다른 개체를 전달합니다. sayHi.call( 사용자 ); // 존 sayHi.call( 관리자 ); // 관리자
그리고 여기서는 주어진 컨텍스트와 문구로 call
to call say
사용합니다.
함수 말(문구) { Alert(this.name + ': ' + 구문); } 사용자 = { 이름: "John" }; // 사용자는 이것이 되고 "Hello"가 첫 번째 인수가 됩니다. say.call( user, "안녕하세요" ); // 존: 안녕하세요
우리의 경우 래퍼의 call
사용하여 컨텍스트를 원래 함수에 전달할 수 있습니다.
작업자 = { someMethod() { 1을 반환합니다. }, 느린(x) { Alert("" + x로 호출됨); return x * this.someMethod(); // (*) } }; 함수 cachingDecorator(func) { 캐시 = new Map(); 반환 함수(x) { if (cache.has(x)) { 캐시를 반환합니다.get(x); } 결과 = func.call(this, x); // "this"가 이제 올바르게 전달되었습니다. 캐시.세트(x, 결과); 결과 반환; }; } 작업자.느린 = cachingDecorator(worker.slow); // 이제 캐싱을 수행합니다. 경고(worker.slow(2)); // 작동 경고(worker.slow(2)); // 작동하지만 원본을 호출하지 않습니다(캐시됨).
이제 모든 것이 괜찮습니다.
모든 것을 명확하게 하기 위해 this
어떻게 전달되는지 더 자세히 살펴보겠습니다.
장식 후 worker.slow
이제 래퍼 function (x) { ... }
입니다.
따라서 worker.slow(2)
실행되면 래퍼는 2
인수로 가져오고 this=worker
(점 앞의 객체입니다)를 얻습니다.
래퍼 내에서 결과가 아직 캐시되지 않았다고 가정하면 func.call(this, x)
현재 this
( =worker
)와 현재 인수( =2
)를 원래 메서드에 전달합니다.
이제 cachingDecorator
더욱 보편적으로 만들어 보겠습니다. 지금까지는 단일 인수 함수로만 작동했습니다.
이제 다중 인수 worker.slow
메소드를 캐시하는 방법은 무엇입니까?
작업자 = { 느림(최소, 최대) { 최소 + 최대를 반환합니다. // 무서운 CPU-hogger가 가정됩니다. } }; // 동일한 인수 호출을 기억해야 합니다. 작업자.느린 = cachingDecorator(worker.slow);
이전에는 단일 인수 x
에 대해 결과를 저장하기 위해 cache.set(x, result)
사용하고 결과를 검색하기 위해 cache.get(x)
사용할 수 있었습니다. 하지만 이제 (min,max)
인수 조합의 결과를 기억해야 합니다. 기본 Map
단일 값만 키로 사용합니다.
가능한 솔루션은 다양합니다.
보다 다재다능하고 다중 키를 허용하는 새로운 맵과 유사한 데이터 구조를 구현합니다(또는 타사 사용).
중첩된 맵을 사용합니다. cache.set(min)
(max, result)
쌍을 저장하는 Map
이 됩니다. 따라서 result
cache.get(min).get(max)
으로 얻을 수 있습니다.
두 값을 하나로 결합합니다. 특별한 경우에는 "min,max"
문자열을 Map
키로 사용할 수 있습니다. 유연성을 위해 여러 값에서 하나의 값을 만드는 방법을 아는 데코레이터에 해싱 함수를 제공할 수 있습니다.
많은 실제 응용 프로그램의 경우 세 번째 변형이 충분하므로 계속 사용하겠습니다.
또한 x
뿐만 아니라 func.call
의 모든 인수를 전달해야 합니다. function()
에서 인수의 의사 배열을 arguments
로 얻을 수 있으므로 func.call(this, x)
func.call(this, ...arguments)
로 대체되어야 합니다.
다음은 더 강력한 cachingDecorator
입니다.
작업자 = { 느림(최소, 최대) { Alert(`${min},${max}로 호출됨`); 최소 + 최대를 반환합니다. } }; 함수 cachingDecorator(func, hash) { 캐시 = new Map(); 반환 함수() { 키 = 해시(인수); // (*) if (cache.has(키)) { 캐시를 반환합니다.get(키); } 결과 = func.call(this, ...arguments); // (**) 캐시.세트(키, 결과); 결과 반환; }; } 함수 해시(인수) { return args[0] + ',' + args[1]; } Worker.slow = cachingDecorator(worker.slow, hash); 경고(worker.slow(3, 5) ); // 작동 Alert( "다시 " + Worker.slow(3, 5) ); // 동일(캐시됨)
이제는 임의 개수의 인수와 함께 작동합니다(물론 해시 함수도 임의 개수의 인수를 허용하도록 조정해야 합니다. 이를 처리하는 흥미로운 방법은 아래에서 다루겠습니다).
두 가지 변경 사항이 있습니다.
(*)
줄에서는 hash
호출하여 arguments
로부터 단일 키를 생성합니다. 여기서는 인수 (3, 5)
를 키 "3,5"
로 바꾸는 간단한 "결합" 함수를 사용합니다. 더 복잡한 경우에는 다른 해싱 함수가 필요할 수 있습니다.
그런 다음 (**)
func.call(this, ...arguments)
사용하여 래퍼가 얻은 컨텍스트와 모든 인수(첫 번째 인수뿐만 아니라)를 원래 함수에 전달합니다.
func.call(this, ...arguments)
대신 func.apply(this, arguments)
사용할 수 있습니다.
내장 메소드 func.apply의 구문은 다음과 같습니다.
func.apply(컨텍스트, 인수)
this=context
로 설정하고 배열과 유사한 객체 args
인수 목록으로 사용하여 func
실행합니다.
call
과 apply
사이의 유일한 구문 차이점은 call
인수 목록을 기대하는 반면 apply
배열과 같은 객체를 사용한다는 것입니다.
따라서 이 두 호출은 거의 동일합니다.
func.call(context, ...args); func.apply(context, args);
주어진 컨텍스트와 인수를 사용하여 동일한 func
호출을 수행합니다.
args
와 관련하여 미묘한 차이점이 있습니다.
확산 구문 ...
사용하면 반복 가능한 args
call
할 목록으로 전달할 수 있습니다.
apply
배열과 유사한 args
만 허용합니다.
...그리고 실제 배열과 같이 반복 가능하고 배열과 유사한 개체의 경우 둘 중 하나를 사용할 수 있지만 대부분의 JavaScript 엔진이 내부적으로 더 잘 최적화하기 때문에 apply
더 빠를 것입니다.
모든 인수를 컨텍스트와 함께 다른 함수에 전달하는 것을 호출 전달 이라고 합니다.
가장 간단한 형태는 다음과 같습니다.
래퍼 = function() { return func.apply(this, 인수); };
외부 코드가 이러한 wrapper
호출하면 원래 함수 func
의 호출과 구별할 수 없습니다.
이제 해싱 함수를 한 가지 더 사소한 개선을 해보겠습니다.
함수 해시(인수) { return args[0] + ',' + args[1]; }
현재로서는 두 가지 인수에서만 작동합니다. 여러 개의 args
붙일 수 있다면 더 좋을 것입니다.
자연스러운 해결책은 arr.join 메소드를 사용하는 것입니다.
함수 해시(인수) { return args.join(); }
...안타깝게도 작동하지 않습니다. hash(arguments)
호출하고 arguments
객체는 반복 가능하고 유사 배열이지만 실제 배열은 아니기 때문입니다.
따라서 아래에서 볼 수 있듯이 join
호출하면 실패합니다.
함수 해시() { 경고(args.join()); // 오류:args.join은 함수가 아닙니다. } 해시(1, 2);
그래도 배열 조인을 사용하는 쉬운 방법이 있습니다.
함수 해시() { 경고( [].join.call(arguments) ); // 1,2 } 해시(1, 2);
그 트릭을 메소드 차용 이라고 합니다.
일반 배열( [].join
)에서 조인 메서드를 가져오고(빌려와) [].join.call
사용하여 arguments
컨텍스트에서 실행합니다.
왜 작동합니까?
네이티브 메소드 arr.join(glue)
의 내부 알고리즘이 매우 간단하기 때문입니다.
거의 "있는 그대로" 사양에서 가져옴:
glue
첫 번째 인수로 두고, 인수가 없으면 쉼표 ","
를 사용합니다.
result
빈 문자열로 둡니다.
result
에 this[0]
추가하세요.
glue
와 this[1]
추가하세요.
glue
와 this[2]
추가하세요.
... this.length
항목이 접착될 때까지 그렇게 합니다.
result
반환합니다.
따라서 기술적으로는 this
가져와 this[0]
, this[1]
...등을 함께 결합합니다. 이는 의도적으로 this
같은 배열을 허용하는 방식으로 작성되었습니다(우연이 아니며 많은 메서드가 이 방식을 따릅니다). 이것이 바로 this=arguments
에서도 작동하는 이유입니다.
일반적으로 한 가지 작은 일을 제외하고 함수나 메소드를 장식된 것으로 바꾸는 것이 안전합니다. 원래 함수에 func.calledCount
등의 속성이 있는 경우 장식된 함수는 해당 속성을 제공하지 않습니다. 포장지이기 때문이죠. 따라서 이를 사용하는 경우 주의가 필요합니다.
예를 들어 위의 예에서 slow
함수에 속성이 있으면 cachingDecorator(slow)
는 속성이 없는 래퍼입니다.
일부 데코레이터는 자체 속성을 제공할 수 있습니다. 예를 들어 데코레이터는 함수가 호출된 횟수와 소요된 시간을 계산하고 래퍼 속성을 통해 이 정보를 노출할 수 있습니다.
함수 속성에 대한 액세스를 유지하는 데코레이터를 생성하는 방법이 있지만, 이를 위해서는 함수를 래핑하기 위해 특수 Proxy
객체를 사용해야 합니다. 이에 대해서는 나중에 Proxy 및 Reflect 기사에서 논의하겠습니다.
데코레이터는 동작을 변경하는 함수를 둘러싼 래퍼입니다. 주요 업무는 여전히 해당 기능에 의해 수행됩니다.
데코레이터는 기능에 추가할 수 있는 "기능" 또는 "측면"으로 볼 수 있습니다. 하나를 추가할 수도 있고 여러 개를 추가할 수도 있습니다. 그리고 이 모든 것은 코드를 변경하지 않고도 가능합니다!
cachingDecorator
구현하기 위해 우리는 다음과 같은 방법을 연구했습니다.
func.call(context, arg1, arg2…) – 주어진 컨텍스트와 인수로 func
호출합니다.
func.apply(context, args) – context
this
로 전달하고 배열과 유사한 args
인수 목록에 전달하는 func
호출합니다.
일반 통화 전달은 일반적으로 apply
사용하여 수행됩니다.
래퍼 = function() { return original.apply(this, 인수); };
또한 객체에서 메서드를 가져와 다른 객체의 컨텍스트에서 call
때 메서드 차용 의 예를 보았습니다. 배열 메소드를 가져와서 arguments
에 적용하는 것은 매우 일반적입니다. 대안은 실제 배열인 나머지 매개변수 객체를 사용하는 것입니다.
세상에는 많은 데코레이터가 있습니다. 이 장의 과제를 해결하여 얼마나 잘 얻었는지 확인하세요.
중요도: 5
calls
속성에 함수에 대한 모든 호출을 저장하는 래퍼를 반환해야 하는 데코레이터 spy(func)
만듭니다.
모든 호출은 인수 배열로 저장됩니다.
예를 들어:
함수 작업(a, b) { 경고( a + b ); // 작업은 임의의 함수 또는 메서드입니다. } 일 = 스파이(일); 일(1, 2); // 3 일(4, 5); // 9 for (work.calls의 인수 허용) { 경고( '전화:' + args.join() ); // "통화:1,2", "통화:4,5" }
PS 그 데코레이터는 때때로 단위 테스트에 유용합니다. 고급 형식은 Sinon.JS 라이브러리의 sinon.spy
입니다.
테스트를 통해 샌드박스를 엽니다.
spy(f)
가 반환한 래퍼는 모든 인수를 저장한 다음 f.apply
사용하여 호출을 전달해야 합니다.
함수 스파이(func) { 함수 래퍼(...args) { // 인수 대신 ...args를 사용하여 Wrapper.calls에 "실제" 배열을 저장합니다. 래퍼.calls.push(args); return func.apply(this, args); } Wrapper.calls = []; 반환 포장지; }
샌드박스에서 테스트를 통해 솔루션을 엽니다.
중요도: 5
f
의 각 호출을 ms
밀리초만큼 지연시키는 데코레이터 delay(f, ms)
을 만듭니다.
예를 들어:
함수 f(x) { 경고(x); } // 래퍼 생성 f1000 = 지연(f, 1000); f1500 = 지연(f, 1500); f1000("테스트"); // 1000ms 후에 "test"를 표시합니다. f1500("테스트"); // 1500ms 후에 "test"를 표시합니다.
즉, delay(f, ms)
f
의 " ms
만큼 지연된" 변형을 반환합니다.
위 코드에서 f
단일 인수의 함수이지만 솔루션은 모든 인수와 컨텍스트 this
전달해야 합니다.
테스트를 통해 샌드박스를 엽니다.
해결책:
함수 지연(f, ms) { 반환 함수() { setTimeout(() => f.apply(this, 인수), ms); }; } f1000 = 지연(경고, 1000); f1000("테스트"); // 1000ms 후에 "test"를 표시합니다.
여기서 화살표 기능이 어떻게 사용되는지 참고하세요. 우리가 알고 있듯이 화살표 함수에는 this
및 arguments
가 없으므로 f.apply(this, arguments)
래퍼에서 this
및 arguments
가져옵니다.
일반 함수를 전달하면 setTimeout
인수 없이 함수를 호출하고 this=window
호출합니다(브라우저에 있다고 가정).
중간 변수를 사용하여 this
권한을 전달할 수 있지만 이는 조금 더 번거롭습니다.
함수 지연(f, ms) { 반환 함수(...args) { saveThis = this; // 이것을 중간 변수에 저장합니다. setTimeout(함수() { f.apply(savedThis, args); // 여기에서 사용하세요 }, ms); }; }
샌드박스에서 테스트를 통해 솔루션을 엽니다.
중요도: 5
debounce(f, ms)
데코레이터의 결과는 ms
밀리초 동안 활동이 없을 때까지(호출 없음, "휴지 기간") f
에 대한 호출을 일시 중단한 다음 최신 인수를 사용하여 f
한 번 호출하는 래퍼입니다.
즉, debounce
"전화 통화"를 수락하고 ms
밀리초 동안 조용해질 때까지 기다리는 비서와 같습니다. 그런 다음에만 최신 통화 정보를 "상사"(실제 f
호출)에게 전송합니다.
예를 들어, 함수 f
있고 이를 f = debounce(f, 1000)
으로 대체했습니다.
그런 다음 래핑된 함수가 0ms, 200ms 및 500ms에 호출되고 호출이 없으면 실제 f
1500ms에 한 번만 호출됩니다. 즉, 마지막 호출로부터 1000ms의 쿨다운 기간 이후입니다.
…그리고 가장 마지막 호출의 인수를 가져오며 다른 호출은 무시됩니다.
이에 대한 코드는 다음과 같습니다(Lodash 라이브러리의 디바운스 데코레이터 사용).
f = _.debounce(alert, 1000); 파"); setTimeout( () => f("b"), 200); setTimeout( () => f("c"), 500); // 디바운싱된 함수는 마지막 호출 후 1000ms를 기다린 후 실행됩니다: Alert("c")
이제 실제적인 예입니다. 사용자가 무언가를 입력하고 입력이 완료되면 서버에 요청을 보내고 싶다고 가정해 보겠습니다.
입력된 모든 문자에 대해 요청을 보내는 것은 의미가 없습니다. 대신 우리는 기다렸다가 전체 결과를 처리하고 싶습니다.
웹 브라우저에서는 입력 필드가 변경될 때마다 호출되는 함수인 이벤트 핸들러를 설정할 수 있습니다. 일반적으로 이벤트 핸들러는 입력된 모든 키에 대해 매우 자주 호출됩니다. 그러나 1000ms까지 debounce
하면 마지막 입력 후 1000ms 후에 한 번만 호출됩니다.
이 라이브 예제에서 핸들러는 결과를 아래 상자에 넣습니다. 시도해 보세요.
보다? 두 번째 입력은 디바운스된 함수를 호출하므로 해당 내용은 마지막 입력으로부터 1000ms 후에 처리됩니다.
따라서 debounce
키 누르기, 마우스 이동 등의 일련의 이벤트를 처리하는 좋은 방법입니다.
마지막 호출 후 지정된 시간을 기다린 후 결과를 처리할 수 있는 함수를 실행합니다.
작업은 debounce
데코레이터를 구현하는 것입니다.
힌트: 생각해보면 그것은 단지 몇 줄에 불과합니다 :)
테스트를 통해 샌드박스를 엽니다.
함수 디바운스(func, ms) { 시간 초과를 허용합니다. 반환 함수() { ClearTimeout(타임아웃); timeout = setTimeout(() => func.apply(this, 인수), ms); }; }
debounce
호출은 래퍼를 반환합니다. 호출되면 지정된 ms
후에 원래 함수 호출을 예약하고 이전 제한 시간을 취소합니다.
샌드박스에서 테스트를 통해 솔루션을 엽니다.
중요도: 5
래퍼를 반환하는 "조절" 데코레이터 throttle(f, ms)
를 만듭니다.
여러 번 호출되면 ms
밀리초당 최대 한 번 호출을 f
에 전달합니다.
디바운스 데코레이터와 비교하면 동작이 완전히 다릅니다.
debounce
"cooldown" 기간 후에 함수를 한 번 실행합니다. 최종 결과를 처리하는 데 좋습니다.
throttle
주어진 ms
시간보다 더 자주 실행되지 않습니다. 자주 발생해서는 안 되는 정기적인 업데이트에 적합합니다.
즉, throttle
전화를 받지만 상사(실제 f
전화)를 귀찮게 하는 비서와 같으며 ms
밀리초당 한 번 이하입니다.
해당 요구 사항을 더 잘 이해하고 해당 요구 사항의 출처를 알아보기 위해 실제 응용 프로그램을 확인해 보겠습니다.
예를 들어 마우스 움직임을 추적하고 싶습니다.
브라우저에서는 마우스가 움직일 때마다 실행되고 포인터가 움직일 때 포인터 위치를 가져오는 기능을 설정할 수 있습니다. 활성 마우스 사용 중에 이 기능은 일반적으로 매우 자주 실행되며 초당 100회(10ms마다) 정도일 수 있습니다. 포인터가 움직일 때 웹페이지의 일부 정보를 업데이트하고 싶습니다.
...하지만 update()
함수를 업데이트하는 것은 모든 미세한 움직임에 대해 수행하기에는 너무 무겁습니다. 100ms당 한 번 이상 업데이트하는 것도 의미가 없습니다.
따라서 우리는 이를 데코레이터로 래핑할 것입니다. 원래 update()
대신 throttle(update, 100)
각 마우스 움직임에 대해 실행되는 함수로 사용합니다. 데코레이터는 자주 호출되지만 최대 100ms당 한 번씩 update()
에 대한 호출을 전달합니다.
시각적으로 보면 다음과 같습니다.
첫 번째 마우스 움직임의 경우 장식된 변형은 즉시 update
호출을 전달합니다. 중요한 점은 사용자가 자신의 움직임에 대한 우리의 반응을 즉시 볼 수 있다는 것입니다.
그런 다음 마우스가 계속 움직이면 100ms
까지 아무 일도 일어나지 않습니다. 장식된 변형은 호출을 무시합니다.
100ms
가 끝나면 마지막 좌표로 한 번 더 update
발생합니다.
그러다가 마침내 마우스가 어딘가에서 멈췄습니다. 장식된 변형은 100ms
만료될 때까지 기다린 다음 마지막 좌표로 update
실행합니다. 따라서 매우 중요한 것은 최종 마우스 좌표가 처리된다는 것입니다.
코드 예:
함수 f(a) { console.log(a); } // f1000은 1000ms당 최대 한 번 f에 호출을 전달합니다. f1000 = 스로틀(f, 1000); f1000(1); // 1을 보여줍니다. f1000(2); // (제한 중, 1000ms가 아직 나오지 않음) f1000(3); // (제한 중, 1000ms가 아직 나오지 않음) // 1000ms가 초과되면... // ...3번 출력, 중간값 2는 무시됨
PS 인수와 this
f1000
에 전달된 컨텍스트는 원본 f
에 전달되어야 합니다.
테스트를 통해 샌드박스를 엽니다.
함수 throttle(func, ms) { isThrottled = false로 설정하세요. 저장된Args, 저장됨; 함수 래퍼() { if (isThrottled) { // (2) saveArgs = 인수; 저장됨이 = 이것; 반품; } isThrottled = 사실; func.apply(this, 인수); // (1) setTimeout(함수() { isThrottled = 거짓; // (3) if (savedArgs) { Wrapper.apply(savedThis,savedArgs); saveArgs = saveThis = null; } }, ms); } 반환 포장지; }
throttle(func, ms)
를 호출하면 wrapper
반환됩니다.
첫 번째 호출 중에 wrapper
func
실행하고 쿨다운 상태를 설정합니다( isThrottled = true
).
이 상태에서는 모든 호출이 savedArgs/savedThis
에 기억됩니다. 문맥과 주장 모두 똑같이 중요하므로 기억해야 합니다. 통화를 재현하려면 동시에 필요합니다.
ms
밀리초가 지나면 setTimeout
트리거됩니다. 쿨다운 상태가 제거되고( isThrottled = false
) 호출을 무시한 경우 마지막으로 기억된 인수와 컨텍스트를 사용하여 wrapper
실행됩니다.
세 번째 단계에서는 func
아닌 wrapper
실행합니다. 왜냐하면 func
실행해야 할 뿐만 아니라 다시 한번 쿨다운 상태로 들어가고 시간 초과를 설정하여 재설정해야 하기 때문입니다.
샌드박스에서 테스트를 통해 솔루션을 엽니다.