I'm reading through Javascript Garden, and I'm trying to wrap my head around the following example:
Passing Arguments
The following is the recommended way of passing arguments from one function to another.
function foo() {
bar.apply(null, arguments);
}
function bar(a, b, c) {
// do stuff here
}
Another trick is to use both call and apply together to create fast, unbound wrappers.
function Foo() {}
Foo.prototype.method = function(a, b, c) {
console.log(this, a, b, c);
};
// Create an unbound version of "method"
// It takes the parameters: this, arg1, arg2...argN
Foo.method = function() {
// Result: Foo.prototype.method.call(this, arg1, arg2... argN)
Function.call.apply(Foo.prototype.method, arguments);
};
I'm trying to figure out two things:
1) What exactly is an "unbound wrapper"?
2) How does the chaining from .call to .apply work and/or make the code faster?
"1) What exactly is an "unbound wrapper"?"
Unbound wrapper is just a function that calls another function by passing on the desired this value and arguments.
"2) How does the chaining from .call to .apply work and/or make the code faster?"
The .call.apply() is faster than having to do a .slice() on the arguments to separate the this from the actual args.
Otherwise it would need to do this, which is slower:
Foo.method = function(ths) {
Foo.prototype.method.apply(ths, Array.prototype.slice.call(arguments, 1));
};
What exactly is an "unbound wrapper"?
A function that is not to be called on an instance, but with an instance as its argument. It's not bound to the prototype / doesn't need to be bound to an instance. Example:
var x = new Foo;
// instead of
x.method(1, 2, 3);
// you now call
Foo.method(x, 1, 2, 3);
The benefit of this is that you can pass the function around without caring about its this context.
How does the chaining from .call to .apply work and/or make the code faster?
It doesn't really make anything "faster". It's not even compared to any "slower" solution.
For how it works, please check the duplicate question What's the meaning to chain call and apply together?.
I think the code should be like this:
function Foo() {}
Foo.prototype.method = function(a, b, c) {
console.log(this, a, b, c);
};
Foo.method = function() {
//Notice this line:
Function.apply.call(Foo.prototype.method, this, arguments);
};
then
Foo.method(1,2,3) => function Foo() {} 1 2 3
Other examples:
Function.apply.call(Array,this,[1,2]) => [1, 2]
Function.call.apply(Array,this,[1,2]) => [window]
Function.call.call(Array,this,[1,2]) => [[1, 2]]
Related
Why is it necessary to write a and b after mls in the setTimeout method here? Aren’t a and b already defined in the arrow function?
function f(a, b) {
alert(a + b);
}
// shows 3 after 1 second
Function.prototype.defer = function(mls) {
return (a, b) => setTimeout(this, mls, a, b);
}
f.defer(1000)(1, 2);
Because f.defer(1000) returns the arrow function which in turn gets called with (1,2) as parameter. This arrow function is calling setTimeout which is supposed to call the same function f with these parameters 1 and 2. And the definition of seTimeout states that you can pass parameter to the callback function after first 2 parameters. Please refer below definition and link for the reference.
window.setTimeout(function[, delay, param1, param2, ...]);
https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout
The arrow function is defining a function that then invokes setTimeout(this, mls, a, b). It could also be written as:
Function.prototype.defer = function(mls){
var that = this;
return function(a,b) {
setTimeout(that, mls, a, b);
}
}
This is important to note because you're actually returning a function from defer. So when you call f.defer(1000), the return value is a function - your arrow function, to be specific - and then you invoke that function with the values 1 and 2 when you call f.defer(1000)(1, 2);.
If you don't, your function will not be called with any parameters. These parameters are passed into the originally invoked function.
setTimeout takes a variable number of arguments. The first one is the function (this, or f in this case), the second one is the delay. Any argument after that is passed to the to-be-called function upon calling it.
Your function f expects two arguments; if they aren’t passed, they are undefined. There are indeed an “a and b already defined in the arrow function”, but they are scoped to the arrow function. If a function calls another function, its arguments aren’t automatically transfered.
Internally, setTimeout is doing something like outerWorking in this example:
const inner = (a, b) => a + b,
outerWorking = (a, b) => inner(a, b),
outerNotWorking = (a, b) => inner();
console.log(outerWorking(2, 3)); // 5
console.log(outerNotWorking(2, 3)); // NaN
So f.defer(1000) returns a new function with mls === 1000 and this === f. It accepts a and b which are yet to be defined with the next function call (calling the arrow functions, with the argument list (1, 2)). Only when you pass a, b at f.defer(1000)(1, 2), then setTimeout(this, mls,a,b) will be called with a === 1, b === 2 and the other two bindings above (this and delay).
Alternatively, this could be written as
Function.prototype.defer = function(mls, a, b){
setTimeout(this, mls, a, b);
};
f.defer(1000, 1, 2)
But there’s an advantage in the original, curried approach, as you can simply detach the “defered function” from the final call:
const add1sDelay = f.defer(1000);
add1sDelay(1, 2);
I would like to create an alias for every function so that instead of calling:
f();
I can just call:
f.theSame();
... and have the same effect.
There's no reason for that, just curiosity.
By using Function.prototype this appears to be easy:
Function.prototype.theSame = function() {
return this(arguments);
};
var foo = function foo() {return 1;};
console.log(foo.theSame()); // prints 1 - OK
The problem appears when I realize that the above doesn't work on "member functions" because the this is hidden:
var a = {x: 3, f: function() {return this.x;}};
console.log(a.f()); // prints 3 - OK
// throws TypeError: object is not a function
console.log(a.f.theSame.apply(a, []));
console.log(a.f.theSame()); // prints undefined
So one has to do something like:
Function.prototype.theSame2 = function() {
var fn = this;
return function() {
return fn.apply(this, arguments);
};
};
Which, however, requires the ()() awkward construct:
console.log(a.f.theSame2()()); // prints 3 - OK
Is there a way to implement theSame such that it works for "member functions" without using the ()() construct?
No. If you call the function with a.f.theSame() then you are calling f as property of itself (i.e. this refers to the function itself). There is no "connection" from the function to the containing object, since a function is just a value and could be a property of many objects.
In order to make your example work you have to perform an additional initialization step that ties the function to a, e.g.
a.f = a.f.bind(a);
There is no way around this.
In C++, you have a bind function that allows you to bind parameters to functions. When you call the function, it will be called with those parameters. Similarly, MySQL has an ability to bind parameters to queries where it will substitute question marks with the variable. In Javascript and jQuery, the bind function has a confusingly different meaning. It makes the passed parameter the this variable, which is not at all what I want. Here's an example of what I want to achieve:
var outerVariable;
var callbacks = [
some_object.event,
some_object.differentEvent.bind(outerVariable)
];
// func() will automatically have outerVariable in scope
$.map(callbacks, function(func) { func(); });
I don't think there is an easy way to use bind without playing around with this, if you want to bind arguments only you may be better writing a wrapper
function bindArgs(fn) {
var slice = Array.prototype.slice,
bound_args = slice.call(arguments, 1);
return function () {
var args = slice.call(arguments, 0);
return fn.apply(this, bound_args.concat(args));
}
}
Now
function foo(a, b) {
console.log(this, a, b);
}
var bar = bindArgs(foo, 'bar');
var obj = {baz: bar};
obj.baz('baz'); // logs obj, "bar", "baz"
Do this instead:
var callbacks = [
some_object.event,
some_object.differentEvent.bind(some_object, outerVariable)
];
The only difference between the C++ and JavaScript bind is that the JavaScript bind takes one extra argument: thisArg, which will bind the keyword this for the bound function to whatever you pass in. If you do not want to re-bind the this-argument, then pass null as the first parameter, and the rest should look familiar to the C++ variant you speak of:
function add(a, b) {
return a + b
}
var three = add(1, 2)
var makeThree = add.bind(null, 1, 2)
three = makeThree()
I have a lot of my code inside an object literal and there are a couple functions where I want to be able to pass the functions arguments for the parameters but I can't figure out how to do that.
Here is an example of my object..
var test = {
button: $('.button'),
init: function() {
test.button.on('click', this.doSomething);
},
doSomething: function(event, param1, param2) {
console.log(param1);
console.log(param2);
}
};
So when the button is clicked and it calls the function doSomething I want to pass in arguments for param1 and param2.
Something similar to this maybe, but this does not work.
test.button.on('click', this.doSomething('click', 'arg1', 'arg2'));
Any ideas, or am I going about this the wrong way?
The jQuery.proxy() function seems to be exactly what you need. Have a good read at the docs and see if they make sense to you. For your specific example,
var test = {
button: $('.button'),
init: function() {
test.button.on('click', $.proxy(this.doSomething, null, 'arg1', 'arg2');
},
doSomething: function(param1, param2, event) {
console.log(param1);
console.log(param2);
}
};
In this example, the parameters to $.proxy are:
this.doSomething - The the function to call
null - The context in which the function will be called. By supplying null, we are saying to use its 'normal' context.
arg1 - The value of param1 formal parameter of the called function
arg2 - The value of param2 formal parameter of the called function
Since the click callback supplied the final parameter (event), that is already provided and doesn't need to be additionally or explicitly declared. The jQuery.proxy() when passed additional parameters passes those at the front of the formal parameter list and any remaining parameters implicitly supplied are passed at the end. So if we a function that looks like:
var f = function(a, b, c) {
console.log(a, b, c);
};
and invoke it through a proxy:
var p = $.proxy(f, null, 2, 3);
p(1);
The value of a, b and c that are logged will be 2,3,1.
This question is also extremely close to this one.
How can I pass arguments to event handlers in jQuery?
I'm reading Javascript Web Applications, from O'Reilly. At various points in the book, the author uses something along the following:
instance.init.apply(instance, arguments);
Does this make any sense? Isn't this exactly the same as:
instance.init(arguments);
.call() and .apply() are used to manually set the execution context of a function. Why should I use them when I'm intending to use the original execution context anyway?
The point is that arguments is an array-like object. Doing ...
instance.init(arguments);
... passes one argument, which is an array-like object containing certain arguments. On the other hand, doing ...
instance.init.apply(instance, arguments);
... will pass the array-like object as separate arguments. It is true that setting instance is kind of useless because you already wrote it, but if using .apply you simply need to set the this value as well.
A quick example of the difference:
function log(a, b, c) {
console.log(a, b, c);
}
function log2() {
log.apply(null, arguments); // `this` value is not meaningful here,
// it's about `arguments`
}
function log3() {
log(arguments);
}
log(1, 2, 3); // logs: 1, 2, 3
log2(1, 2, 3); // logs: 1, 2, 3
log3(1, 2, 3); // logs: <Arguments>, undefined, undefined
// where <Arguments> contains the values 1, 2, 3
Using apply() in that example insures that 'this' === instance, instead of DOMWindow if instance.init() is executed from another function/expression.
var x = function(){ debugger; },
y = function(){ x.apply(x, arguments); },
z = function() { x(arguments); };
y("abc", true, []); // this === x
z("abc", true, []); // this === DOMWindow
It's simply specifying context.