What you'll be able to do
By the end of this page you should be able to write a constructor function with new, explain what new actually does in plain steps, and spot the bug when someone calls a constructor without new.
Previous post: call, apply, and bind.
Who this is for
- People who see
function User(name)in older tutorials and wonder where theclasswent - Beginners who called
User("Asha")and pollutedwindowor gotundefinedback - Anyone who wants the mental model behind
new Date()andnew Array()
You can skip this if new and constructor behavior are already obvious. Classes in the next posts are sugar on top of this idea.
Object literals do not scale forever
const user1 = { name: "Asha", role: "admin" };
const user2 = { name: "Dev", role: "editor" };
Copy-paste works twice. At ten users with shared methods, you duplicate save on every object or forget to update one copy.
Scenario A — a small demo. Literals are fine.
Scenario B — many instances with the same behavior. A constructor function builds objects with one blueprint.
A constructor function + new
function User(name, role) {
this.name = name;
this.role = role;
}
User.prototype.save = function () {
console.log("Saving " + this.name);
};
const u1 = new User("Asha", "admin");
const u2 = new User("Dev", "editor");
u1.save(); // Saving Asha
Scenario A — correct call. new User(...) creates a fresh object, sets this to that object, and returns it (unless you return your own object).
Scenario B — method on prototype. save lives once on User.prototype; both instances share it (prototype chain — next post).
What new actually does (four steps)
When you write new User("Asha"), the engine roughly:
- Creates a new empty object linked to
User.prototype - Calls
Userwiththis= that object - If
Userdoes not return its own object, uses the new object - Returns that object
You do not need to memorize engine internals — the habit is capitalize constructor names and always use new when the function is meant as a constructor.
Forgetting new — the classic bug
function User(name) {
this.name = name;
}
const u = User("Asha"); // no new!
Scenario A — sloppy mode in a script tag. this might become window; you just set window.name = "Asha".
Scenario B — strict mode or modules. this is undefined; assignment throws or you get undefined back instead of an instance.
Some teams use new.target or factory functions to guard this. For learning, the fix is simpler: if it is a constructor, type new.
A tiny practice file
<!DOCTYPE html>
<html>
<body>
<button id="go">Make users</button>
<pre id="out"></pre>
<script>
"use strict";
function User(name) {
this.name = name;
}
User.prototype.label = function () {
return "User: " + this.name;
};
const out = document.getElementById("out");
document.getElementById("go").addEventListener("click", function () {
const a = new User("Asha");
const b = new User("Dev");
out.textContent = a.label() + "\n" + b.label();
});
</script>
</body>
</html>
Both instances share label from User.prototype.
Mistakes I see a lot
1. Forgetting new. The function runs, this is wrong, and you get garbage or a global leak.
2. Putting every method inside the constructor. That recreates functions per instance; shared methods belong on prototype.
3. Returning a primitive from a constructor. return 5 is ignored; returning a different object replaces the new instance — rare but confusing.
4. Treating new as "old" and skipping the mental model. class still uses new under the hood.
What to try before the next post
- Call a constructor with and without
newin strict mode; log the result. - Put
saveonprototypevs inside the constructor; compareu1.save === u2.save. - Build the tiny HTML file.
Next in this series: prototype chain — where .map and shared methods really live.
Try this next outside the series
These mental models become more useful when you can point at a real framework problem they explain.
- React Server Components explained — see execution boundaries and composition decisions in a modern app
- Node.js error handling — watch call stacks and thrown errors matter in real request code