Trying to understand the source of underscore.js - the call and application used in the library

In Jeremy Ashkenas amazing Underscore.js library, I tried to understand one thing about the source file. I do not understand this:

var slice = Array.prototype.slice; args = slice.call(arguments, 2); 

So that:

 args = Array.prototype.slice.call(arguments, 2); 

.call or .apply are function methods. But here, what functions does .call ? The first parameter should be the context, but is arguments the context? The second parameter should be the parameters passed to the function. Here they have number 2 . What does it mean? Sometimes a library uses 1 or 0 . Are they the number of parameters that are passed to the function?

 _.bind = function bind(func, context) { var bound, args; if (func.bind === nativeBind && nativeBind) return nativeBind.apply(func, slice.call(arguments, 1)); if (!_.isFunction(func)) throw new TypeError; args = slice.call(arguments, 2); return bound = function() { if (!(this instanceof bound)) return func.apply(context, args.concat(slice.call(arguments))); ctor.prototype = func.prototype; var self = new ctor; var result = func.apply(self, args.concat(slice.call(arguments))); if (Object(result) === result) return result; return self; }; }; 

Question 2: I do not quite understand the logic of this function. Need help to understand. An example should be very helpful.

  // Invoke a method (with arguments) on every item in a collection. _.invoke = function(obj, method) { var args = slice.call(arguments, 2); return _.map(obj, function(value) { return (method.call ? method || value : value[method]).apply(value, args); }); }; 

Thank you for your help.

+7
source share
1 answer

The slice function in the Array prototype expects this to reference the array on which it should work. In other words, if you have a real array:

 var myArray = [1, 2, 3]; 

and you call slice() :

 var sliced = myArray.slice(1); 

Then in this call to slice() , this refers to the array "myArray". As Rainos notes in a comment:

 myArray.slice(1) 

coincides with

 myArray.slice.call(myArray, 1); 

Thus, when you use call() to call a function and pass its arguments as a context object, the slice() code works on arguments . Other parameters passed through .call() are just the parameter or parameters for slice() . In my example above, note that I passed 1 function.

Now, as for your second question, the .invoke() function first isolates the arguments passed after the first two. This means that when you use _.invoke() , you pass two or more arguments to it: the first is a list for work, the second is a method, and (optional) subsequent arguments are passed to the method for each element of the list.

This call to _.map() is complicated (and actually I think it's a little nonsense). It iterates through the list, calling a function for each value in the list. This function allows you to first determine whether the parameter "method" is actually a function. If so, then it calls this function through .apply() with the list item as context. If the β€œmethod” is not a function, it assumes that it is the property name of each list item and that the properties are functions.

So, for example, with a simple list, this is pretty simple:

 var myList = [1, 2, 3]; var result = _.invoke(myList, function(n) { return this * n; }, 2); 

This will give the result [2, 4, 6] , because the function I passed multiplies my context object ( this ) with the passed parameter, and I passed 2 in the _.invoke() call.

With a more complex list, I can use the second flavor of _.invoke() and call the method for each object in the list:

 var getName = function(prefix) { return prefix + " " + this.name; }; var list = [ { name: "Bob", getName: getName }, { name: "Sam", getName: getName }, { name: "Lou", getName: getName } ]; var result = _.invoke(list, "getName", "Congressman"); 

This will call the getName function for each object in the list and return a list of results. The effect will be a list ["Congressman Bob", "Congressman Sam", "Congressman Lou"] .

Now about this nonsense. In the code for _.invoke() :

 return _.map(obj, function(value) { return (method.call ? method || value : value[method]).apply(value, args); }); 

This is a subexpresion method || value method || value will always return the value of the "method" or, at least, almost always prohibits any exotic trick. If method.call is true, then the method reference must also be true. Also, if it were my code, I would check the method outside the _.map() callback so that the solution would not need to be repeated over and over again. Maybe something like:

 return _.map(obj, method.call ? function(value) { method.apply(value, args); } : function(value) { value[method].apply(value, args); } ); 
+7
source

All Articles