// makeClass - By John Resig (MIT Licensed)
function makeClass(){
return function(args){
if ( this instanceof arguments.callee ) {
if ( typeof this.init == "function" )
this.init.apply( this, args.callee ? args : arguments );
} else
return new arguments.callee( arguments );
};
}
especially this line this.init.apply( this, args.callee ? args : arguments );
What’s the difference between args and arguments? Can args.callee ever be false?
You write that the existing answers don’t have enough detail, but even after reading your specific questions, I’m not completely sure exactly which aspects of the code are throwing you for a loop — it has a number of tricky parts — so I apologize in advance if this answer goes overboard with details about things you’ve already understood!
Since
makeClassis always meant to be called the same way, it’s a bit easier to reason about it if we remove one level of indirection. This:is equivalent to this:
Since we’re no longer dealing with an anonymous function, we no longer need the
arguments.calleenotation: it necessarily refers toMyClass, so we can replace all instances of it withMyClass, giving this:where
argsis an identifier forMyClass‘s first argument, andarguments, as always, is an array-like object containing all ofMyClass‘s arguments.The line you’re asking about is only reached if the “class” has a function named
initin its prototype (which will be the “constructor”), so let’s give it one:Once we’ve done that, consider this:
Inside the call to
MyClass,thiswill refer to the object being constructed, sothis instanceof MyClasswill be true. Andtypeof this.init == "function"will be true, because we madeMyClass.prototype.initbe a function. So we reach this line:Here
argsis equal to'value'(the first argument), so it’s a string, so it doesn’t have thecalleeproperty; soargs.calleeis undefined, which in a Boolean context means it’s false, soargs.callee ? args : argumentsis equivalent toarguments. Therefore, the above line is equivalent to this:which is equivalent to this:
(if you don’t already know how
applyworks, and how it differs fromcall, see https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/apply).Does that make sense so far?
The other case to consider is this:
Inside the call to
MyClass,thiswill refer to the global object (typicallywindow), sothis instanceof MyClasswill be false, so we reach this line:where
argumentsis an array-like object containing a single element:'value'. Note that this is not the same asnew MyClass('value').Terminological note: So the call to
MyClass('value')results in a second call toMyClass, this time withnew. I’m going to call the first call (withoutnew) the “outer call”, and the second call (withnew) the “inner call”. Hopefully that’s intuitive.Inside the inner call to
MyClass,argsnow refers to the outer call’sargumentsobject: instead ofargsbeing'value', it’s now an array-like object containing'value'. And instead ofargs.calleebeing undefined, it now refers toMyClass, soargs.callee ? args : argumentsis equivalent toargs. So the inner call toMyClassis callingthis.init.apply(this, args), which is equivalent tothis.init('value').So the test on
args.calleeis intended to distinguish an inner call (MyClass('value')→new MyClass(arguments)) from a normal direct call (new MyClass('value')). Ideally we could eliminate that test by replacing this line:with something hypothetical that looked like this:
but JavaScript doesn’t allow that notation (nor any equivalent notation).
You can see, by the way, that there are a few small problems with Resig’s code:
MyClass.prototype.init, and then we instantiate the “class” by writingvar myInstance3 = new MyClass();, thenargswill be undefined inside the call toMyClass, so the test onargs.calleewill raise an error. I think this is simply a bug on Resig’s part; at any rate, it’s easily fixed by testing onargs && args.calleeinstead.callee, then the test onargs.calleewill produce a false positive, and the wrong arguments will be passed into the constructor. This means that, for example, we cannot design the constructor to take anargumentsobject as its first argument. But this issue seems difficult to work around, and it’s probably not worth worrying about.