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.
Related
I'm re-creating functions from the underscore library but I'm running into a roadblock while trying to implement the _.reject() function. For the purposes of this question, I'll include the code I've written for three functions: _.each(), _.filter(), and _.reject().
_.each = function(collection, iterator) {
if (Array.isArray(collection)) {
for (var i = 0; i < collection.length; i++) {
iterator(collection[i], i, collection);
}
} else {
for (var i in collection) {
iterator(collection[i], i, collection);
}
}
};
_.filter = function(collection, test) {
var results = [];
_.each(collection, function(i) {
if (test(i)) {
results.push(i);
}
})
return results;
};
And here's the code for the function that I'm getting a problem with, the _.reject() method, along with the isEven() function that I'm passing in as the test argument.
_.reject = function(collection, test) {
return _.filter(collection, !test);
};
var isEven = function(x) {
if (x % 2 === 0) return true;
return false;
};
According to MDN's page on Expressions and Operators, the Logical NOT (!) operator Returns false if its single operand can be converted to true; otherwise, returns true.
But when I run the following code _.reject([1,2,3], isEven) I get an error saying that test is not a function. Why am I unable to use the ! operator while invoking a function (e.g., _.filter([1,2,3], !isEven))?
When you refer to a function, rather than calling it, you're referring to the function's object reference:
function foo() {
alert("Hi there");
}
var f = foo; // <== Getting the function's reference, not calling it
f(); // <== Now we call it
So !isEven would be negating the function reference. Since isEven is a non-null reference, it's truthy; and so !isEven is false. Not what you want. :-)
Your reject could be written with a function that calls test and inverts its return value:
_.reject = function(collection, test) {
return _.filter(collection, function(e) { return !test(e); });
};
Or if you want to go the functional programming approach, you can write a function that, when called, will return a new function that negates the return value:
function not(f) {
return function() {
return !f.apply(this, arguments);
};
}
Then anywhere you want to invert a callback's meaning, you'd just use invert:
_.reject = function(collection, test) {
return _.filter(collection, not(test));
};
Why am I unable to use the ! operator while invoking a function (e.g., _.filter([1,2,3], !isEven))?
Note that you are actually not invoking isEven, you are merely referencing it. As you said, ! "Returns false if its single operand can be converted to true; otherwise, returns true."
isEven is a reference to a function, i.e. an object. Objects convert to true, hence !test results in false:
_.filter([1,2,3], false))
Now you are passing a Boolean instead of a function to _.filter, hence the error message "test is not a function".
Instead, you have to pass a function that negates the result of the test function:
_.filter([1,2,3], function() {
return !test.apply(this, arguments);
});
You cannot negate a function. It's a meaningless thing to do (unless you really want the value false without typing it). Instead you want to negate the function's return value. I suspect what you want is something like so
_.reject = function(collection, test) {
return _.filter(collection, function(e) { return !test(e); });
}
I have a question about the annotated underscore.js source code:
It says:
Internal function that returns an efficient (for current engines) version of the passed-in callback, to be repeatedly applied in other Underscore functions.
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);
};
};
Explicitly, how is reached the efficiency in this block of code ?
Cause I see that with the wrapper, is created one more level of indirection and the scope chain grows one more level two.
I need to understand how is reached the efficiency, in order to apply this tricks in my own js.
Thanks!
There seems to be some shuffling of argument order but some of the cases are actually equivalent if later arguments were left empty. I think that logic also probably makes the callback play nicely with browser optimisations. For example, if the return value is predictable then the result may be inlined rather than calling the actual function each time.
Here is a related discussion about performance and arguments, where testing showed some big speed differences:
Performance penalty for undefined arguments
When using the underscore iterator functions (such as _.map, _.reduce and _.each), the callbacks that are provided offer different arguments. For example:
_.map(list, function(value, key, list) {});
_.reduce(list, function(memo, num, list) {});
The createCallback is a convenience function used internally by underscore to define the order of the arguments in these iterator functions. If we look at the _.reduce function we can see this line:
iteratee = createCallback(iteratee, context, 4);
Which will output:
func.call(context, accumulator, value, index, collection);
Where func is the iterator function provided to _.reduce
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.
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.
I am new to underscore.js. What is the purpose of [context] in _.each()? How should it be used?
The context parameter just sets the value of this in the iterator function.
var someOtherArray = ["name","patrick","d","w"];
_.each([1, 2, 3], function(num) {
// In here, "this" refers to the same Array as "someOtherArray"
alert( this[num] ); // num is the value from the array being iterated
// so this[num] gets the item at the "num" index of
// someOtherArray.
}, someOtherArray);
Working Example: http://jsfiddle.net/a6Rx4/
It uses the number from each member of the Array being iterated to get the item at that index of someOtherArray, which is represented by this since we passed it as the context parameter.
If you do not set the context, then this will refer to the window object.
Extras:
To answer the What's the advantage of that? Why not just refer to someOtherArray[num] rather than this[num]? upvoted question found in the comments below, let's move the anonymous iteratee callback into a function for easy re-use:
const someOtherArray = ["name","patrick","d","w"];
const yetAnotherArray = ["what","goes","here","?"];
function alertStr(num){
alert( this[num] );
}
_.each([1, 2, 3], alertStr, someOtherArray);
_.each([1, 2, 3], alertStr, yetAnotherArray);
You can see how the this reference allows us to re-use the iteratee function across multiple _.each calls with different context values. This would not work if we had the someOtherArray hardcoded inside the iteratee.
context is where this refers to in your iterator function. For example:
var person = {};
person.friends = {
name1: true,
name2: false,
name3: true,
name4: true
};
_.each(['name4', 'name2'], function(name){
// this refers to the friends property of the person object
alert(this[name]);
}, person.friends);
The context lets you provide arguments at call-time, allowing easy customization of generic pre-built helper functions.
some examples:
// stock footage:
function addTo(x){ "use strict"; return x + this; }
function pluck(x){ "use strict"; return x[this]; }
function lt(x){ "use strict"; return x < this; }
// production:
var r = [1,2,3,4,5,6,7,8,9];
var words = "a man a plan a canal panama".split(" ");
// filtering numbers:
_.filter(r, lt, 5); // elements less than 5
_.filter(r, lt, 3); // elements less than 3
// add 100 to the elements:
_.map(r, addTo, 100);
// encode eggy peggy:
_.map(words, addTo, "egg").join(" ");
// get length of words:
_.map(words, pluck, "length");
// find words starting with "e" or sooner:
_.filter(words, lt, "e");
// find all words with 3 or more chars:
_.filter(words, pluck, 2);
Even from the limited examples, you can see how powerful an "extra argument" can be for creating re-usable code. Instead of making a different callback function for each situation, you can usually adapt a low-level helper. The goal is to have your custom logic bundling a verb and two nouns, with minimal boilerplate.
Admittedly, arrow functions have eliminated a lot of the "code golf" advantages of generic pure functions, but the semantic and consistency advantages remain.
I always add "use strict" to helpers to provide native [].map() compatibility when passing primitives. Otherwise, they are coerced into objects, which usually still works, but it's faster and safer to be type-specific.
Simple use of _.each
_.each(['Hello', 'World!'], function(word){
console.log(word);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
Here's simple example that could use _.each:
function basket() {
this.items = [];
this.addItem = function(item) {
this.items.push(item);
};
this.show = function() {
console.log('items: ', this.items);
}
}
var x = new basket();
x.addItem('banana');
x.addItem('apple');
x.addItem('kiwi');
x.show();
Output:
items: [ 'banana', 'apple', 'kiwi' ]
Instead of calling addItem multiple times you could use underscore this way:
_.each(['banana', 'apple', 'kiwi'], function(item) { x.addItem(item); });
which is identical to calling addItem three times sequentially with these items. Basically it iterates your array and for each item calls your anonymous callback function that calls x.addItem(item). The anonymous callback function is similar to addItem member function (e.g. it takes an item) and is kind of pointless. So, instead of going through anonymous function it's better that _.each avoids this indirection and calls addItem directly:
_.each(['banana', 'apple', 'kiwi'], x.addItem);
but this won't work, as inside basket's addItem member function this won't refer to your x basket that you created. That's why you have an option to pass your basket x to be used as [context]:
_.each(['banana', 'apple', 'kiwi'], x.addItem, x);
Full example that uses _.each and context:
function basket() {
this.items = [];
this.addItem = function(item) {
this.items.push(item);
};
this.show = function() {
console.log('items: ', this.items);
}
}
var x = new basket();
_.each(['banana', 'apple', 'kiwi'], x.addItem, x);
x.show();
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
In short, if callback function that you pass to _.each in any way uses this then you need to specify what this should be referring to inside your callback function. It may seem like x is redundant in my example, but x.addItem is just a function and could be totally unrelated to x or basket or any other object, for example:
function basket() {
this.items = [];
this.show = function() {
console.log('items: ', this.items);
}
}
function addItem(item) {
this.items.push(item);
};
var x = new basket();
_.each(['banana', 'apple', 'kiwi'], addItem, x);
x.show();
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
In other words, you bind some value to this inside your callback, or you may as well use bind directly like this:
_.each(['banana', 'apple', 'kiwi'], addItem.bind(x));
how this feature can be useful with some different underscore methods?
In general, if some underscorejs method takes a callback function and if you want that callback be called on some member function of some object (e.g. a function that uses this) then you may bind that function to some object or pass that object as the [context] parameter and that's the primary intention. And at the top of underscorejs documentation, that's exactly what they state: The iteratee is bound to the context object, if one is passed
As explained in other answers, context is the this context to be used inside callback passed to each.
I'll explain this with the help of source code of relevant methods from underscore source code
The definition of _.each or _.forEach is as follows:
_.each = _.forEach = function(obj, iteratee, context) {
iteratee = optimizeCb(iteratee, context);
var i, length;
if (isArrayLike(obj)) {
for (i = 0, length = obj.length; i < length; i++) {
iteratee(obj[i], i, obj);
}
} else {
var keys = _.keys(obj);
for (i = 0, length = keys.length; i < length; i++) {
iteratee(obj[keys[i]], keys[i], obj);
}
}
return obj;
};
Second statement is important to note here
iteratee = optimizeCb(iteratee, context);
Here, context is passed to another method optimizeCb and the returned function from it is then assigned to iteratee which is called later.
var optimizeCb = 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);
};
};
As can be seen from the above method definition of optimizeCb, if context is not passed then func is returned as it is. If context is passed, callback function is called as
func.call(context, other_parameters);
^^^^^^^
func is called with call() which is used to invoke a method by setting this context of it. So, when this is used inside func, it'll refer to context.
// Without `context`
_.each([1], function() {
console.log(this instanceof Window);
});
// With `context` as `arr`
var arr = [1, 2, 3];
_.each([1], function() {
console.log(this);
}, arr);
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
You can consider context as the last optional parameter to forEach in JavaScript.