How can a constructor be made so that calling it with "new" is optional?

P

Passiday

Hello,

Reading the JQuery API, I've paused on this page:
http://api.jquery.com/category/events/event-object/

It says there "The new operator is optional.", and shows two examples:

//Create a new jQuery.Event object without the "new" operator.
var e = jQuery.Event("click");

// Create a new jQuery.Event object with the "new" operator
(optional).
var e = new jQuery.Event("click");

Now, how this is achieved? If, in the first line of constructor
function, I can detect that it is called without "new" operator, I can
call it internally and then return the result. So, apparently the
trick is in this detection. Is it done by testing (typeof this ==
"undefined")? I am wondering if there are contexts where this can be
fooled: one is closure and other is when you call the construction
function's call or apply method. The first problem is fully controlled
by the developer of class, and the other problem can be controlled by
kindly asking users not to do like that.

Is this how it is done?
 
P

Passiday

Read the source:

| jQuery.Event = function( src ) {
|     // Allow instantiation without the 'new' keyword
|     if ( !this.preventDefault ) {
|         return new jQuery.Event( src );
|     }

If the this keyword references an object with a preventDefault
property that doesn't type convert to false (i.e. is falsey), it
assumes that the function has been called with the new operator.
Otherwise, it assumes it hasn't so calls itself with the new operator
and same argument.

Thanks, Rob, for your clarification. I had only minified version of
the class, so it was daunting to study the code. But I've downloaded
the source version now.

Ok, so the trick is to check for some hidden property, what is
registered in the class prototype object, and what is not found in the
window object (as that is the default context to what this would
refer, if the function is called without new).

Passiday
 
R

RobG

Thanks, Rob, for your clarification. I had only minified version of
the class, so it was daunting to study the code. But I've downloaded
the source version now.

Ok, so the trick is to check for some hidden property, what is
registered in the class prototype object,

There are no classes in javascript, and there can be more than one
prototype on a prototype chain.
and what is not found in the
window object (as that is the default context to what this would
refer, if the function is called without new).

No. If called as jQuery.Event(...) (i.e. without new) then this within
the function will refer to the jQuery object. And a function's this
keyword doesn't represent the "context", since it can be set to
anything by the call.
 
S

Scott Sauyet

"The new operator is optional."
[ ... ]
Now, how this is achieved?

This is a method I've seen used fairly often:

var Something = function (params) {
if (this.constructor !== Something) {
return new Something(params);
}
// this.prop1 = whatever ...
}

My main concern with this is that it might well not play well with the
various inheritance schemes some people layer onto JS. (I don't know
this for sure, but it's certainly easy enough to envision.) Are there
other concerns over this technique?

-- Scott
 
P

Passiday

If called as jQuery.Event(...) (i.e. without new) then this within
the function will refer to the jQuery object.

Oh, this totally has escaped my consciousness that when the function
is called the simple way (no "new", no .apply or .call), while in the
function body, "this" is set up to refer to the container object at
the moment, when the function call is made:

Container1 = {}
Container1.message = "Message from Container1";
Container2 = {}
Container2.message = "Message from Container2";

function alerter(s) {
alert(this.message);
}

Container1.fn = alerter;
Container2.fn = alerter;

alert((Container1.fn === Container2.fn) ? "Functions are the same" :
"Functions are different");
Container1.fn(); // Outputs "Message from Container1";
Container2.fn(); // Outputs "Message from Container2";

This is a method I've seen used fairly often:

var Something = function (params) {
if (this.constructor !== Something) {
return new Something(params);
}
// this.prop1 = whatever ...
}

My main concern with this is that it might well not play well with the
various inheritance schemes some people layer onto JS. (I don't know
this for sure, but it's certainly easy enough to envision.) Are there
other concerns over this technique?

Scott, from the example above you can see that there is very high
probability that this refers to a constructed object. The (inevitably
duct-typey) check must be using check for more proprietary property of
the object being constructed.

-- Passiday
 
W

Wesley Platt

Thanks, Rob, for your clarification. I had only minified version of
the class, so it was daunting to study the code. But I've downloaded
the source version now.

Ok, so the trick is to check for some hidden property, what is
registered in the class prototype object, and what is not found in the
window object (as that is the default context to what this would
refer, if the function is called without new).

Passiday

Couldn't you just use instanceof. For example:

jQuery.Event = function( src ) {
// Allow instantiation without the 'new' keyword
if ( !(this instanceof jQuery.Event) ) { //if ( !
this.preventDefault ) {
return new jQuery.Event( src );
}

Or would that not work because of the anonymous function?
 
S

Scott Sauyet

Passiday said:
Scott Sauyet wrote:

Scott, from the example above you can see that there is very high
probability that this refers to a constructed object. The (inevitably
duct-typey) check must be using check for more proprietary property of
the object being constructed.

I don't understand the objection, or even if you're raising an
objection. The original question, I thought, was about how to make
the "new" operator optional for a constructor function. The main
reason to do this, IMHO, is to prevent several types of errors that
might occur when that "new" is dropped:

var Person1 = function(first, last) {
this.firstName = first;
this.lastName = last;
}
Person1.prototype.greet = function() {
alert("Hello from " + this.firstName + " " + this.lastName);
}

var p1a = new Person1("John", "McCarthy");
var p1b = Person1("Grace", "Hopper");
alert(window.firstName) // "Grace"
p1a.greet(); // "Hello from John McCarthy"
p1b.greet(); // error, p1b is undefined

Note that when "new" is forgotten for p1b, there are several errors.
First of all, the variable ends up undefined. Second, the properties
set during what should be object construction are actually set in
whatever context you happen to be running. In this case, the window
object now has "firstName" and "lastName" properties.

The technique I mentioned avoids that:

var Person2 = function(first, last) {
if (this.constructor != Person2) {
return new Person2(first, last);
}
this.firstName = first;
this.lastName = last;
}
Person2.prototype.greet = function() {
alert("Hello from " + this.firstName + " " + this.lastName);
}

var p2a = new Person2("John", "McCarthy");
var p2b = Person2("Grace", "Hopper");
alert(window.firstName) // undefined
p2a.greet(); // "Hello from John McCarthy"
p2b.greet(); // "Hello from Grace Hopper"

Even though p2b is created without "new", it still returns a Person2
object.

If you're question is more about the details of jQuery's
implementation, "Use the source, Luke." Yes, their code is often more
obfuscated than necessary, it shouldn't take long to see. RobG
pointed to part of the implementation:

| jQuery.Event = function( src ) {
| // Allow instantiation without the 'new' keyword
| if ( !this.preventDefault ) {
| return new jQuery.Event( src );
| }

and the other key piece is

| jQuery.Event.prototype = {
| preventDefault: function() {
| this.isDefaultPrevented = returnTrue;
| // ...
| }, // ...
| }

I'm not sure why they choose to do it this way, as it seems more
fragile than what I suggested above. The following shows what would
happen if something added "preventDefault" to the jQuery object:

var evt1 = new jQuery.Event("Type 1")
alert(evt1.type) // "Type 1"

var evt2 = jQuery.Event("Type 2")
alert(evt2.type) // "Type 2"

jQuery.preventDefault = true;
var evt3 = jQuery.Event("Type 3")
alert(jQuery.type); // "Type 3"
alert(evt3.type) // error: evt3 is undefined

I still wonder if there is some hidden flaw in the technique I
mentioned. Anyone have a reason not to use it?

-- Scott
 
M

Michael Haufe (\TNO\)

"The new operator is optional."
[ ... ]
Now, how this is achieved?

This is a method I've seen used fairly often:

    var Something = function (params) {
      if (this.constructor !== Something) {
        return new Something(params);
      }
      // this.prop1 = whatever ...
    }

My main concern with this is that it might well not play well with the
various inheritance schemes some people layer onto JS.  (I don't know
this for sure, but it's certainly easy enough to envision.)  Are there
other concerns over this technique?

constructor is often overwritten by sub-"classing"

probably better to use if(!(this instanceof Something))
 
S

Scott Sauyet

Michael Haufe (TNO) said:
constructor is often overwritten by sub-"classing"

probably better to use if(!(this instanceof Something))

Absolutely.

-- Scott
 
P

Passiday

I don't understand the objection, or even if you're raising an
objection.

Well, I objected to my illusion, I thought your suggestion is

var Something = function (params) {
if (!this.constructor) {
return new Something(params);
}
}

of course, your code (this.constructor !== Something) is not what I
was objecting to.

Ok, so, the does the Oscar really goe to if(!(this instanceof
Something)) ?
This article objects: http://perfectionkills.com/instanceof-considered-harmful-or-how-to-write-a-robust-isarray
The reason - instanceof breaks in multi-window environment.
Apparently, this is the reason why JQuery opts for the sneaky duct-
typing.

-- Passiday
 
S

Scott Sauyet

Passiday said:
Ok, so, the does the Oscar really goe to if(!(this instanceof
Something)) ?

I think so. I'm not seeing any flaws in it right now.

kangax is very bright, and that article is excellent, but it's
irrelevant here. We don't care about matching, only mismatching.

The reason - instanceof breaks in multi-window environment.

Yes, but that will never affect us.

Apparently, this is the reason why JQuery opts for the sneaky duct-
typing.

"Duck-typing" is the "Duct-tape" of loosely-typed development!

(It is "duck" here, as in, "If it walks like a duck and quacks like a
duck, it must be a duck.")

-- Scott
 
M

Michael Haufe (\TNO\)

Well, I objected to my illusion, I thought your suggestion is

var Something = function (params) {
  if (!this.constructor) {
    return new Something(params);
  }

}

of course, your code (this.constructor !== Something) is not what I
was objecting to.

Ok, so, the does the Oscar really goe to if(!(this instanceof
Something)) ?
This article objects:http://perfectionkills.com/instanceof-considered-harmful-or-how-to-wr...
The reason - instanceof breaks in multi-window environment.
Apparently, this is the reason why JQuery opts for the sneaky duct-
typing.

In such cases I've always subscribed to the philosophy of
serialization. Personally I've never come across a serious or
practical use case in the past 10 years for cross-frame object sharing
but YMMV.
 
A

Andrea Giammarchi

The reason - instanceof breaks in multi-window environment.

you are passing through a constructor, you are not using instanceof
outside the constructor ... it's about knowing if you called

top.MyConstructor()
or
new top.MyConstructor()

for this case, to be sure that top.MyConstructor returns the object
with __proto__ == top.MyConstructor.prototype, which is the only thing
you want in this case, no matter which frame is calling it, the easy
solution is this one:

function MyConstructor(a, b, c) {
if (!(this instanceof MyConstructor))
return new MyConstructor(a, b, c)
;
// rest of the initialization logic here
}

If you have no arguments you can simplify via:

function MyConstructor() {
return this instanceof MyConstructor || new MyConstructor;
}

since returning the value true won't change the "new" behavior.

typeof(new function(){return true}) == "object"; // true

Last not efficient way, unless you always use constructors without new
keyword (Pythonic), and still it will mean two function calls per
instance rather than 1, is to create your class and expose something
else

var MyConstructor = (function(){
function _MyConstructor(a, b, c) {
return new MyConstructor(a, b, c);
}
function MyConstructor(a, b, c) {
// initialization here ...
}
// define MyConstructor.prototype

_MyConstructor.prototype = MyConstructor.prototype;
return _MyConstructor;
}());

(new MyConstructor) instanceof MyConstructor; // true

in latter case you will always create a new instanceof MyConstructor,
regardless usage of "new" in the outer scope

Hope these examples help

Best Regards,
Andrea Giammarchi
 
P

Passiday

var MyConstructor = (function(){
    function _MyConstructor(a, b, c) {
        return new MyConstructor(a, b, c);
    }
    function MyConstructor(a, b, c) {
        // initialization here ...
    }
    // define MyConstructor.prototype

    _MyConstructor.prototype = MyConstructor.prototype;
    return _MyConstructor;

}());

(new MyConstructor) instanceof MyConstructor; // true

in latter case you will always create a new instanceof MyConstructor,
regardless usage of "new" in the outer scope

Hi Andrea,

That's pretty clever. I actually did not know that using return in the
constructor body the value is passed even when new is used while
calling. This seems like pretty smooth way how to handle the original
topic question - no need for duck-tape (thanks, Scott :) solutions
whatsoever. The related question of checking the object type is just
not relevant when using this approach.

Passiday
 
R

RobG

you are passing through a constructor, you are not using instanceof
outside the constructor ... it's about knowing if you called

top.MyConstructor()
or
new top.MyConstructor()

for this case, to be sure that top.MyConstructor returns the object
with __proto__ == top.MyConstructor.prototype, which is the only thing
you want in this case, no matter which frame is calling it, the easy
solution is this one:

function MyConstructor(a, b, c) {
    if (!(this instanceof MyConstructor))
        return new MyConstructor(a, b, c)
    ;
    // rest of the initialization logic here
}

So going back to the original question, a better version would be:

jQuery.Event = function (src) {

// if not called as new jQuery.Event(...)
if (!(this.instanceof jQuery.Event)) {
return new jQuery.Event(src);
}

// continue as called with new...
}

If you have no arguments you can simplify via:

function MyConstructor() {
    return this instanceof MyConstructor || new MyConstructor;
}

I think that's much clearer as:

return (this instanceof MyConstructor)? this : new MyConstructor();

since returning the value true won't change the "new" behavior.

typeof(new function(){return true}) == "object"; // true

I don't see how that's relevant to this thread.

But since it's here, is there any point to it at all? If calling the
anonymous function as a constructor returns any non-object value, then
the new object referenced by its this keyword will be returned. You
can replace true with any primitive:

typeof(new function(){return 'foo'}) == "object"; // true

typeof(new function(){return 42}) == "object"; // true

Calling:

var x = new function(){<return any non-object>};

is essentially the same as:

var x = {};

except that in the first case, there is an extra, inaccessible object
referenced by x[[prototype]] (ignoring the deprecated __proto__
property).

Last not efficient way, unless you always use constructors without new
keyword (Pythonic), and still it will mean two function calls per
instance rather than 1, is to create your class and expose something
else

var MyConstructor = (function(){
    function _MyConstructor(a, b, c) {
        return new MyConstructor(a, b, c);
    }
    function MyConstructor(a, b, c) {
        // initialization here ...
    }
    // define MyConstructor.prototype

    _MyConstructor.prototype = MyConstructor.prototype;

That seems to be redundant, since no matter how _MyConstructor is
called, it will always call:

new MyConstructor(...)

and since that always returns an object, that is the object that will
be returned to the call. Of course removing the assignment breaks the
instanceof operator (since the returned object isn't really an
instance of the "outer" MyConstructor but of the "inner"
MyConstructor) but has the benefit (for some) of making the prototype
inaccessible.

    return _MyConstructor;

}());

(new MyConstructor) instanceof MyConstructor; // true

More interesting (to me) is that if the prototype assignment is left
in tact, then:

MyConstructor() instance of MyConstructor; // true

but if it's removed:

MyConstructor() instance of MyConstructor; // false
 
S

Scott Sauyet

RobG said:
I think that's much clearer as:

  return (this instanceof MyConstructor)? this : new MyConstructor();

Yes, that's clearer, but both could easily be broken if there is any
code in the constructor before the return statement, at least any code
that uses `this`.

var MyConstructor = function() {
this.init = 100; // set on the wrong object!
return (this instanceof MyConstructor) ? this : new
MyConstructor();
}

This might well go unnoticed for a long time, so long as everyone uses
`new`. That's why for the most part, the `if`-construct is safer.

var MyConstructor = function(params) {
if (!(this instanceof MyConstructor)) {
return new MyConstructor(params);
}
// now we know `this` is set correctly
}

-- Scott
 
R

RobG

Yes, that's clearer, but both could easily be broken if there is any
code in the constructor before the return statement, at least any code
that uses `this`.

    var MyConstructor = function() {
      this.init = 100; // set on the wrong object!
      return (this instanceof MyConstructor) ? this : new
MyConstructor();
    }

This might well go unnoticed for a long time, so long as everyone uses
`new`.  That's why for the most part, the `if`-construct is safer.

    var MyConstructor = function(params) {
      if (!(this instanceof MyConstructor)) {
        return new MyConstructor(params);
      }
      // now we know `this` is set correctly
    }

Yes. Now who is going to contribute it to jQuery? :)
 
S

Scott Sauyet

RobG said:
On Apr 11, 10:32 pm, Scott Sauyet <[email protected]> wrote:

(regarding this format:)
| jQuery.Event = function (src) {
| if (!(this instanceof jQuery.Event)) {
| return new jQuery.Event(src);
| }
| // continue as called with new...
| }
Yes. Now who is going to contribute it to jQuery? :)

I'm not sure if that emoticon is to suggest that no one here would
bother. I would. I did. I brought it up on jQuery's developer's
forum here:

http://forum.jquery.com/topic/why-does-jquery-event-use-preventdefault-as-an-instance-test

and got into a debate with one person who thought this suggestion was
an attempt to prove I'm smart by breaking the code :) and worried
about the 11 additional source-code characters. Another member (one
of the core developers) was more interested. He responded: "I'm not
inclined to change this code since nobody has ever reported a problem
with it.", which IMHO is not unreasonable although perhaps a little
short-sighted. He also did some research to find out WHY this is
being done this way, but received no real satisfaction. I wouldn't be
at all surprised to find that after kangax published his article,
someone swept through the JQ code and blindly eliminated all
`instanceof` operators, even if they did not have the relevant cross-
frame issues.

If I had a bit more free time, I'd create a demo plug-in to show the
sorts of things that look reasonable but might break JQ without this
fix, then submit a patch. But I'm pretty swamped right now.

-- Scott
 
M

Michael Haufe (\TNO\)

I'm not sure if that emoticon is to suggest that no one here would
bother.  I would.  I did.  I brought it up on jQuery's developer's
forum here:

   http://forum.jquery.com/topic/why-does-jquery-event-use-preventdefaul...

and got into a debate with one person who thought this suggestion was
an attempt to prove I'm smart by breaking the code  :) and worried
about the 11 additional source-code characters.  Another member (one
of the core developers) was more interested.  He responded: "I'm not
inclined to change this code since nobody has ever reported a problem
with it.", which IMHO is not unreasonable although perhaps a little
short-sighted.  He also did some research to find out WHY this is
being done this way, but received no real satisfaction.  I wouldn't be
at all surprised to find that after kangax published his article,
someone swept through the JQ code and blindly eliminated all
`instanceof` operators, even if they did not have the relevant cross-
frame issues.
[...]

From the linked thread:

"It was my mistake for even bothering to respond to your post that
proves nothing more then you're lack of respect for a documented API
and how well you can break it. Woo! Congrats!"

This speaks volumes IMO.
 
S

Scott Sauyet

The overall outcome is a little disappointing. I'm not familiar enough
with jQuery to judge the actual impact of this problem... maybe someone
can enlighten me. Did I understand correctly that jQuery plugins can -
and do - add random properties to the main jQuery object, basically
without naming restrictions except to avoid conflicts with the official
jQuery API? In this case, there could well be a problem with a (future)
plugin adding a "preventDefault" property, and it would be better to be
safe than sorry.

There is nothing to prevent users from doing so. I don't think it's
common to have it happen. Most plug-ins run against jQuery's wrapped
sets of DOM elements. But there are a number that add functions to
the jQuery constructor object as well. That sort could have this
problem. But I don't think I've ever seen any that are themselves
constructor functions, which is the only case that could trigger this
issue. But there is nothing to prevent someone from writing one.
For reference, the original code author's comments were

| I'm not 100% sure now, it's been a while. I guess the reasons were:
|
| - cross frame compat
| - Seems simpler (short and clean)
| - There was no instanceof [in some browsers] at that time I think
| - I'm pretty sure I took jQuery.fn.init as a reference which at that
|   time did stuff like that.

"cross frame compat" is of no concern in this case.

"seems simpler" - okay, maybe - "(short and clean)" - it's no longer
clean if it can be a source of unintentional behavior, IMO.

Right, although that was not an issue for anyone until this point was
raised, so it's a reasonable answer to why it was coded thus.

"there was no instanceof" - really? I'm not sure, but that seems rather
unlikely. Doesn't jQuery have a list of supported browsers? I would be
very surprised if there was any browser on that list three years ago
(which is when that piece of code was added, I think) that didn't
support instanceof. Thomas Lahn might know more about that.

I'm guessing it's simply faulty memory. "instanceof" was, I'm almost
certain, widely implemented and consistent at that time.

"jQuery.fn.init" - (sorry, can't comment on that, I have no idea what
this function does)

`jQuery.fn` is just an alias to jQuery.prototype. `init` is the
actual constructor function used. Again, I'm guessing there might be
some faulty memory here since, in these days at least, the relevant
code looks like this:

(inside an immediately invoked function expression)
| var jQuery = function( selector, context ) {
| return new jQuery.fn.init( selector, context, rootjQuery );
| },

and the actual `init` function, which is the true constructor, does
not use any similar type checking.

As an aside, I'm glad we can finally discuss techniques used in
libraries like jQuery, without somebody going off about how jQuery is
*evil*. I may not use jQuery, but I'm still interested in why they do
things the way they do.

Agreed. There are many interesting techniques used in the various
libraries. There is plenty of bad code in all of them, but also code
worth looking at.

Here's to civil discourse!

-- Scott
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,582
Members
45,067
Latest member
HunterTere

Latest Threads

Top