Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Understanding this JavaScript function overloading example

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

like image 218
davy Avatar asked Feb 15 '23 15:02

davy


1 Answers

So, 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 returns allow the result to be passed back through / in reverse.

like image 90
Jonathan Lonowski Avatar answered Feb 18 '23 10:02

Jonathan Lonowski