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 the class went
  • Beginners who called User("Asha") and polluted window or got undefined back
  • Anyone who wants the mental model behind new Date() and new 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:

  1. Creates a new empty object linked to User.prototype
  2. Calls User with this = that object
  3. If User does not return its own object, uses the new object
  4. 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

  1. Call a constructor with and without new in strict mode; log the result.
  2. Put save on prototype vs inside the constructor; compare u1.save === u2.save.
  3. 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.