doubts on javascript apply - function memoization

2019-01-20 15:04发布

问题:

I'm struggling with an example of js memoization found on a book, here's the code:

Function.prototype.memoized = function(key){
    this._values = this._values || {};
    return this._values[key] !== undefined ? this._values[key] : this._values[key] = this.apply(this, arguments);
}

here's a fiddle with a complete example

what I don't really get is how this piece of code works and what it does, in particular the apply part:

return this._values[key] !== undefined ? this._values[key] : this._values[key] = this.apply(this, arguments);

I know and understand how apply works

The apply() method calls a function with a given this value and arguments provided as an array

suppose that this._values[key] is equal to undefined, then the returned value will be this.apply(this, arguments): does this code re-launch the memoized function? I've tried to add some logs inside the function to see how many times the function is called, but it seems it's been launched only once..

Can anyone please give me a hint? It's probably a dummy question, please be patient, thanks

回答1:

Let's use a simple example, fibonacci numbers.

function fib(n) {
    if (n < 2) return 1;
    return fib.memoized(n-1) + fib.memoized(n-2);
}

Here we can see that the memoized method is applied on the fib function, i.e. your this keyword refers to the fib function. It does not relaunch the memoized function, but "launches" the function on which it was called. However, it does call it with this set to the function itself, which does not make any sense. Better:

Function.prototype.memoize = function(key){
    if (!this._values)
        this._values = {};
    if (key in this._values)
        return this._values[key];
    else
        return this._values[key] = this.apply(null, arguments);
// pass null here:                            ^^^^
}

Even better would be if memoized would return a closure:

Function.prototype.memoized = function(v) {
    var fn = this, // the function on which "memoized" was called
        values = v || {};
    return function(key) {
        if (key in values)
            return values[key];
        else
            return values[key] = fn.apply(this, arguments);
    }
}
var fib = function(n) {
    if (n < 2) return 1;
    return fib(n-1) + fib(n-2);
}.memoized();
// or even
var fib = function(n) { return fib(n-1) + fib(n-2) }.memoized({0:1, 1:1});


回答2:

Notes

  1. Since you are attaching memoized to the Function.prototype, you can invoke this memoized on some other function only. Like in your example

    isPrime.memoized(5)
    
  2. Since you are invoking memoized on a function, the this will be referring to the function on which the memoized is invoked. So, in this case, this refers to isPrime.


Actual explanation

this._values = this._values || {};

This line makes sure that the isPrime has got an attribute with the name _values and it should have an empty object, if it is not there already.

this._values[key] !== undefined

This check is to make sure that we have been already called with key or not. If the value is not undefined, then return this._values[key].

Otherwise,

this._values[key] = this.apply(this, arguments)

store the result of calling this.apply(this, arguments) in this._values[key] and return it. Now the important part.

this.apply(this, arguments)

It is straight forward. arguments is an array like object. So, If you have actually called isPrime like this isPrime(1, 2, 3, 4), arguments will have {'0': 1, '1': 2, '2': 3, '3': 4}. Now that we are inside memoized, we need to invoke isPrime as it was intended to be invoked. So, this.apply(this, arguments) is done. Function.prototype.apply, tries to spread the array like object passed as the second parameter, while invoking the function.