I'm reading the book Javascript: The Good Parts. And I'm confused by the following code.
Function.method('curry', function ( ) {
var slice = Array.prototype.slice,
args = slice.apply(arguments),
that = this;
return function ( ) {
return that.apply(null, args.concat(slice.apply(arguments)));
};
});
Where is the null in slice.apply(arguments)?
arguments is being passed as the context (this), not the function's arguments.
It's equivalent to arguments.slice(), except that arguments.slice() doesn't exist.
That's the equivalent of calling slice() on an array with no arguments - i.e. it returns an array with all the elements of the original array. In this case, 'arguments' is not a true array, so calling Array.prototype.slice on it in effect turns it into one.
Two different functions are being invoked.
In the first case
var slice = Array.prototype.slice,
args = slice.apply(arguments),
for great explanation refer to
http://blog.sebarmeli.com/2010/11/12/understanding-array-prototype-slice-applyarguments/
apply method of Array.prototype.slice, is being invoked, which will convert the arguments passed to the function into an array.
In the second function apply method of function is being called. The usage and details of this function is well defined here
http://www.devguru.com/technologies/ecmascript/quickref/apply.html
Related
Historically, we had to do:
var args = Array.prototype.slice.call(arguments)
fn.apply(this, args);
to grab actual function call arguments and pass them to Function.prototype.apply. However, since some time, I can see that arguments object does have slice method available, hence above snippet is not required anymore.
The question is - when did it change, what was it, a specification update?
edit: I was unclear above... I mean that fn.apply(this, arguments) does work (I checked in latest chrome and firefox):
function add(a,b){
return a + b;
}
function whatever(){
return add.apply(null, arguments)
}
whatever(2,3) // 5
in other words, arguments can be now used within apply directly. Some time ago, it didn't work, it had to be passed to Array.prototype.slice.apply as above.
It isn't.
Try the following:
var foo = function() {
console.log(Object.prototype.toString.call(arguments));
};
foo(); // [object Arguments]
console.log(Object.prototype.toString.call([])); // [object Array]
Note that the difference between arrays and objects is a little fuzzy in JS-land:
var foo = { "0": 1, "1": 2 };
Object.defineProperty(foo, "length", {
value: 2
});
foo will now work with a surprising number of array methods using call, apply, etc.
No it didn't.
According to MDN
The arguments object is an Array-like object corresponding to the arguments passed to a function.
Example: We want to return the arguments passed to function as an array
Below code throws error because we are directly calling slice method on arguments Array-like object.
function fun(){
// throws error arguments.slice is not function
return arguments.slice(0);
}
Below works expected as we are converting Array-like object to Array
function fun(){
// returns the arguments as array
return Array.prototype.slice.call(arguments, 0)
}
I'm learning functional programming and node.js, and I came across this odd problem when using Function.prototype.apply and .bind.
function Spy(target, method) {
var obj = {count: 0};
var original = target[method]
target[method] = function (){//no specified arguments
obj.count++
original.apply(this, arguments)//only arguments property passed
}
return obj;
}
module.exports = Spy
This code works, it successfully spies on target.method.
//same code here
target[method] = function (args){//args specified
obj.count++
original.apply(this, args)//and passed here
}
//same code here
This code, however, does not. It gives an error message: TypeError: CreateListFromArrayLike called on non-object.
And then the biggest surprise is, this method works perfectly fine.
//same code here
target[method] = function (args){
obj.count++
original.bind(this, args)
}
//same code here
So why exactly do I get this error? Is it because function arguments are not necessarily objects? Or is it because apply has a stricter description than bind?
In this version:
target[method] = function (args){//args specified
obj.count++
original.apply(this, args)//and passed here
}
Here you are not taking all the arguments but just one, named args. Since apply expects an array like object you cannot use args since it is only the first argument passed to the original target.
You can change it to:
target[method] = function (arg){ //only one argument specified
obj.count++
original.apply(this,[arg]) //one argument passed here
}
Now it works, but you can only spy on one argument functions. Using call would be better since you only have one extra argument:
target[method] = function (arg){ //only one argument specified
obj.count++
original.call(this,arg) //one argument passed here
}
Now bind is a totally different animal. It partial applies functions, thus return functions. Imagine you need to send a callback that takes no arguments but calls a function with some arguments you have when making it. You see code like:
var self = this;
return function() {
self.method(a, b);
}
Well. bind does this for you:
return this.method.bind(this, a, b);
When calling either of these returned functions the same happens. The method method is called with the arguments a and b. So calling bind on a function returns a partial applied version of that function and does not call it like call or apply does.
bind is called the same way as call is, even though they do very different things.
If you really wanted to use bind in this way. You could use the spread operator (ES2015) to expand the arguments 'array' to individual arguments:
original.bind(null, ...args);
That will bind the original function with the array values as individual arguments.
I'm reading Javascript: The Definitive Guide 6th Edition. It teaches ECMAscript 5. Anyway, it doesn't explain certain things thoroughly, like the call() function for example. This is about the extent of the book's definition:
Any arguments to call() after the first invocation context argument are the values that are passed to the function that is invoked. For example, to pass two numbers to the function f() and invoke it as if it were a method of the object o, you could use code like this:
f.call(o, 1, 2);
In the next section the author builds a map function. I've been studying Ruby so I know how map works. My question is about the implementation using the call() function. It looks like this:
var map = function(a,f, o) {
var results = [];
for(var i = 0, len = a.length; i < len; i++) {
if (i in a)
results[i] = f.call(o || null, a[i], i, a);
}
return results;
};
It then defines a square function and puts map to use:
function square(x){
return x*x;
}
var array = [1,2,3,4,5];
var results = map(array, square);
What is the purpose of the i, and a parameters in the call() function? If I remove them I get the same results.
Array.prototype.map is defined to pass the index and the array to the callback, just in case you need them. For example, instead of square(x), you could use Math.pow(base, exponent):
var results = [1, 2, 3, 4, 5].map(Math.pow);
console.log(results); // [1, 2, 9, 64, 625]
This map behaves in the same way. You don’t have to use the arguments if you don’t need them in a particular case.
Function.call allows you to call a function as though it were a method attached to an object.
What this means is you can have a function that is defined somewhere unrelated to an object, and then you can call that function as though it was a part of that object. This is a long way of saying that when you use Function.call, you are telling the JS engine to use the first parameter whenever you use 'this' inside the function.
So:
function set_field_value(name, value) {
// do stuff
this[name] = value;
}
makes no sense by itself, because the special variable 'this' is not set to anything (meaningful)
But, if you use call, you can set it to whatever you want:
// if my_object = some object:
set_field_value.call(my_object, 'firstname', 'bob');
console.log(my_object.firstname); // prints 'bob'
The only important argument to call is the first one, (in the above case, my_object) because the first argument becomes 'this' inside the function. The rest of the arguments are passed 'as is' to the function.
So - in your example, the i and a arguments are there to make the map function look like other map functions, which provide the array (a) and index (i) that are being worked on.
Hope that helps,
Jay
PS - I strongly recommend the book 'Javascript: the good parts' - it makes a lot more sense than the definitive guide.
f.call in this example equals to square.call, and square requires only one parameter(x), so i and a are totally redundant here (and not used). Only a[i] is used by the function.
However, since you can pass in any function you want as the second parameter of the map function, chances are there will be another function instead of square coming up in the book, and that function would require those additional two parameters as well. Or you can make one example yourself to try it.
function threeParams(a, b, c) {
return [a, b, c]; // simply puts the three parameters in an array and returns it
}
var array = [1,2,3,4,5];
var results = map(array, threeParams);
Your main confusion is not really about the call method. It's more about how javascript treats function arguments.
Forget about call for a moment and let's look at a regular function to minimize the number of things under consideration.
In javascript, functions are allowed to be called with more arguments than is specified. This is not considered an error. The arguments may be accessed via the arguments object:
function foo (arg1) {
alert('second argument is: ' + arguments[1]);
}
foo('hello','world'); // this is not an error
Javascript also allows functions to be called with fewer arguments than specified. Again, this is not considered an error. The unpassed arguments are simply given the value undefined:
function foo (arg1,arg2, arg3) {
alert('third argument is: ' + arg3);
}
foo('hello'); // this is not an error
That's all there is to it. When the function passed to map() is defined to accept one argument but map() calls it with three the remaining two arguments are essentially ignored.
On MDN's page for setTimer, there is a little shim / compatibility layer for setTimer that will let Internet Explorer accept additional arguments in the setTimer method that will be passed to the callback.
I pretty much understand all of the code below:
if (document.all && !window.setTimeout.isPolyfill) {
var __nativeST__ = window.setTimeout;
window.setTimeout = function (
vCallback,
nDelay /*,
argumentToPass1,
argumentToPass2, etc. */
) {
var aArgs = Array.prototype.slice.call(arguments, 2);
return __nativeST__(vCallback instanceof Function ? function () {
vCallback.apply(null, aArgs);
} : vCallback, nDelay);
};
window.setTimeout.isPolyfill = true;
}
Except for one line:
var aArgs = Array.prototype.slice.call(arguments, 2);
It references arguments, but I cannot see that name being referenced anywhere before this line. It is not on the Reserved words list either, so it does not seem to be magic in any way. In order for me to make any sense out of it, it must somehow refer to the arguments of the overridden setTimeout function, and then use slice() to get every argument after the first two.
The object arguments holds all the arguments that were passed to a function including those, that were not named in the function declaration.
Remember, that assuming the following function
function doStuf( param1 ) { /* do something */ }
it is also valid to make such a call
doStuff( 'stuff', 'morestuff', 2, 'evenmorestuff' );
In that case you could reference all parameters using the arguments object.
So in your particular code, the following row
var aArgs = Array.prototype.slice.call(arguments, 2);
copies all arguments passed to the shim-function, but the first two. The first two, however, were explicitly named and are referenced as such (vCallback and nDelay).
MDN documentation
arguments is indeed magic; it's an object very similar to an array that contains the arguments passed to the current function. This object has local scope inside all functions.
Since it is not exactly an array, the pattern Array.prototype.slice.call(arguments) is commonly used to extract a number of (or all) the argument values into a real array. In this specific case, aArgs ends up being an array that contains all arguments passed to the setTimeout replacement apart from the first two.
arguments is magic - it's the arguments that were passed to a function, in array form.
See arguments on MDN: "An Array-like object corresponding to the arguments passed to a function [...] available within all functions"
I want to store a pointer to a function and then call it later with some arguments, but I'm having trouble with how to pass the arguments to the function.
For example:
var MyObject = (function () {
return {
myMethod: function (a, b) {
return a + b;
}
};
}());
var method = MyObject.myMethod;
var args = [2, 5];
method(args);
So in the last line, which I know doesn't do what I want method(args), I want to essentially call MyObject.myMethod(2, 5), so my array attempt fails and creating an object also fails. How do I pass in the stored arguments to the stored method?
you can use apply which accepts an array of arguments as an argument
var myarray = [2,5,...] //your array of arguments
method.apply(this,myarray); //execute your method
and in the method you have, you can receive the arguments via the "hidden" "pseudo-array" arguments argument.
function method(){
var args = arguments; //is [2,5,...]
}
this has advantage over call since you can pre-build the argument array and don't have to enumerate the arguments in the call's ()
K. Scott Allen has a great old article on this kind of thing. Use call or apply as a way to affect the value of this inside the called function.
http://odetocode.com/blogs/scott/archive/2007/07/05/function-apply-and-function-call-in-javascript.aspx