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
newto 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 instancenewmakes. - constructor property
- A property on
Fn.prototypepointing back toFn, so instances can find their maker. - instanceof
- An operator that checks whether a constructor's
prototypeis in an object's prototype chain. - new.target
- Inside a function, the constructor if it was called with
new, orundefinedif 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.
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.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.
04Check the links
The instance has its own name and city, and finds greet and constructor on Person.prototype.
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.
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
Move the method into the constructor
In the first example, delete the
Person.prototype.greet = ...block and instead writethis.greet = function () { return "Hi, I am " + this.name; };insidePerson. Predict thegreet is sharedline and theown keysline. - 2
Break myNew
In the second example, change
Object.create(Fn.prototype)to{}. Which line fails now, and why? (Think: where doeslabellive?) - 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
The console shows the constructor's name before the object. `greet` isn't an own key: it lives once on `Person.prototype`.
Preview
Console
Nothing logged yet.Expected console output
Person { name: 'Asha', city: 'Pune' }
true true
greet is shared: true
own keys: [ 'name', 'city' ]Returning an object from a constructor throws the new object away. Returning a number is ignored.
Preview
Console
Nothing logged yet.Expected console output
Tea: Rs 30 | Coffee: Rs 50
true
{ y: 2 } ReturnsNumber { x: 1 }`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
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);.
Break #2
Replace the prototype and lose constructor
Write Person.prototype = { greet() { return "hi"; } }; then check new Person("Asha").constructor === Person.
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 byf()) and[[Construct]](used bynew f()). Arrow functions, methods, async functions and generators have only[[Call]], so they are not constructors. Classes have[[Construct]]but their[[Call]]throws. - ▸
instanceofis customisable withstatic [Symbol.hasInstance](value), and it fails across realms: an array from an iframe isn'tinstanceof Arrayin the parent page, because each window has its ownArray.prototype. That's whyArray.isArrayexists. 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;andParent.call(this, ...)insideChild. You'll still read this in older code;class ... extends(Topic 7.7) does the same wiring for you.
Remember this
- 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
thiswith properties. By convention its name starts with a capital letter (Person,Product) to say "call me withnew". The objects it makes are called instances. - 2
**What
new Person("Asha")does, step by step:** (1) creates a brand-new empty object; (2) sets that object's[[Prototype]]toPerson.prototype; (3) callsPersonwiththisset to the new object and your arguments; (4) if the function returns an object, that object is the result; otherwise (noreturn, 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 areturn. - 3
Every regular function automatically gets a **
prototypeproperty**: 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
**
instanceof** answers "was this object built from that constructor?" by checking the chain:asha instanceof PersonistruewhenPerson.prototypeappears anywhere inasha's prototype chain. Each instance can also reach its maker through the chain:asha.constructor === Person, becauseconstructorlives onPerson.prototype. Built-ins work the same way:new Date(),new Map(),new Error("x"). - 5
**Forgetting
new** is the classic bug.Person("Asha")is a plain call (Topic 7.1): no new object is made andthisisundefinedin strict mode (so settingthis.namethrows) orwindowin sloppy mode (so you silently create a global variablenameand getundefinedback). Since ES2015, **new.target** tells a function whether it was called withnew: it is the constructor when called withnew, andundefinedotherwise. Classes (Topic 7.5) refuse to be called withoutnewat all. - 6
Not every function can be a constructor. Arrow functions and method shorthand (
{ m() {} }) have noprototypeproperty and no ownthis, sonewon them throws... is not a constructor. Also avoidnewon primitive wrappers:new Number(5),new String("a")andnew Boolean(false)create objects, and an object is always truthy, soif (new Boolean(false))runs. Call them withoutnew(Number("5")) to convert values instead (Topic 1.5).
Explain it without notes
What exactly happens when you call a function with new?
Why should methods go on Fn.prototype instead of being created inside the constructor?
How does instanceof work, and when can it give a surprising answer?
What happens if you forget new, and how can you protect against it?
Practice
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.
Write myNew(Fn, ...args) without looking, and test it with a constructor Pet(name) that has a prototype method hello().
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
newdoes, 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
#privatefields in classes).
Done when you can
I can write a constructor function and create instances with
new.I can list the four steps
newperforms, and writemyNewmyself.I put shared methods on
Fn.prototype.I can explain how
instanceofand theconstructorproperty work.I can guard a constructor with
new.targetand know why classes are safer.