Command Palette

Search for a command to run...

Hectal
PHASE 7Intermediate ~26 min· topic 4 of 8

Topic 7.4

Constructor Functions and new

In one line

A constructor is an ordinary function meant to be called with new; new creates a fresh object, links it to the function's prototype property, runs the function with this set to that object, and gives you the object back.

Think of it like this

A biscuit cutter and a tray. The constructor is the cutter: the same shape every time. Each press makes a new biscuit (an instance) that you can decorate differently: one with chocolate, one with jam. The recipe card pinned on the wall that every biscuit follows ("bake for 10 minutes") is the prototype: written once, used by every biscuit, not copied onto each one.

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

Constructor function
A regular function written to be called with new to build objects. Its name starts with a capital letter by convention.
Instance
One object built by a constructor or class, like one biscuit from the cutter.
new
The operator that creates an object, links it to the constructor's prototype, runs the constructor on it and returns it.
prototype property
An object on a function (Person.prototype) that becomes the prototype of every instance new makes.
constructor property
A property on Fn.prototype pointing back to Fn, so instances can find their maker.
instanceof
An operator that checks whether a constructor's prototype is in an object's prototype chain.
new.target
Inside a function, the constructor if it was called with new, or undefined if not.
Primitive wrapper
An object like new Number(5) that wraps a primitive value. Almost never wanted.

Step by step

01Write a constructor

It fills in this. There's no return: new will return the object for us.

person.jswhole filejavascript
function Person(name, city) {
  this.name = name;
  this.city = city;
}

02Put shared methods on the prototype

Person.prototype already exists; we add to it. Every person made with new Person will find greet through the chain.

person.jswhole filejavascript
Person.prototype.greet = function () {
  return "Hi, I am " + this.name + " from " + this.city;
};

03Call it with new

Here is what happens inside new Person("Asha", "Pune"), in order.

Call it with newdiagram
Rendering diagram…

04Check the links

The instance has its own name and city, and finds greet and constructor on Person.prototype.

person.jswhole filejavascript
const asha = new Person("Asha", "Pune");
Object.getPrototypeOf(asha) === Person.prototype; // true
asha instanceof Person;                             // true
asha.constructor === Person;                        // true (inherited)

05Write new yourself

Interviewers love asking for this. With Object.create and apply you can do all four steps in a few lines, which proves new has no magic beyond them.

my-new.jswhole filejavascript
function myNew(Fn, ...args) {
  const obj = Object.create(Fn.prototype);   // steps 1 and 2
  const result = Fn.apply(obj, args);        // step 3
  return result !== null && (typeof result === "object" || typeof result === "function")
    ? result                                 // step 4: an object was returned
    : obj;
}

Try it yourself

  1. 1

    Move the method into the constructor

    In the first example, delete the Person.prototype.greet = ... block and instead write this.greet = function () { return "Hi, I am " + this.name; }; inside Person. Predict the greet is shared line and the own keys line.

  2. 2

    Break myNew

    In the second example, change Object.create(Fn.prototype) to {}. Which line fails now, and why? (Think: where does label live?)

  3. 3

    Remove the guard

    In the third example, delete the if (!new.target) { ... } block. Predict the second log line before running. What does strict mode do here?

Code & diagrams

A constructor with shared methods New tab

The console shows the constructor's name before the object. `greet` isn't an own key: it lives once on `Person.prototype`.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

Person { name: 'Asha', city: 'Pune' }
true true
greet is shared: true
own keys: [ 'name', 'city' ]
What new does, rebuilt by hand New tab

Returning an object from a constructor throws the new object away. Returning a number is ignored.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

Tea: Rs 30 | Coffee: Rs 50
true
{ y: 2 } ReturnsNumber { x: 1 }
Guarding against a missing new ES2015+ New tab

`Point` repairs a missing `new`. `Unsafe` has no guard, so in strict mode `this` is `undefined` and the script stops. A wrapper object like `new Boolean(false)` is truthy.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

Point { x: 1, y: 2 }
Point { x: 3, y: 4 }
arrow has prototype? false
object true
Uncaught TypeError: Cannot set properties of undefined (setting 'name')

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

Forget new in sloppy mode

Without "use strict", write function User(name) { this.name = name; } then const u = User("Asha"); console.log(u, window.name);.

terminal
$ Console
── what you'll see ──
undefined Asha

Break #2

Replace the prototype and lose constructor

Write Person.prototype = { greet() { return "hi"; } }; then check new Person("Asha").constructor === Person.

terminal
$ Console
── what you'll see ──
false

Myth vs fact

Myth

Constructor functions are a special kind of function.

Fact

Any regular function can be called with new. The capital letter is only a naming convention to signal intent. What matters is the call: new Fn() versus Fn().

Myth

instanceof checks which constructor created the object.

Fact

It checks whether Fn.prototype is anywhere in the object's prototype chain. Change the chain or replace Fn.prototype and the answer changes, whoever created the object.

Myth

Methods defined inside the constructor are the same as prototype methods.

Fact

They behave alike when called, but each instance gets its own copy of the function, using more memory, and they can't be changed for all instances at once.

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    Functions have two internal methods: [[Call]] (used by f()) and [[Construct]] (used by new f()). Arrow functions, methods, async functions and generators have only [[Call]], so they are not constructors. Classes have [[Construct]] but their [[Call]] throws.

  • ▸

    instanceof is customisable with static [Symbol.hasInstance](value), and it fails across realms: an array from an iframe isn't instanceof Array in the parent page, because each window has its own Array.prototype. That's why Array.isArray exists. For your own types, a brand check such as #field in obj (Topic 7.6) is more reliable.

  • ▸

    V8 uses slack tracking: the first few objects a constructor creates are given extra room, then the engine settles on a fixed size and shape for later instances. Assigning the same properties in the same order in every constructor call keeps all instances on one hidden shape and keeps property access fast.

  • ▸

    Before classes, libraries built inheritance between constructors by hand: Child.prototype = Object.create(Parent.prototype); Child.prototype.constructor = Child; and Parent.call(this, ...) inside Child. You'll still read this in older code; class ... extends (Topic 7.7) does the same wiring for you.

Remember this

  1. 1

    When you need many objects with the same shape (many users, many products), writing each literal by hand repeats code. A constructor function is a regular function that fills in this with properties. By convention its name starts with a capital letter (Person, Product) to say "call me with new". The objects it makes are called instances.

  2. 2

    **What new Person("Asha") does, step by step:** (1) creates a brand-new empty object; (2) sets that object's [[Prototype]] to Person.prototype; (3) calls Person with this set to the new object and your arguments; (4) if the function returns an object, that object is the result; otherwise (no return, or a returned primitive like a number) the result is the new object from step 1. Steps 1, 2 and 4 are why a constructor doesn't need a return.

  3. 3

    Every regular function automatically gets a **prototype property**: an object with one property, constructor, pointing back to the function. Put methods there, not inside the constructor: Person.prototype.greet = function () { ... }. Then all instances share one copy through the prototype chain (Topic 7.3). A method created inside the constructor (this.greet = function () {}) is a new function for every instance, which costs memory.

  4. 4

    **instanceof** answers "was this object built from that constructor?" by checking the chain: asha instanceof Person is true when Person.prototype appears anywhere in asha's prototype chain. Each instance can also reach its maker through the chain: asha.constructor === Person, because constructor lives on Person.prototype. Built-ins work the same way: new Date(), new Map(), new Error("x").

  5. 5

    **Forgetting new** is the classic bug. Person("Asha") is a plain call (Topic 7.1): no new object is made and this is undefined in strict mode (so setting this.name throws) or window in sloppy mode (so you silently create a global variable name and get undefined back). Since ES2015, **new.target** tells a function whether it was called with new: it is the constructor when called with new, and undefined otherwise. Classes (Topic 7.5) refuse to be called without new at all.

  6. 6

    Not every function can be a constructor. Arrow functions and method shorthand ({ m() {} }) have no prototype property and no own this, so new on them throws ... is not a constructor. Also avoid new on primitive wrappers: new Number(5), new String("a") and new Boolean(false) create objects, and an object is always truthy, so if (new Boolean(false)) runs. Call them without new (Number("5")) to convert values instead (Topic 1.5).

Explain it without notes

01

What exactly happens when you call a function with new?

02

Why should methods go on Fn.prototype instead of being created inside the constructor?

03

How does instanceof work, and when can it give a surprising answer?

04

What happens if you forget new, and how can you protect against it?

Practice

01

Write a constructor Book(title, pages) with a prototype method isLong() that returns true when pages are over 300. Create two books and print isLong() for each.

02

Write myNew(Fn, ...args) without looking, and test it with a constructor Pet(name) that has a prototype method hello().

03

Add a new.target guard to Book that throws a clear error (Book must be called with new) instead of fixing the call, and show what happens when someone calls Book("x", 10).

Trade-offs

  • ↔

    Constructor functions work in every engine and show exactly what new does, but the prototype wiring is manual and easy to get wrong; classes (next topic) produce the same objects with clearer syntax and built-in safety checks.

  • ↔

    Methods inside the constructor can close over truly private variables, at the cost of one function copy per instance; prototype methods are shared but can only use public properties (or #private fields in classes).

Done when you can

  • I can write a constructor function and create instances with new.

  • I can list the four steps new performs, and write myNew myself.

  • I put shared methods on Fn.prototype.

  • I can explain how instanceof and the constructor property work.

  • I can guard a constructor with new.target and know why classes are safer.