Simple “Class” Instantiation

javascript, oop

Solution

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 `makeClass` is 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:

var MyClass = makeClass();

is equivalent to this:

function MyClass(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 );
}

Since we're no longer dealing with an anonymous function, we no longer need the `arguments.callee` notation: it necessarily refers to `MyClass`, so we can replace all instances of it with `MyClass`, giving this:

function MyClass(args)
{
  if ( this instanceof MyClass )
  {
    if ( typeof this.init == "function" )
      this.init.apply( this, args.callee ? args : arguments );
  }
  else
    return new MyClass( arguments );
}

where `args` is an identifier for `MyClass`'s first argument, and `arguments`, as always, is an array-like object containing all of `MyClass`'s arguments.

The line you're asking about is only reached if the "class" has a function named `init` in its prototype (which will be the "constructor"), so let's give it one:

MyClass.prototype.init =
  function (prop)
  {
    this.prop = prop;
  };

Once we've done that, consider this:

var myInstance1 = new MyClass('value');

Inside the call to `MyClass`, `this` will refer to the object being constructed, so `this instanceof MyClass` will be true. And `typeof this.init == "function"` will be true, because we made `MyClass.prototype.init` be a function. So we reach this line:

this.init.apply( this, args.callee ? args : arguments );

Here `args` is equal to `'value'` (the first argument), so it's a string, so it doesn't have the `callee` property; so `args.callee` is undefined, which in a Boolean context means it's false, so `args.callee ? args : arguments` is equivalent to `arguments`. Therefore, the above line is equivalent to this:

this.init.apply(this, arguments);

which is equivalent to this:

this.init('value');

(if you don't already know how `apply` works, and how it differs from `call`, 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:

var myInstance2 = MyClass('value');

Inside the call to `MyClass`, `this` will refer to the global object (typically `window`), so `this instanceof MyClass` will be false, so we reach this line:

return new MyClass( arguments );

where `arguments` is an array-like object containing a single element: `'value'`. Note that this is not the same as `new MyClass('value')`.

Terminological note: So the call to `MyClass('value')` results in a second call to `MyClass`, this time with `new`. I'm going to call the first call (without `new`) the "outer call", and the second call (with `new`) the "inner call". Hopefully that's intuitive.

Inside the inner call to `MyClass`, `args` now refers to the outer call's `arguments` object: instead of `args` being `'value'`, it's now an array-like object containing `'value'`. And instead of `args.callee` being undefined, it now refers to `MyClass`, so `args.callee ? args : arguments` is equivalent to `args`. So the inner call to `MyClass` is calling `this.init.apply(this, args)`, which is equivalent to `this.init('value')`.

So the test on `args.callee` is 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:

return new MyClass( arguments );

with something hypothetical that looked like this:

return new MyClass.apply( itself, arguments );

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:

- If we define a constructor `MyClass.prototype.init`, and then we instantiate the "class" by writing `var myInstance3 = new MyClass();`, then `args` will be undefined inside the call to `MyClass`, so the test on `args.callee` will raise an error. I think this is simply a bug on Resig's part; at any rate, it's easily fixed by testing on `args && args.callee` instead.

- If our constructor's first argument happens to actually have a property named `callee`, then the test on `args.callee` will 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 an `arguments` object as its first argument. But this issue seems difficult to work around, and it's probably not worth worrying about.

Problem

From John Resig blog: ``` // 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`?

Original source