Sir Galahad
Sir Galahad

Reputation: 187

Method to declare Javascript objects without the "new" keyword kosher?

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

Upvotes: 3

Views: 113

Answers (2)

Jared Smith
Jared Smith

Reputation: 21926

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.

Upvotes: 1

Scott Weaver
Scott Weaver

Reputation: 7351

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 ?

Upvotes: 0

Related Questions