3

I've been been learning Javascript over the past few months, and sometimes get the feeling that I'm writing code faster than I actually grasp the nuances of the language, so please bear with me here.

Anyway, my question pertains to the behavior of a certain methodology I've been using. The basic gist is to be able to design objects that can be constructed and used without using the "new" keyword.

Here's an example:

<!DOCTYPE html>
<html>
    <head>
        <script>

        function example()
        {
            if(!(this instanceof example))
                return new example();
        }

        example.prototype.test = function()
        {
            var
                result = example();
            if(result instanceof example)
            {
                console.log("Type: example");
                if(result === this)
                    console.log("Unexpected: (result === this)");    
                else
                    console.log("Expected: (result !== this)");    

            }
            else 
                console.log("Type: ?");
            return result;
        }    

        function run()
        {
            var
                one = new example(), 
                two = example(), 
                three = one.test(),
                four = two.test();    
            three.test();
            four.test();
        }

        </script>
    </head>
    <body onload = "run()">
    </body>
</html>

The output I get is:

Type: example
Expected: (result !== this)
Type: example
Expected: (result !== this)
Type: example
Expected: (result !== this)
Type: example
Expected: (result !== this)

...which is exactly what I want to see. My question, specifically, is whether or not it's:

(1) standard behavior

(2) supported by all browsers

(3) prone to any undesirable side-effects

Sir Galahad
  • 187
  • 10
  • 1
    Have you ever used jQuery? Have you noticed that it doesn't use `new`? – Barmar Dec 08 '16 at 18:24
  • Never. My experience with Javascript is rather limited, actually. Do you have any idea how jQuery accomplishes that? – Sir Galahad Dec 08 '16 at 18:32
  • 1
    Very similar to you: `jQuery = function(selector, context) { return new jQuery.fn.init( selector, context ); }` – Barmar Dec 08 '16 at 18:36
  • Of course, you *are* using `new`, you're just hiding it. Whether a behavior is "standard" is opinion-based, since some people prefer classic OOP behavior, creating objects with `new`, whereas others prefer a more functional style. – Heretic Monkey Dec 08 '16 at 18:51
  • Just to clarify, when I say "standard" I mean "compliant with the ECMAScript standard". And yes, I do realize that **new** is still being invoked, just not (necessarily) by the caller. – Sir Galahad Dec 08 '16 at 19:05
  • 1
    Yes, [it's a pretty](http://stackoverflow.com/q/22211755/1048572) [common idiom](http://stackoverflow.com/q/20859985/1048572). – Bergi Dec 08 '16 at 19:29

2 Answers2

1

Your pattern

function Foo() {
  if (!(this instanceof Foo)) {
    return new Foo();
  }
}

is pretty common and well known (yes its kosher). But it has some shortcomings, mostly revolving around dealing with passing arguments around. Also its unnecessary, JavaScript has higher order functions, so there's no need to add boilerplate to every single function you write:

let wrapConstructor = fn => (...args) => {
  return new (fn.bind.apply(fn, [fn, ...args]));
};

let Foo = function Foo() {
  if (!(this instanceof Foo)) {
    throw new TypeError('Not a Foo...');
  }
};

let wrappedFoo = wrapConstructor(Foo);

let foo = wrappedFoo(); // doesn't throw

This has the added benefit of working with built-in constructors like Date:

let wrappedDate = wrapConstructor(Date);
let myDate = wrappedDate(2016, 11, 8); // today's date

In terms of browser compatibility, as written obviously its not great but any environment with Function.prototype.bind and Function.prototype.apply (IE 9+) can run this after a little babel magic.

As to whether or not its standard behavior, well, if you mean conforms to the spec than this certainly does. If you mean 'is this common practice', well, it depends on what JavaScript crowd you run with. If you tend to use a lot of higher-order functions (like my little wrapConstructor utility) then new is something of a pain and something to be avoided.

For a library that includes this sort of functionality, check out ramda.

UPDATE

Note also that you can combine the two patterns easily enough with a slight modification to the helper:

let construct = (fn, args) => {
  return new (fn.bind.apply(fn, [fn, ...args]));
};

function Foo() {
  if (!(this instanceof Foo)) {
    return construct(Foo, Array.from(arguments));
  }
}

now Foo can be called with or without new, and the arguments to the function are automatically passed along without having to know how many there are ahead of time. This method will work with constructors of any arity, including variadic constructors.

Community
  • 1
  • 1
Jared Smith
  • 19,721
  • 5
  • 45
  • 83
  • Interesting! One problem with that sort of idiom though is that it really only makes sense to use outside of the constructor (otherwise you'd have infinite recursion, obviously) which kind of defeats the purpose. I just want the caller to be able to invoke the constructor with or without having to use "new" in the first place. – Sir Galahad Dec 08 '16 at 18:31
0

It works, from a technical point of view. But you should be careful about changing expected semantics unless you have a compelling reason to, which in this instance (no pun intended), I'd argue you do not. Especially if your code is being consumed by other developers - best to stick with convention, like making the Constructor capitalized, and regular functions not.

Can't you just throw an error if the function is not called with new ?

Scott Weaver
  • 7,192
  • 2
  • 31
  • 43
  • This is a pretty common pattern (as Barmar noted in the comments). If you writing an application there should be a style guide to cover this, but if you are authoring a library its actually quite nice to provide the option to the consumers of your API (which is presumably why libraries like jQuery do so). – Jared Smith Dec 08 '16 at 18:53
  • 1
    @sweaver2112 No, I do not want to throw an exception as I am naturally trying to enable that sort of usage. Anyway, considering that it doesn't seem to break anything, I really don't see the harm in adding this bit of functionality to a library. A developer can choose to use that syntax or not, it just boils down to a matter of preference. – Sir Galahad Dec 08 '16 at 19:18