views:

2193

answers:

10

Given a function:

function x(arg) { return 30; }

You can call it two ways:

result = x(4);
result = new x(4);

The first returns 30, the second returns an object.

How can you detect which way the function was called inside the function itself?

Whatever your solution is, it must work with the following invocation as well:

var Z = new x(); 
Z.lolol = x; 
Z.lolol();

All the solutions currently think the Z.lolol() is calling it as a constructor.

+22  A: 

1) You can check this.constructor:

function x(y)
{
    if (this.constructor == x)
        alert('called with new');
    else
         alert('called as function');
}

2) Yes, the return value is just discarded when used in the new context

Greg
ah, nice. is this.constructor undefined in the 'else'?
Claudiu
No it's default object constructor because the context of 'this' in that case is the function x itself, as you can see in my redundant answer below which I wouldn't have posted if SO had indicated this was already answered *sigh*
annakata
@annakata: no worries, your answer is still valuabe. upvote from me in any case.
Claudiu
NOTE: The return value is not discarded; if the return value is an Object, then that object is returned instead of the 'this'.
Claudiu
this also doesn't work if something like "x.prototype = new Array()", and the x.prototype.constructor field isn't re-assigned.
Claudiu
This doesn't work. Behold: { var Z = new x(); Z.lolol = x; Z.lolol();}. It thinks the 2nd invocation is it being called as a constructor.
Claudiu
This fails if `window.constructor == x`. Use `instanceof` or `Object.getPrototypeOf`.
Eli Grey
+4  A: 

Two ways, essentially the same under the hood. You can test what the scope of this is or you can test what this.constructor is.

If you called a method as a constructor this will be a new instance of the class, if you call the method as a method this will be the methods' context object. Similarly the constructor of an object will be the method itself if called as new, and the system Object constructor otherwise. That's clear as mud, but this should help:

var a = {};

a.foo = function () 
{
  if(this==a) //'a' because the context of foo is the parent 'a'
  {
    //method call
  }
  else
  {
    //constructor call
  }
}

var bar = function () 
{
  if(this==window) //and 'window' is the default context here
  {
    //method call
  }
  else
  {
    //constructor call
  }
}

a.baz = function ()
{
  if(this.constructor==a.baz); //or whatever chain you need to reference this method
  {
    //constructor call
  }
  else
  {
    //method call
  }
}
annakata
typeof (this) will always be 'object' in your examples
Greg
bah, getting ahead of myself should just be testing 'this' directly - edited
annakata
note that this.constructor for a non-new call is DOMWindow in Chrome, and undefined in IE , so you can't rely on it.
Claudiu
but in either of those cases the salient fact is that this.constructor is *still not* a.baz which is what matters
annakata
yep, definitely - just wanted to point out that "and the system Object constructor otherwise" is not necessarily correct
Claudiu
+10  A: 

The benefit of the code below is that you don't need to specify the name of the function twice and it works for anonymous functions too.

function x() {
    if ( (this instanceof arguments.callee) ) {
      alert("called as constructor");
    } else {
      alert("called as function");
    }
}
some
This doesn't work. Behold: { var Z = new x(); Z.lolol = x; Z.lolol();}. It thinks the 2nd invocation is it being called as a constructor.
Claudiu
+1  A: 

In my testing for http://packagesinjavascript.wordpress.com/ I found the test if (this == window) to be working cross-browser in all cases, so that's the one I ended up using.

-Stijn

try it for the example i give. it won't work
Claudiu
A: 

Use this instanceof arguments.callee (optionally replacing arguments.callee with the function it's in, which improves performance) to check if something is called as a constructor. Do not use this.constructor as that can be easily changed.

Eli Grey
read the other answers before posting
Claudiu
Claudiu, I did, but none of them mentioned using `this isntanceof contructorName`.
Eli Grey
+6  A: 

I don't think what you want is possible. There simply isn't enough information available within the function to make a reliable inference.

Looking at the ECMAScript 3rd edition spec, the steps taken when new x() is called are essentially:

  • Create a new object
  • Assign its internal [[Prototype]] property to the prototype property of x
  • Call x as normal, passing it the new object as this
  • If the call to x returned a value, return it, otherwise return the new object

Nothing useful about how the function was called is made available to the executing code, so the only thing it's possible to test inside x is the this value, which is what all the answers here are doing. As you've observed, a new instance of* x when calling x as a constructor is indistinguishable from a pre-existing instance of x passed as this when calling x as a function, unless you assign a property to every new object created by x as it is constructed:

function x(y) {
    var isConstructor = false;
    if (this instanceof x // <- You could use arguments.callee instead of x here
            && !this.__previouslyConstructedByX) {
        isConstructor = true;
        this.__previouslyConstructedByX = true;
    }
    alert(isConstructor);
}

Obviously this is not ideal, since you now have an extra useless property on every object constructed by x that could be overwritten, but I think it's the best you can do.

(*) "instance of" is an inaccurate term but is close enough, and more concise than "object that has been created by calling x as a constructor"

Tim Down
why are you using this.__previouslyConstructedByX instead of var previouslyConstructedByX ?
PiPeep
@PiPeep: Only as a naming convention intended to suggest that the property isn't to be used for any other purpose. Some people use a convention that properties not intended as part of the public API for an object start with an underscore, while there are some non-standard properties of JavaScript objects in some browsers that start with double underscores and I was taking my cue from those ideas. I didn't think about it in great detail and could be persuaded that the two underscores is a bad idea.
Tim Down
I think this is the right answer. in the general case, you just can't do it.
Claudiu
I just stopped by and was pretty surprised to see that this answer only had one upvote. It is a great answer. Come on, people, don't be so stingy. (Needless to say, plus 1).
Charlie Flowers
A: 

Tim Down I think is correct. I think that once you get to the point where you think you need to be able to distinguish between the two calling modes, then you should not use the "this" keyword. this is unreliable, and it could be the global object, or it could be some completely different object. the fact is, that having a function with these different modes of activation, some of which work as you intended, others do something totally wild, is undesirable. I think maybe you're trying to figure this out because of that.

There is an idiomatic way to create a constructor function that behaves the same no matter how it's called. whether it's like Thing(), new Thing(), or foo.Thing(). It goes like this:

function Thing () {
   var that = Object.create(Thing.prototype);
   that.foo="bar";
   that.bar="baz";
   return that;
}

where Object.create is a new ecmascript 5 standard method which can be implemented in regular javascript like this:

if(!Object.create) {
    Object.create = function(Function){
        // WebReflection Revision
       return function(Object){
           Function.prototype = Object;
           return new Function;
    }}(function(){});
}

Object.create will take an object as a parameter, and return a new object with that passed in object as its prototype.

If however, you really are trying to make a function behave differently depending on how it's called, then you are a bad person and you shouldn't write javascript code.

Breton
That's an unfortunate choice of parameter names in the `Object.create` implementation: `Function` and `Object` both being native JavaScript objects makes it more confusing to read.
Tim Down
You're right, but I'll note that I didn't write it. It's simply the best implementation of that particular function I'm able to find. The final ES5 version actually takes some additional parameters, that this won't handle. I await the canonical backwards compatible implementations for ES5 functions. Alas I'm not quite clever enough to write them myself.
Breton
+2  A: 

Extending Gregs solution, this one works perfectly with the test cases you provided:

function x(y) {
    if( this.constructor == arguments.callee && !this._constructed ) {
        this._constructed = true;
        alert('called with new');
    } else {
        alert('called as function');
    }
}

EDIT: adding some test cases

x(4);             // OK, function
var X = new x(4); // OK, new

var Z = new x();  // OK, new
Z.lolol = x; 
Z.lolol();        // OK, function

var Y = x;
Y();              // OK, function
var y = new Y();  // OK, new
y.lolol = Y;
y.lolol();        // OK, function
frunsi
this works, unless code modified _constructed.
Claudiu
That's exactly the same as my suggested solution, except using the weaker check on the `constructor` property rather than using `instanceof`
Tim Down
@Tim: Well yes, you're right.
frunsi
A: 

From John Resig:

function makecls() {

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

}

var User = makecls();

User.prototype.init = function(first, last){

this.name = first + last;

};

var user = User("John", "Resig");

user.name

haijin
+1  A: 

Until I saw this thread I never considered that the constructor might be a property of an instance, but I think the following code covers that rare scenario.

// Store instances in a variable to compare against the current this
// Based on Tim Down's solution where instances are tracked
var Klass = (function () {
    // Store references to each instance in a "class"-level closure
    var instances = [];

    // The actual constructor function
    return function () {
        if (this instanceof Klass && instances.indexOf(this) === -1) {
            instances.push(this);
            console.log("constructor");
        } else {
            console.log("not constructor");
        }
    };
}());

var instance = new Klass();  // "constructor"
instance.klass = Klass;
instance.klass();            // "not constructor"

For most cases I'll probably just check instanceof.

Oh yeah. IE6 will need an implementation of indexOf added to Array.prototype.
ah nice. I like this answer best so far, since you can't tamper with it as much as if you tack on a property to the object
Claudiu