What does 'new' in JavaScript do anyway? [duplicate]
Asked Answered
A

3

41

I am very confused about how constructors work in JavaScript; despite using the language for several years (mostly as if it were like a semi-imperative version of Lisp) I would like to know more about how objects are supposed to work in it.

Given this code:

function Foo(x) {
    return {
        bar: function() { return x; }
    };
}

What is the difference between calling myFoo = Foo(5) and myFoo = new Foo(5)? Or, in other words, what exactly does a constructor in JavaScript do?

Adept answered 27/2, 2012 at 16:0 Comment(2)
related: What is the 'new' keyword in JavaScript?, What does the new keyword do under the hood?.Dotdotage
Even now in 2018, the results (mostly blog articles) I found in google search for "how does new work in javascript" and "how does prototype work in javascript" were unclear (to me), including the MDN tutorial linked by slashnick. The first post I found that I thought was a clear explanation was: yehudakatz.com/2011/08/12/…. (I'm not sure whether that explanation is as complete as Mike Samuel's answer below, but at least it was clear and made sense to me.)Mash
D
50

What is the difference between calling myFoo = Foo(5) and myFoo = new Foo(5)?

There's no difference for that code, because it returns an object, and the spec says:

  • Let result be the result of calling the [[Call]] internal property of F, providing obj as the this value and providing the argument list passed into [[Construct]] as args.
  • If Type(result) is Object then return result.

Since that function returns a result that is an Object, its result is used. You would notice a difference if it did not return an object, or if it checked this, for example if you rewrote it as:

function Foo(x) {
  if (!(this instanceof Foo)) { return new Foo(x); }
  this.bar = function() { return x; };
}
// Now instanceof works.
alert((new Foo) instanceof Foo);

What does new in JavaScript do, anyway?

The new operator causes the function to be called with this bound to a newly created Object whose prototype is that function's prototype property.

For user-defined functions,

new f(a, b, c)

is equivalent to

// Create a new instance using f's prototype.
var newInstance = Object.create(f.prototype), result;

// Call the function
result = f.call(newInstance, a, b, c),

// If the result is a non-null object, use it, otherwise use the new instance.
result && typeof result === 'object' ? result : newInstance

Note, that the language specification actually defines functions with two operations, [[Call]] and [[Construct]], so there are some corner cases where new behaves oddly.

For example, bound and built-in functions:

var g = f.call.bind(f);

should define a function that when called, just calls f, so g should be the same as f in all respects, but

new g()

produces

TypeError: function call() { [native code] } is not a constructor

because the builtin function Function.prototype.call supports [[Call]] but not [[Construct]].

Function.prototype.bind also behaves differently around new and regular calls. The this value is always the bound thisValue when called, but is a newly constructed instance when you use new.

Dressy answered 27/2, 2012 at 16:3 Comment(9)
This is informative, but doesn't exactly answer the question.Adorn
@benekastah, There are two questions. One in the title, and one in the text. I answered the title first, and have now edited to answer the one in the text.Dressy
So you did. I withdraw my objection :)Adorn
Thanks, this is finally a straightforward explanation of how Javascript objects work that I haven't been able to find anywhere else (including from our lead Javascript guy who just says it's "obvious" and "prototype-based," as if that answers everything). +1 and accepted.Adept
@fluffy, You're welcome. When you have questions about core language features like this, the language spec is the best place to go. It's not easy reading but it is authoritative and often has answers that no other document does.Dressy
Well, I did try reading it, of course, but I didn't find it particularly clear. :) Having a nice distilled "this is what it means in plain English" is nice, and was also my goal here - I'm sure that if I had trouble with this, others were even worse off.Adept
regarding line result = f.call(newInstance, a, b, c), result && typeof result === 'object' ? result : newInstance as it is not using comma-operator the rightmost expression is evaluated, but not returned (returns leftmost expression). Are you certain it shouldn't be result = (f.call(newInstance, a, b, c), result && typeof result === 'object' ? result : newInstance) instead (parantheses turns it into comma operator; evals left-to-right, returns rightmost expression)? var a, b; a = 0, 1; b = (0, 1); console.log(a, b); // prints "0 1"Robertaroberto
@felix, wouldn't result be undefined when typeof applies then?Dressy
@MikeSamuel typeof would never apply, as result is declared at first line but not assigned until last. To use an example more similar to your snippet (note that I changed result={} and last line to "aaa : bbb" instead): function f() {return true}; var newInstance = Object.create(f.prototype), result = {}; result = f.call(newInstance), result && typeof result === 'object' ? "aaa" : "bbb"; console.log(typeof result, result); outputs boolean true. So no, when typeof applies, result is assigned return-value of f. While with the parantheses I suggested you get string aaa instead.Robertaroberto
S
13

In this particular example, there's no difference in the end result.

This is because your Foo function is returning an object instance.

The new operator returns a newly created object that inherits from the constructor's prototype only when the function returns a primitive value (or it doesn't return anything, which is technically the undefined value).

For example:

function Foo () {
  return 5; // or "", or null, or no return statement at all (undefined)
}

var foo = new Foo();
typeof foo; // "object"
foo instanceof Foo; // true
Foo.prototype.isPrototypeOf(foo); // true

When you return an object, the newly created object that inherits from the constructor's prototype is simply discarded:

function Foo () {
  return {};
}

var foo = new Foo();
typeof foo; // "object"
foo instanceof Foo; // false
Foo.prototype.isPrototypeOf(foo); // false

See also:

Scrutineer answered 27/2, 2012 at 16:7 Comment(0)
G
7

In this instance there will not be any difference as you are returning a new object. It could be rewritten as:

function Foo(x){
   this._x = x;
}

Foo.prototype.bar = function() {
   return this._x;
}

With this syntax, each time you call new Foo, it will create a new object with a property of _x. The benefit is that the bar function will be stored once and reused for multiple instances of Foo. With the code in the question calling Foo() multiple times, it will create a bar function for every instance. So attaching functions to the prototype rather than having them directly on the object will be lighter in memory.

A full breakdown of how the prototype works can be found at MDN.

Gyneco answered 27/2, 2012 at 16:8 Comment(1)
The link is broken: "Page not found"Sharpsighted

© 2022 - 2024 — McMap. All rights reserved.