I'm currently studying Secrets of the JavaScript Ninja by John Resig and I'm hoping someone can help me further understand one of the examples.
It is a function that allows method overloading on an object, each overload has it's own definition and behaviour. He blogged about it here.
The code looks like this:
function addMethod(object, name, fn) {
var old = object[name];
object[name] = function(){
if (fn.length == arguments.length)
return fn.apply(this, arguments)
else if (typeof old == 'function')
return old.apply(this, arguments);
};
And used like this:
addMethod(obj,'funcName',function(){});
addMethod(obj,'funcName',function(a){});
addMethod(obj,'funcName',function(a,b){});
I think I understand most of how this works but you can get a better explanation than I can give from the blog post above).
However, it accesses the value of old
and fn
using closures, which I'm still studying.
EDIT - added jsFiddle
below.
When trying to understand it, I realised that the line return fn.apply(this, arguments)
could be simply return fn()
with what seems to be the same result. See an example in this jsFiddle.
So, Why is it using the apply
syntax if not required?
I have tried playing with the example in jsFiddle without apply and it always seems to wo
Also, what exactly is happening when we return those functions, especially in the case of:
return old.apply(this, arguments);
I really want to get a solid understanding of not just how to use this method but why it works so any insight would be greatly appreciated.
Thanks
I'm currently studying Secrets of the JavaScript Ninja by John Resig and I'm hoping someone can help me further understand one of the examples.
It is a function that allows method overloading on an object, each overload has it's own definition and behaviour. He blogged about it here.
The code looks like this:
function addMethod(object, name, fn) {
var old = object[name];
object[name] = function(){
if (fn.length == arguments.length)
return fn.apply(this, arguments)
else if (typeof old == 'function')
return old.apply(this, arguments);
};
And used like this:
addMethod(obj,'funcName',function(){});
addMethod(obj,'funcName',function(a){});
addMethod(obj,'funcName',function(a,b){});
I think I understand most of how this works but you can get a better explanation than I can give from the blog post above).
However, it accesses the value of old
and fn
using closures, which I'm still studying.
EDIT - added jsFiddle
below.
When trying to understand it, I realised that the line return fn.apply(this, arguments)
could be simply return fn()
with what seems to be the same result. See an example in this jsFiddle.
So, Why is it using the apply
syntax if not required?
I have tried playing with the example in jsFiddle without apply and it always seems to wo
Also, what exactly is happening when we return those functions, especially in the case of:
return old.apply(this, arguments);
I really want to get a solid understanding of not just how to use this method but why it works so any insight would be greatly appreciated.
Thanks
Share Improve this question edited Aug 8, 2013 at 11:08 davy asked Aug 8, 2013 at 9:12 davydavy 4,58210 gold badges50 silver badges73 bronze badges 1- Thanks everyone for all the answers. I will study them all and accept the best answer - although, that looks as if it may be difficult looking at the high quality of the answers :) – davy Commented Aug 8, 2013 at 11:04
6 Answers
Reset to default 3So, Why is it using the
apply
syntax if not required?
It is actually required for the usage.
this
and arguments
are different for every function
and are set when they're called. By using fn()
, fn
will be called with an empty arguments
collection or no value passed for this
.
.apply(this, arguments)
calls fn
or old
and passes along the values for both from the current function
.
var obj = {};
addMethod(obj, 'funcName', function (a, b) {
console.log(this === obj);
console.log(a, b);
console.log(arguments[0], arguments[1]);
});
obj.funcName(2, 3);
// true
// 2, 3
// 2, 3
Also, what exactly is happening when we return those functions, especially in the case of:
return old.apply(this, arguments);
Well, the purpose of addMethod
is to create a chain of functions where each knows about and can call the old
function created before it.
For the example from the book, the chain is built as:
// after: addMethod(obj, 'funcName', function(){});
obj.funcName = function(){...} ──> function(){}
// after: addMethod(obj, 'funcName', function(a){});
obj.funcName = function(){...} ──────> function(a){}
└── function(){...} ──> function(){}
// after: addMethod(obj, 'funcName', function(a,b){});
obj.funcName = function(){...} ──────────> function(a,b){}
└── function(){...} ──────> function(a){}
└── function(){...} ──> function(){}
Legend:
`└──` represents an `old` reference
`──>` represents a `fn` reference
Each function(){...}
is a unique instance created by reevaluating the same expression in a different scope/closure:
function(){
if (fn.length == arguments.length)
return fn.apply(this, arguments)
else if (typeof old == 'function')
return old.apply(this, arguments);
}
Each .apply()
then follows an "arm" or "arrow" to either an old
or fn
and the return
s allow the result to be passed back through / in reverse.
Here is a breakdown of the code.
function addMethod(object, name, fn) {
//get the old function from the object
var old = object[name];
//assign a new function to the property
object[name] = function(){
//See if the method signatures match, if they do execute the new method
if (fn.length == arguments.length)
/*
Call the function provided using apply, the first argument "this" is the object
it sets the context for this in the function we provide, second argument is
the arguments provided to the function. We must return the result of the
function.
*/
return fn.apply(this, arguments)
//If the old property on the object is a function and the new functions signature
//did not match call the old function assigned to the property.
else if (typeof old == 'function')
// Same as before calling with apply, setting context and returning result
return old.apply(this, arguments);
};
It is important to understand how apply
works. It sets the context for this
within a function. For example:
var myFunction = function(){
alert(this.msg); //this will be set by what apply passes in as the first arg
};
var obj1 = {msg: "Hello"};
var obj2 = {msg: "World"};
myFunction.apply(obj1);
myFunction.apply(obj2);
Example http://jsfiddle/NCaKX/
I think you're missing the point of .apply
I wont try to explain it :P you can find many good explanations, such as:
- What is the difference between call and apply?
- Why invoke "apply" instead of calling function directly?
tl;dr
.apply
allows you set the this
context. .apply
allows you to pass arguments as an array, allowing for a variable number of arguments.
In Javascript the this
variable is not set in the moment of declaration of the function, but in the moment of execution, depending on which object was used to access the function reference.
You need to use apply
not only because of the aforementioned dynamic bind of this
, but also because you don't know how many arguments are there before hand; so apply
receives a list of arguments and passes each element of the list as an individual argument to the function.
Using .apply
the context can be specified
window.name = "window";
var object = {
name:"object"
};
function showName(){
alert(this.name);
}
showName(); // shows window
showName.apply(object); // shows object
more here : https://developer.mozilla/en-US/docs/Web/JavaScript/Reference/Operators/this
fn.apply(this, arguments)
calls the fn
with this
as the current this
and arguments
as parameters.
fn()
calls the fn
with this
equal to window
or undefined
(depending if you are in "non strict mode" or in "strict mode") but without parameters.
In this Mozilla page there is a good discussion of this
(always useful)