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
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.memoized = 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});
Notes
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)
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.
Related
I'm currently working on some exercises to get a deeper understanding of the 'this' keyword. It does seem to have a lot of use cases so I did read on MDN about 'this'. I'm wondering, what does the 'this' keyword in this exercise refer to? I know that when you use apply (which has a maximum of 2 arguments), your first argument is where you want the this 'keyword to be referenced to' and the second argument is an array to which the 'this' keyword is newly referenced to. where is return fn.apply(this,arguments); being referenced to and what is arguments in the second argument? Is it in the function, the window? Sorry, I'm just really confused and trying to wrap my head around it. This is the line of code that I'm confused about:
function add(a, b) {
return a + b;
}
function invokeMax(fn, num) {
var counter = 0;
return function() {
counter++;
if (counter > num) {
return 'Maxed Out!';
}
return fn.apply(this, arguments);
};
}
You can console.log() this in the returned function and find out. Here, you will see it points to the global object (or window in a browser). This code doesn't depend on this being anything in particular. You could rewrite it as:
return fn.apply(null, arguments);
and get the same result.
this is determined by the way functions are called. The function here returns a function that (presumably) you will just call by itself, so the only calling's context is the window:
function add(a, b) {
return a + b;
}
function invokeMax(fn, num) {
var counter = 0;
return function() {
counter++;
if (counter > num) {
return 'Maxed Out!';
}
console.log("this is window?", this === window)
return fn.apply(this, arguments);
};
}
let f = invokeMax(add, 2)
console.log(f(5, 6))
Calling the same function in a different context leads to a different value of this:
function add(a, b) {
return a + b;
}
function invokeMax(fn, num) {
var counter = 0;
return function() {
counter++;
if (counter > num) {
return 'Maxed Out!';
}
console.log("this: ", this)
return fn.apply(this, arguments);
};
}
let someObj = {name: "myObj"}
someObj.f = invokeMax(add, 2) // now this will be someObj
someObj.f()
EDIT based on comment
A basic apply() example. The function will use the object passed to the first parameter of apply() as this in the function:
function print(someArg){
console.log(this.myName, someArg)
}
print.apply({myName: "Mark"}, ["hello"]) // set this to the passed in object
print.apply({myName: "Teddy"}, ["hello"]) // set this to the passed in object
print("hello") // called normally `this` will be the widow which doesn't have a `myName` prop.
// but you can give window that property (but probably shouldn't)
window.myName = "I'm window"
print("hello")
In that instance this refers to the current scope, which is the function where this is contained. In JavaScript, functions are also objects that can have properties assigned to them.
I am using Underscore.js to expand my knowledge and understanding of more complex javascript concepts and was hoping someone could help me understand how exactly the _.iteratee function gets executed in a specific example.
Here is that example, with comments about my understanding thus far.
I am using the _.map function like so:
_.map({one: 1, two: 2, three: 3}, function(num, key){ return num * 3; });
=> [3, 6, 9]
At the very bottom are the relevant functions being used, with some insignificant ones like _.keys being left out.
My understanding is this:
Within the _.map function, the first instance of iteratee within the body of the function is being set like so: iteratee = _.iteratee(iteratee, context); which based on the _.iteratee function (because an function is being passed into _.map) should evaluate to createCallback(value, context, argCount).
The next time the iteratee variable (which should now be a callback function) is used in the _.map function is here: results[index] = iteratee(obj[currentKey], currentKey, obj);.
This is where I get lost.
Questions:
Assuming my #1 assumption above is indeed correct, when we get to this line of the _.map function inside the loop: results[index] = iteratee(obj[currentKey], currentKey, obj); what we are actually calling is createCallback(obj[currentKey], currentKey, obj). So does obj[currentKey] get passed to the func parameter in createCallback? (Doesn't seem to make sense).
If the above is true, where I get lost is, when createCallback is evaluated, what is the value of obj which in createCallback is the argCount. I do not understand which part of the switch statement in createCallback gets referenced.
Which switch statement gets called in this case?
If I have that, I should be able to complete the trace to the closure inside createCallback. Any additional information you may provide to guide me is greatly appreciated.
Thanks.
Functions
_.map
_.map = _.collect = function(obj, iteratee, context) {
if (obj == null) return [];
iteratee = _.iteratee(iteratee, context);
var keys = obj.length !== +obj.length && _.keys(obj),
length = (keys || obj).length,
results = Array(length),
currentKey;
for (var index = 0; index < length; index++) {
currentKey = keys ? keys[index] : index;
results[index] = iteratee(obj[currentKey], currentKey, obj);
}
return results;
};
createCallback
var createCallback = function(func, context, argCount) {
if (context === void 0) return func;
switch (argCount == null ? 3 : argCount) {
case 1: return function(value) {
return func.call(context, value);
};
case 2: return function(value, other) {
return func.call(context, value, other);
};
case 3: return function(value, index, collection) {
return func.call(context, value, index, collection);
};
case 4: return function(accumulator, value, index, collection) {
return func.call(context, accumulator, value, index, collection);
};
}
return function() {
return func.apply(context, arguments);
};
};
_.iteratee
_.iteratee = function(value, context, argCount) {
if (value == null) return _.identity;
if (_.isFunction(value)) return createCallback(value, context, argCount);
if (_.isObject(value)) return _.matches(value);
return _.property(value);
};
In this particular line,
results[index] = iteratee(obj[currentKey], currentKey, obj);
iteratee will be actually called like this
iteratee(<actual value of obj[currentKey]>, currentKey, obj);
So, obj will not be passed on to the iteratee function.
In your case, since you are passing an object as the first argument, on each and every iteration the results accumulation line would evaluate something similar to this
results[0] = iteratee(1, "one", obj);
results[1] = iteratee(2, "two", obj);
results[2] = iteratee(3, "three", obj);
iteratee =
And when this line is executed,
_.iteratee(iteratee, context);
iteratee is actually the function you passed as one of the arguments to _.map. And since you are not passing a context object explicitly, by default, the value would be undefined. So,
if (context === void 0) return func;
check in the createCallback function will evaluated to true (since undefined == void 0), the function you actually passed to the _.map will be used as the call back function.
Taken from Secrets of the JavaScript Ninja, Listing 5.14 passes the num argument of isPrime to a memoized function, I assumed the num argument would be visible in #1, and not in #2, But it's actually The other way around!
Function.prototype.memoized = function(key){
this._values = this._values || {};
return this._values[key] !== undefined ?
this._values[key] :
this._values[key] = this.apply(this, arguments);
};
Function.prototype.memoize = function() {
var fn = this; //#1
console.log(Array.prototype.slice.call(arguments)); // Prints []
return function(){ //#2
console.log(Array.prototype.slice.call(arguments)); //Prints [17]
return fn.memoized.apply(fn, arguments);
};
};
var isPrime = (function(num) {
var prime = num != 1;
for (var i = 2; i < num; i++) {
if (num % i == 0) {
prime = false;
break;
}
}
return prime;
}).memoize();
assert(isPrime(17), "17 is prime"); //#3
How is it possible that the num argument (17 in this case) is visible only in the inner closure (#2) and not in the wrapping memoize function? I don't understand at which point the memoize() call passes the num argument to the closure in #2.
PS. To reiterate, and complement the Question above: Why can't I see the num argument in #1?
Thank you.
Because #2 is the function that is assigned to isPrime. And you pass 17 to isPrime. On the other hand, you call .memoize (#1) without passing any arguments to it:
(function() { ... }).memoize()
// ^^ no arguments
I don't understand at which point the memoize() call passes the num argument to the closure in #2.
It doesn't. memoize returns a new function and it's that function to which the argument is passed.
Because at that point, the anonymous function has't been called.
What you are doing is calling memoize, with your anonymous function as the this value and no arguments (hence the empty arguments array)
memoize then returns a function, which basically checks "has this function already been called with this argument", and either return the previous value if so, or calls the function and stores its return value if not.
What this means is that your function is only called when you actually do isPrime(17), and at that point your are inside the function where it says return function() {...} and that's where you can see your argument.
Since bind is not a cross browser (old ones) function , there is a polyfill for it : ( from John Resig's book)
/*1*/ Function.prototype.bind = function ()
/*2*/ {
/*3*/ var fn = this,
/*4*/ args = Array.prototype.slice.call(arguments),
/*5*/ object = args.shift();
/*6*/ return function ()
/*7*/ {
/*8*/ return fn.apply(object,
/*9*/ args.concat(Array.prototype.slice.call(arguments)));
/*10*/ };
/*11*/ };
But I don't understand why do we need arguments at line #9.
I mean :
If I have this object :
var foo = {
x: 3
}
And I have this function :
var bar = function(p,b){
console.log(this.x+' '+p+' '+b);
}
So , if I want bar to run in the foo context , with parameters - All I need to do is :
var boundFunc = bar.bind(foo,1,2)
boundFunc ()...
So When I run var.bind(foo,1,2) the arguments is [object Object],1,2.
Those arguments are saved at line #4.
Great.
Now , the bind function returns its own closured function :
function ()
{
return fn.apply(object,
args.concat(Array.prototype.slice.call(arguments)));
}
Question
Why do we need arguments here ? it seems that they are for something like :
var boundFunc = bar.bind(foo,1,2)
boundFunc (more1,more2....) //<----- ??
Am I missing something ?
Oonce I set the first var boundFunc = bar.bind(foo,1,2) , I already declared the parameters. why do we need them twice ?
There are two places you can pass in arguments to the bound function:
1) When you call bind (the first arguments). These are always applied to the bound function when it is called.
2) When you call the bound function (the second arguments). These are the "more1, more2" that you mention. These change depending on what is provided when the bound argument is called.
Line 9 is combining the original bound arguments with the supplied extra arguments.
I guess the concept you might be confused about is that you don't have to bind ALL arguments initially - you can bind just the context object, or you can bind the first one argument as well but have callers of the bound function supply the rest. For example:
function sum() {
var _sum = 0
for (var i = 0; i < arguments.length ; i++) {
_sum += arguments[i];
}
return _sum;
}
var sum_plus_two = sum.bind({},2);
sum_plus_two(5,7) == 14;
.bind also serves as partial application solution. Event handlers might be the best example:
var handler = function(data, event) { };
element.addEventListener('click', handler.bind(null, someData));
If the arguments from the actual function call wouldn't be passed on, you couldn't access the event object.
function asArray(quasiArray, start) {
var result = [];
for (var i = (start || 0); i < quasiArray.length; i++)
result.push(quasiArray[i]);
return result;
}
function partial(func) {
var fixedArgs = asArray(arguments, 1);
return function(){
return func.apply(null, fixedArgs.concat(asArray(arguments)));
};
}
function compose(func1, func2) {
return function() {
return func1(func2.apply(null, arguments));
};
}
var isUndefined = partial(op["==="], undefined);
var isDefined = compose(op["!"], isUndefined);
show(isDefined(Math.PI));
show(isDefined(Math.PIE));
Why can't the function compose simply return:
func1(func2);
and give the proper output. I thought the partial function which is stored in the variable isUndefined already returns func.apply(null, [fixed, arguments])
var op = {
"+": function(a, b){return a + b;},
"==": function(a, b){return a == b;},
"===": function(a, b){return a === b;},
"!": function(a){return !a;}
/* and so on */
};
Both partial and compose are higher-order functions.
isUndefined will return a function that, when invoked, will invoke the originally passed function with the original arguments plus any new arguments passed at invocation.
To answer your question, you'd be calling apply on the function returned from partial which will in turn, call apply on the function originally passed to partial.
You want compose to return a function that when called, will return the result of calling the first function passed the second function as an argument (with the second function passed the arguments passed to the compose invocation). If compose returned func1(func2), then you'd assign the result of the invocation to the variable isDefined.
EDIT:
Now that we have op, let's try to decompose this:
var isUndefined = partial(op["==="], undefined);
this is equivalent to
var isUndefined = partial(function(a, b){return a === b;}, undefined);
isUndefined is assigned a function that, when called, will call the function passed as the first argument to partial, passing in undefined as the first argument to that function call, followed by the arguments passed to the function isUndefined i.e.
partial(function(a, b){return a === b;}, undefined /* this will become 'a' when isUndefined is invoked */)(argumentForisUndefined /* this will become 'b' when isUndefined is invoked */);
isDefined composes isUndefined with another function that negates the result of isUndefined.
var isDefined = compose(op["!"], isUndefined);
is equivalent to
var isDefined = compose(function(a){return !a;}, isUndefined);
which is equivalent to (renamed variables for clarity)
var isDefined = compose(
function(a){return !a;},
partial( /* partial function becomes 'a' passed to first function */
function(b, c) {
return b === c;
},
undefined /* undefined becomes 'b' passed to partial */
)
)(argumentForisDefined /* argumentForisDefined becomes 'c' passed to partial */);
If we look at what we have so far and substituting for readability, boils down to a function that takes an argument and compares it to undefined, negates the result and returns a boolean
var isDefined = function (b) { return !undefined === b; }
So lets simply dissect it. Assuming we have this compose function:
function compose(func1, func2) {
return func1(func2.apply(null, arguments));
}
What will happen when you use it like this?
a = compose(function(){console.log(1)}, function(){console.log(2)});
The second function would be call immediately outputting 2, and straight afterwards the first function will be called outputting 1. a will be undefined, because the first function does not return anything.
What you want combine to do, is to return a new function, that combines the two other functions and that you can call at will.
Doing the above all on the original compose, will return a new function, that, when you call it with a() will output 2 and then 1.