Through the lastDecorator mode, call/applyBased on the extension of several examples

Spy decorator

Degree of importance: *****

Create a decorator Spy (FUNc) that should return a wrapper that stores all calls to the function in its Calls property.

Each call is saved as an array of parameters.

Such as:

function work(a, b) {
  alert( a + b ); // Work is an arbitrary function or method
}

work = spy(work);

work(1.2); / / 3
work(4.5); / / 9

for (let args of work.calls) {
  alert( 'call:' + args.join() ); / / "call: 1, 2," "call: 4, 5"
}Copy the code

P.S. This decorator is sometimes useful for unit testing. Its advanced form is sinon.spy in the sinon.js library.

Solution:

The wrapper returned by SPY (f) should store all parameters and then forward the call using F.apply.

function spy(func) {

  function wrapper(. args) {
    // using ... args instead of arguments to store "real" array in wrapper.calls
    wrapper.calls.push(args);
    return func.apply(this, args);
  }

  wrapper.calls = [];

  return wrapper;
}Copy the code


Delay decorator

Degree of importance: *****

Create a decorator delay(f, ms) that delays each call to f by ms milliseconds.

Such as:

function f(x) {
  alert(x);
}

// create wrappers
let f1000 = delay(f, 1000);
let f1500 = delay(f, 1500);

f1000("test"); // Display "test" after 1000ms
f1500("test"); // Display "test" after 1500msCopy the code

In other words, delay(f, ms) returns a variant of f after delayed ms.

In the above code, f is a function of a single argument, but your solution should pass all arguments and the context this.

Solution:

Notice how the arrow function is used here. As we know, the arrow function doesn’t have its own this and arguments, so f.ply (this, arguments) gets this and arguments from the wrapper.

If we pass a regular function, setTimeout will call it with no arguments and this=window (assuming we’re in a browser environment).

We can still pass the correct this by using an intermediate variable, but that’s a bit tricky:

function delay(f, ms) {

  return function(. args) {
    let savedThis = this; // Store this in an intermediate variable
    setTimeout(function() {
      f.apply(savedThis, args); // Use it here
    }, ms);
  };

}Copy the code


Degree of importance: *****

The result of the Debounce (f, MS) decorator should be a wrapper that allows the call to be passed to F at most once every ms milliseconds.

In other words, when we call the “debmentioning” function, it will guarantee that all subsequent calls with less than ms milliseconds since the last call will be ignored.

Such as:

let f = debounce(alert, 1000);

f(1); // Execute immediately
f(2); / / is ignored

setTimeout( (a)= > f(3), 100); // Ignored (only 100 ms later)
setTimeout( (a)= > f(4), 1100); / / run
setTimeout( (a)= > f(5), 1500); // Ignored (not more than 1000 ms since last run)Copy the code

In practice, for functions that retrieve/update something, debounce is useful when we know there won’t be anything new for a while, so it’s best not to waste resources.

The solution
function debounce(f, ms) {

  let isCooldown = false;

  return function() {
    if (isCooldown) return;

    f.apply(this.arguments);

    isCooldown = true;

    setTimeout((a)= > isCooldown = false, ms);
  };

}Copy the code

The call to debounce returns a wrapper. There are two possible states:

  • isCooldown = false— Be ready to execute.
  • isCooldown = true— Wait time ends.

On the first call, isCooldown is false, so the call continues with the state changing to true.

When isCooldown is true, all other calls are ignored.

SetTimeout then restores the given delay to false after it ends.


Throttle decorator

Degree of importance: *****

Create a “throttle” decorator throttle(f, MS) — returns a wrapper that passes the call to F at most every 1ms. Calls that fall into the “cooling” period are ignored.

withdebounceIf the ignored call is the last one during the cooling period, it will be executed at the end of the delay.

Let’s look at a real life application to better understand this requirement and see where it comes from.

For example, we want to track mouse movement.

In the browser, we can set up a function that runs every time the mouse moves and gets the position of the pointer when the mouse moves. During mouse use, this function is usually executed very frequently, about 100 times per second (every 10 milliseconds).

When the mouse pointer moves, we want to update some information on the web page.

… But the update function update() is too heavy to execute on every tiny move. Updates more frequently than once every 100ms are also meaningless.

So we wrapped it in decorator: Use Throttle (update, 100) as a function to run on every mouse move, instead of the original update(). Decorators are called frequently, but at most forward calls to Update () once every 100ms.

Visually, it looks like this:

  1. For the first mouse movement, the decorated variant immediately passes the call toupdate. It’s important that users immediately see how we react to their actions.
  2. Then, move with the mouse until100msNothing happened. The decoration variant ignores the call.
  3. in100msAt the end — the last coordinate happens againupdate.
  4. And then, finally, the mouse stops somewhere. The change of decoration will wait100msExpire, and then run it once with the last coordinateupdate. Therefore, it is very important to process the final mouse coordinates.

A code example:

function f(a) {
  console.log(a);
}

// F1000 passes the call to F at most every 1000ms
let f1000 = throttle(f, 1000);

f1000(1); / / show 1
f1000(2); // (throttle, not yet 1000ms)
f1000(3); // (throttle, not yet 1000ms)

// When 1000ms time arrives...
/ /... Output 3, intermediate value 2 is ignoredCopy the code

The P.S. arguments and the context this passed to F1000 should be passed to the original f.

The solution
function throttle(f, ms) {

  let isThrottled = false,
    savedArgs,
    savedThis;

  function wrapper() {

    if (isThrottled) { / / (2)
      savedArgs = arguments;
      savedThis = this;
      return;
    }

    f.apply(this.arguments); / / (1)

    isThrottled = true;

    setTimeout(function() {
      isThrottled = false; / / (3)
      if (savedArgs) {
        wrapper.apply(savedThis, savedArgs);
        savedArgs = savedThis = null;
      }
    }, ms);
  }

  return wrapper;
}Copy the code

The call throttle(f, ms) returns the wrapper.

  1. During the first call,wrapperOnly runfAnd set the cooling state (isThrottled = true).
  2. In this state, all calls are remembered insavedArgs/savedThisIn the. Note that context is as important as arguments and should be noted down. We also need them to reproduce the call.
  3. … And then aftermsMilliseconds later, triggersetTimeout. Cooldown state removed (isThrottled = false), if we omit the call, it will be executed with the last remembered arguments and contextwrapper.

Step 3 runs not func, but wrapper, because not only do we need to execute func, we also need to go into the cooling state again and set timeout to reset it.

There are other common welcome comments, I will update the synchronization ~~