Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

JavaScript (ES2026)  Module 11 – Prototypes and classes

Constructor functions and the new operator

The four steps the new operator performs, how constructor functions and prototype methods were written before classes, and what goes wrong without new.

  • Intermediate
  • 18 minutes
  • Examples run with Node.js 24.21.0 and quickjs 0.32.0
  • By MySmartCoPilot
Free lesson, Pro answers: 18 minutes
Free: the whole lesson. With a pass: the model answers to its 2 interview questions.

What you will learn

  • Explain the four steps the new operator performs
  • Read pre-class code that uses constructor functions and prototype methods
  • Avoid calling constructors without new, and guard old constructors with new.target
  • Identify which functions can be called with new, and how built-in functions treat it

Before you start

What you’ll learn · Before you start

What you will learn

  • Explain the four steps the new operator performs
  • Read pre-class code that uses constructor functions and prototype methods
  • Avoid calling constructors without new, and guard old constructors with new.target
  • Identify which functions can be called with new, and how built-in functions treat it

Before you start

On this page

A constructor function is an ordinary function written to be called with new. new Account('Asha') creates a new object, links it to Account.prototype, runs Account with this set to that object so the function can fill it in, and hands the object back, unless the function returned a different object instead. Methods are stored once, on Account.prototype, and every object made this way inherits them through its prototype chain. Before ES2015 added class, this was how JavaScript made many objects of one kind, and the pattern is still everywhere: in older libraries, in code compiled for old browsers and in many answers you will find online. Classes perform the same steps underneath. This lesson takes new apart step by step, measures why methods belong on the prototype, reads the patterns of pre-class code, and reproduces what happens when someone forgets new.

What new does, in four steps

A function written with function, as a declaration or an expression (not a generator or an async function), gets a prototype property when it is created: an object with a constructor property that points back to the function. That object is waiting to become the prototype of whatever new makes. Here a constructor is called with new, and then the same work is done by hand:

new Booking(…) and the same four steps by hand JavaScript · what-new-does.mjs
// A constructor function, called with new, and the same four steps written out by hand.
function Booking(passenger, seat) {
  this.passenger = passenger;
  this.seat = seat;
}
Booking.prototype.label = function () {
  return `${this.passenger}, seat ${this.seat}`;
};

const withNew = new Booking('Anil', '14C');

// 1. Create a new, empty object.
// 2. Link it to Booking.prototype (that object becomes its [[Prototype]]).
const obj = Object.create(Booking.prototype);
// 3. Run Booking with this set to the new object.
const returned = Booking.call(obj, 'Anil', '14C');
// 4. The result is the new object, unless Booking returned an object of its own.
const isObject = (typeof returned === 'object' && returned !== null) || typeof returned === 'function';
const byHand = isObject ? returned : obj;

console.log(withNew.label(), '|', byHand.label());
console.log(Object.getPrototypeOf(withNew) === Booking.prototype, Object.getPrototypeOf(byHand) === Booking.prototype);
console.log(withNew instanceof Booking, byHand instanceof Booking);
console.log('Booking returned:', returned);

// Step 4 in action: an object that the constructor returns replaces the new one; a primitive is ignored.
function GivesObject() {
  this.made = 'by new';
  return { made: 'by the return statement' };
}
function GivesNumber() {
  this.made = 'by new';
  return 42;
}
console.log(new GivesObject().made, '|', new GivesNumber().made);

Output

Anil, seat 14C | Anil, seat 14C
true true
true true
Booking returned: undefined
by the return statement | by new

Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node what-new-does.mjs

The four steps of new Booking('Anil', '14C'): create an object, link its prototype, run Booking with this, then pick the result.new Booking('Anil', '14C')1. Create a new, empty object2. Set its [[Prototype]]to Booking.prototype3. Run Booking with this =the new object, and the arguments4. Did Booking return an object?Yes: that objectis the resultNo: the new objectis the resultyesno (nothing, or a primitive)

What new does with a constructor function

Text description of the diagram

The diagram shows what happens when `new Booking('Anil', '14C')` runs, from top to bottom.

1. A new, empty object is created. 2. Its `[[Prototype]]` is set to `Booking.prototype`, so the object inherits the methods stored there. If `Booking.prototype` is not an object, `Object.prototype` is used instead. 3. `Booking` runs with `this` set to the new object and with the arguments `'Anil'` and `'14C'`, so its assignments to `this` fill the new object in. 4. If `Booking` returned an object, that object is the result of the whole `new` expression. If it returned nothing or a primitive value such as a number, the new object from step 1 is the result.

  1. Create a new, empty object.
  2. Link it: its [[Prototype]] becomes Booking.prototype. That is why both objects pass the instanceof Booking test and can call label(). If Booking.prototype has been replaced by something that is not an object, the new object gets Object.prototype instead.
  3. Run Booking with this set to the new object and with the arguments. The assignments to this fill the object in. The function itself returned nothing, which is why Booking returned: undefined is printed.
  4. Return the new object, unless the function returned an object of its own. The last line shows both cases: GivesObject returned an object, which replaced the new one, while the 42 from GivesNumber was ignored.

The specification describes the same sequence in the [[Construct]] method of a function, with GetPrototypeFromConstructor for step 2. Returning a different object from a constructor is legal but surprises every reader, so avoid it; ESLint’s no-constructor-return rule reports it in class constructors.

Methods belong on the prototype

A constructor can also create methods for each object inside its body, this.deposit = function (paise) { … }. That works, but every call of the constructor then makes new function objects. This run counts what that costs in V8, the engine of Node.js and Chrome, for 10,000 accounts:

Two methods per account, made in the constructor or shared on the prototype JavaScript · method-cost.mjs
// What it costs when every object gets its own copies of its methods, measured in V8, the engine of Node.js.
// v8.queryObjects() runs a full garbage collection and then counts the functions that are still reachable; the heap
// is read right after it, so both numbers describe live objects only.
import { queryObjects } from 'node:v8';

// Node.js 24 warns that queryObjects() is experimental, with the process number in the message. These two lines
// print the same warning without the number, so that every run prints the same text.
process.removeAllListeners('warning');
process.on('warning', (warning) => console.error(`${warning.name}: ${warning.message}`));

function AccountWithOwnMethods(owner) {
  this.owner = owner;
  this.balancePaise = 0;
  this.deposit = function (paise) {
    this.balancePaise += paise;
  };
  this.statement = function () {
    return `${this.owner}: ${this.balancePaise} paise`;
  };
}

function Account(owner) {
  this.owner = owner;
  this.balancePaise = 0;
}
Account.prototype.deposit = function (paise) {
  this.balancePaise += paise;
};
Account.prototype.statement = function () {
  return `${this.owner}: ${this.balancePaise} paise`;
};

const owners = [];
for (let i = 0; i < 10_000; i++) owners.push(`customer ${i}`);

function measure(label, Constructor) {
  const accounts = new Array(owners.length);
  const functionsBefore = queryObjects(Function, { format: 'count' });
  const heapBefore = process.memoryUsage().heapUsed;
  for (let i = 0; i < owners.length; i++) accounts[i] = new Constructor(owners[i]);
  const functionsAfter = queryObjects(Function, { format: 'count' });
  const bytesPerAccount = (process.memoryUsage().heapUsed - heapBefore) / accounts.length;
  console.log(`${label}: ${functionsAfter - functionsBefore} new functions, about ${Math.round(bytesPerAccount)} bytes per account`);
  return accounts;
}

const own = measure('methods made in the constructor', AccountWithOwnMethods);
const shared = measure('methods on the prototype       ', Account);
console.log('own[0].deposit === own[1].deposit:', own[0].deposit === own[1].deposit);
console.log('shared[0].deposit === shared[1].deposit:', shared[0].deposit === shared[1].deposit);

Output

methods made in the constructor: 20000 new functions, about 201 bytes per account
methods on the prototype       : 0 new functions, about 41 bytes per account
own[0].deposit === own[1].deposit: false
shared[0].deposit === shared[1].deposit: true

Printed as an error (standard error)

ExperimentalWarning: v8.queryObjects() is an experimental feature and might change at any time

This output changes from run to run: the heap sizes vary by a few bytes per account between runs

Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node method-cost.mjs

The version that makes its methods in the constructor created exactly two functions per account, 20,000 in all, and in the recorded run each of its accounts took about five times the memory of an account that shares its methods through Account.prototype. The shared version created no functions at all, because deposit and statement exist once and every account finds them on its prototype. Inside them, this is the account the method was called on, so one function serves every object. Per-object functions still have their place: a function made inside the constructor can keep private variables in a closure. For methods that only use this, the prototype is the right home.

Reading code written before classes

Older code puts the pieces next to each other by hand: the constructor, then one assignment per method on its prototype, and “static” helpers as properties of the function itself. Here is one account type written that way and again as a class:

A constructor function and the class that replaces it JavaScript · legacy-account.mjs
// One account type written twice: as a constructor function (how code was written before ES2015) and as a class.

// --- The constructor-function version ---
function Account(owner, balancePaise) {
  this.owner = owner;
  this.balancePaise = balancePaise;
}
Account.prototype.deposit = function (paise) {
  this.balancePaise += paise;
  return this;
};
Account.prototype.toString = function () {
  return `${this.owner}: Rs ${(this.balancePaise / 100).toFixed(2)}`;
};
Account.fromRupees = function (owner, rupees) {
  return new Account(owner, Math.round(rupees * 100)); // a "static" helper: a property of the function
};

// --- The class version ---
class AccountClass {
  constructor(owner, balancePaise) {
    this.owner = owner;
    this.balancePaise = balancePaise;
  }
  deposit(paise) {
    this.balancePaise += paise;
    return this;
  }
  toString() {
    return `${this.owner}: Rs ${(this.balancePaise / 100).toFixed(2)}`;
  }
  static fromRupees(owner, rupees) {
    return new AccountClass(owner, Math.round(rupees * 100));
  }
}

const a = Account.fromRupees('Meera', 1250.5).deposit(4950);
const b = AccountClass.fromRupees('Meera', 1250.5).deposit(4950);
console.log(String(a), '|', String(b));
console.log(typeof Account, typeof AccountClass);
console.log(Object.keys(a), Object.keys(b)); // own properties: the same

// Differences that show up in real code:
const forIn = (obj) => {
  const keys = [];
  for (const key in obj) keys.push(key);
  return keys.join(', ');
};
console.log('for...in, function version:', forIn(a)); // methods assigned to the prototype are enumerable
console.log('for...in, class version:   ', forIn(b)); // class methods are not
const flags = (C) => Object.getOwnPropertyDescriptor(C, 'prototype').writable;
console.log('prototype can be replaced:', flags(Account), flags(AccountClass));

Output

Meera: Rs 1300.00 | Meera: Rs 1300.00
function function
[ 'owner', 'balancePaise' ] [ 'owner', 'balancePaise' ]
for...in, function version: owner, balancePaise, deposit, toString
for...in, class version:    owner, balancePaise
prototype can be replaced: true false

Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node legacy-account.mjs

Both versions build the same objects, with the same own properties, and typeof calls both a function. The differences show up at the edges:

  • Methods assigned to Account.prototype are enumerable, so a for…in loop over an account lists deposit and toString next to the data. Methods written in a class body are not.
  • The function’s prototype property is writable, so old code can, and sometimes does, replace the whole object. A class’s prototype property is read-only.

Replacing the prototype is the pattern that causes the most confusion when you read old code:

Parcel.prototype replaced by an object literal JavaScript · replaced-prototype.mjs
// Old code often replaced a constructor's whole prototype with an object literal. Two things change.
function Parcel(weightKg) {
  this.weightKg = weightKg;
}
const early = new Parcel(2); // made while Parcel.prototype is still the original object

Parcel.prototype = {
  charge() {
    return 40 + 15 * Math.ceil(this.weightKg);
  },
};
const late = new Parcel(3.2);

console.log('late.charge():', late.charge());
// 1. The literal has no constructor property, so the one inherited from Object.prototype shows through.
console.log('late.constructor === Parcel:', late.constructor === Parcel, '| it is', late.constructor.name);
// 2. Objects made earlier keep the old prototype: they lost the method, and instanceof now says no.
console.log('typeof early.charge:', typeof early.charge, '| early instanceof Parcel:', early instanceof Parcel);

// The usual repair in such code: put constructor back, as a non-enumerable property like the original one.
Object.defineProperty(Parcel.prototype, 'constructor', { value: Parcel, writable: true, configurable: true });
console.log('after the repair:', new Parcel(1).constructor === Parcel);

Output

late.charge(): 100
late.constructor === Parcel: false | it is Object
typeof early.charge: undefined | early instanceof Parcel: false
after the repair: true

Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node replaced-prototype.mjs

The new literal has no constructor property, so late.constructor falls through to the one on Object.prototype. Worse, early, made before the replacement, still points to the old prototype: it lost charge(), and early instanceof Parcel is now false, because instanceof looks for the current Parcel.prototype in the object’s chain. Code that replaced prototypes usually put constructor back, as the last lines do, and had to run before any object was made. The same era also built inheritance by hand, with Parent.call(this, …) inside the child constructor and Child.prototype = Object.create(Parent.prototype); the lesson on extends in this module puts that pattern next to a class.

Forgetting new

A constructor function can be called like any other function, and without new nothing creates an object for it. What happens then depends on the code around it:

A constructor called without new, three ways JavaScript · without-new.mjs
// One mistake, three outcomes: a constructor called without new.

// A constructor in sloppy code, the way old classic scripts were written. The Function constructor makes sloppy code
// unless the text starts with "use strict", so it stands in for such a script here.
// eslint-disable-next-line no-new-func -- the example needs a function compiled as sloppy code
const SloppyWallet = new Function('owner', 'this.owner = owner; this.balance = 0;');
const sloppy = SloppyWallet('Asha'); // no new: this is the global object
console.log('sloppy:', sloppy, '| globalThis.owner is now', globalThis.owner);
delete globalThis.owner; // remove the two globals the call created
delete globalThis.balance;

// The same constructor in strict code (every module is strict): this is undefined, so its first line throws.
function StrictWallet(owner) {
  this.owner = owner;
  this.balance = 0;
}
try {
  StrictWallet('Asha');
} catch (err) {
  console.log('strict:', `${err.name}: ${err.message}`);
}

// A class refuses the call before its body runs.
class ClassWallet {
  constructor(owner) {
    this.owner = owner;
    this.balance = 0;
  }
}
try {
  ClassWallet('Asha');
} catch (err) {
  console.log('class: ', `${err.name}: ${err.message}`);
}

// new.target is the function that new was used with, and undefined in a plain call: a guard for old constructors.
function GuardedWallet(owner) {
  if (!new.target) throw new TypeError('GuardedWallet must be called with new');
  this.owner = owner;
  this.balance = 0;
}
try {
  GuardedWallet('Asha');
} catch (err) {
  console.log('guard: ', `${err.name}: ${err.message}`);
}
console.log('with new:', new GuardedWallet('Asha').owner);

Output

sloppy: undefined | globalThis.owner is now Asha
strict: TypeError: Cannot set properties of undefined (setting 'owner')
class:  TypeError: Class constructor ClassWallet cannot be invoked without 'new'
guard:  TypeError: GuardedWallet must be called with new
with new: Asha

Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node without-new.mjs

Version note

When you press Run, the QuickJS engine reports the two engine errors in its own words: cannot set property 'owner' of undefined for the strict function and class constructors must be invoked with 'new' for the class.

In your browser, QuickJS (quickjs-emscripten 0.32.0) prints:

sloppy: undefined | globalThis.owner is now Asha
strict: TypeError: cannot set property 'owner' of undefined
class:  TypeError: class constructors must be invoked with 'new'
guard:  TypeError: GuardedWallet must be called with new
with new: Asha
  • In sloppy code, the old default of classic scripts, a plain call sets this to the global object. The constructor’s assignments quietly created two global variables, owner and balance, and the call returned undefined. In a browser the global object is window, so a constructor that sets this.name overwrites window.name, the name of the browser window.
  • In strict code, which includes every module and every class, this is undefined in a plain call, so the first assignment throws a TypeError. That is a better outcome: the bug shows up on the line that has it.
  • A class constructor refuses a plain call before any of its code runs, with a TypeError.

new.target, added in ES2015, tells a function how it was called: it holds the function that new was used with, and undefined in a plain call. Libraries written before it used the test this instanceof Wallet for the same job, either to throw, as GuardedWallet does, or to repair the call with return new Wallet(owner). Throwing is clearer, and a class does it for you. The other defence is a naming convention: constructors start with a capital letter and ordinary functions do not, so a call such as Wallet('Asha') looks wrong at a glance, and ESLint’s new-cap rule checks the convention.

Which functions can be called with new?

Only functions that have a [[Construct]] method are constructors. Reflect.construct throws when its third argument is not one, which gives a way to ask without running anything:

Constructors and non-constructors JavaScript · constructible.mjs
// Which functions can be called with new? Reflect.construct(String, [], F) throws a TypeError when F is not a
// constructor, so it answers the question without running F.
function canConstruct(fn) {
  try {
    Reflect.construct(String, [], fn);
    return true;
  } catch {
    return false;
  }
}

const shop = {
  method() {},
};
class Basket {}
const kinds = {
  'function declaration': function plain() {},
  'arrow function': () => {},
  'method in an object literal': shop.method,
  'class': Basket,
  'async function': async function load() {},
  'generator function': function* ids() {},
  'bound function': function plain() {}.bind(null),
};
for (const [kind, fn] of Object.entries(kinds)) {
  console.log(`${kind.padEnd(28)} ${canConstruct(fn) ? 'yes' : 'no'}`);
}

// Built-in functions decide for themselves what a call with and without new means.
console.log(typeof Date(), typeof new Date(0)); // a string, then a Date object
console.log(typeof String(42), typeof new String(42)); // a primitive, then a wrapper object
try {
  // eslint-disable-next-line no-new-native-nonconstructor -- the TypeError is what the line shows
  new Symbol('id');
} catch (err) {
  console.log('new Symbol:', err.name);
}

Output

function declaration         yes
arrow function               no
method in an object literal  no
class                        yes
async function               no
generator function           no
bound function               yes
string object
string object
new Symbol: TypeError

Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node constructible.mjs

Function declarations and expressions, classes, and functions made from them with bind() are constructors. Arrow functions, methods written with the shorthand syntax, async functions and generators are not: new with one of them throws TypeError: … is not a constructor. Arrow functions, shorthand methods and async functions do not even have a prototype property; a generator function has one, but it serves the generator objects the function returns, not new.

Built-in functions decide for themselves what a call with or without new means. Date() returns the current time as a string, while new Date() makes a Date object. String(42) converts to a primitive string, while new String(42) makes a wrapper object, whose typeof is 'object' and which is truthy even when it wraps '', so avoid it; ESLint’s no-new-wrappers rule reports it. Symbol and BigInt refuse new with a TypeError.

Constructor function or class?

A constructor function and a class compared
Criterion Constructor functionClass
Called without new Runs: sloppy code creates globals, strict code throwsAlways a TypeError
Methods added to the prototype Enumerable when assigned, so for…in lists themNot enumerable
The prototype property Writable: it can be replacedRead-only
Used before its declaration Works: function declarations are hoistedReferenceError until the class has been defined
Body Sloppy unless the code is strictAlways strict
When to choose Reading and maintaining older code, or code that must run unchanged in very old enginesNew code: the same objects and prototype methods, with fewer traps, plus fields, private members and extends
JavaScript Linter Paste constructible.mjs, delete its eslint-disable comment and choose Strict: no-new-native-nonconstructor marks new Symbol(…) and no-new-wrappers marks new String(42). JavaScript & TypeScript Online Compiler Run what-new-does.mjs, make Booking return an array, and predict what new Booking(…) gives before you run it again.

Exercise: write new yourself

Exercise · Medium · JavaScript

Write your own new as a function

Write myNew(Constructor, ...args), a function that does what new Constructor(...args) does for an ordinary constructor function, without using new or Reflect.construct.

  • The result is linked to Constructor.prototype: Object.getPrototypeOf(myNew(F)) === F.prototype, so instanceof and inherited methods work. When Constructor.prototype is not an object (someone replaced it with a number, say), link the result to Object.prototype instead, as new does.
  • Constructor runs once, with this set to the new object and with every argument passed on.
  • If Constructor returns an object, and functions count as objects here, that object is the result. Any other return value (a number, a string, undefined or null) is ignored, and the new object is the result.
  • If Constructor is not a function, throw a TypeError.
function Ticket(from, to) {
  this.route = `${from}-${to}`;
}
Ticket.prototype.label = function () {
  return `Ticket ${this.route}`;
};
const t = myNew(Ticket, 'Pune', 'Goa');
t.label();             // 'Ticket Pune-Goa'
t instanceof Ticket;   // true

The starter code runs the constructor on a plain object and returns that object. Run the sample tests to see what it misses.

Starter code · my-new.mjs

// myNew(Constructor, ...args): what new Constructor(...args) does, written as a function.
// This version runs the constructor on a plain object, so the result inherits nothing from Constructor.prototype.

export function myNew(Constructor, ...args) {
  const obj = {};
  Constructor.apply(obj, args);
  return obj;
}
The sample tests · my-new.test.mjs
import { test, assert } from 'mysmartcopilot:test';
import { myNew } from './my-new.mjs';

function Ticket(from, to) {
  this.route = `${from}-${to}`;
}
Ticket.prototype.label = function () {
  return `Ticket ${this.route}`;
};

test('the result is linked to Constructor.prototype', () => {
  const ticket = myNew(Ticket, 'Pune', 'Goa');
  assert.equal(Object.getPrototypeOf(ticket), Ticket.prototype);
  assert.ok(ticket instanceof Ticket);
  assert.equal(ticket.label(), 'Ticket Pune-Goa');
});

test('the constructor runs once, with this and every argument', () => {
  let calls = 0;
  let seen;
  function Probe(...args) {
    calls += 1;
    seen = { self: this, args };
  }
  const result = myNew(Probe, 1, 'two', [3]);
  assert.equal(calls, 1);
  assert.equal(seen.self, result);
  assert.deepEqual(seen.args, [1, 'two', [3]]);
});

test('an object returned by the constructor replaces the new one', () => {
  const shared = { kind: 'cached' };
  function Cached() {
    this.kind = 'new';
    return shared;
  }
  assert.equal(myNew(Cached), shared);
});

test('a primitive or null returned by the constructor is ignored', () => {
  function GivesNumber() {
    this.kind = 'new';
    return 42;
  }
  function GivesNull() {
    this.kind = 'new';
    return null;
  }
  assert.equal(myNew(GivesNumber).kind, 'new');
  assert.equal(myNew(GivesNull).kind, 'new');
});

test('something that is not a function throws a TypeError', () => {
  assert.throws(() => myNew({}), TypeError);
  assert.throws(() => myNew('Ticket'), TypeError);
});
A hint

Make the object with Object.create(proto), where proto is Constructor.prototype when that is an object or a function and Object.prototype otherwise. Run the constructor with Constructor.apply(obj, args) and keep what it returns. Remember that typeof null is 'object': check for null before you decide that the constructor returned an object.

The sample tests run on this device, in your browser (QuickJS): nothing is sent to mysmartcopilot.com. The first run downloads JavaScript (about 0.6 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.

Self-check

Check yourself

5 questions about this lesson. Every answer and why it is right is on the page, behind “Show the answer”. Your score stays in this browser.

  1. Question 1 of 5 What does this program print?

    Read the code, then choose one answer.

    function A() {
      this.size = 1;
      return { size: 2 };
    }
    function B() {
      this.size = 1;
      return 2;
    }
    console.log(new A().size, new B().size);
    Show the answer to question 1

    Answer: 2 1

    The last step of new returns the new object unless the constructor returned an object of its own. A returned an object, which replaces the new one; B returned a number, which new ignores.

  2. Question 2 of 5 A module (strict code) contains function Point(x) { this.x = x; } and then const p = Point(3);. What happens?

    Choose one answer.

    Show the answer to question 2

    Answer: A TypeError is thrown, because this is undefined in the plain call

    Module code is strict, so a plain call passes undefined as this, and this.x = x throws a TypeError. A global x would appear only in sloppy code, where this is the global object.

  3. Question 3 of 5 Which of these can be called with new? Choose every one.

    Choose every answer that is right.

    Show the answer to question 3

    Answer:

    • A function made by Fare.bind(null), where Fare is a function declaration
    • function Fare(km) { this.km = km; }
    • class Fare {}

    Function declarations and expressions, classes and bound copies of them have a [[Construct]] method. Arrow functions and shorthand methods do not, so new with them throws TypeError: … is not a constructor.

  4. Question 4 of 5 What does replaced-prototype.mjs print?

    What does this program print? Choose one answer.

    // Old code often replaced a constructor's whole prototype with an object literal. Two things change.
    function Parcel(weightKg) {
      this.weightKg = weightKg;
    }
    const early = new Parcel(2); // made while Parcel.prototype is still the original object
    
    Parcel.prototype = {
      charge() {
        return 40 + 15 * Math.ceil(this.weightKg);
      },
    };
    const late = new Parcel(3.2);
    
    console.log('late.charge():', late.charge());
    // 1. The literal has no constructor property, so the one inherited from Object.prototype shows through.
    console.log('late.constructor === Parcel:', late.constructor === Parcel, '| it is', late.constructor.name);
    // 2. Objects made earlier keep the old prototype: they lost the method, and instanceof now says no.
    console.log('typeof early.charge:', typeof early.charge, '| early instanceof Parcel:', early instanceof Parcel);
    
    // The usual repair in such code: put constructor back, as a non-enumerable property like the original one.
    Object.defineProperty(Parcel.prototype, 'constructor', { value: Parcel, writable: true, configurable: true });
    console.log('after the repair:', new Parcel(1).constructor === Parcel);
    Show the answer to question 4

    Answer: it prints

    late.charge(): 100
    late.constructor === Parcel: false | it is Object
    typeof early.charge: undefined | early instanceof Parcel: false
    after the repair: true

    The object literal that replaced Parcel.prototype has no constructor, so late.constructor is the one inherited from Object.prototype. early was made before the replacement and still points to the old prototype, which has no charge, and instanceof looks for the current Parcel.prototype, so it says false.

  5. Question 5 of 5 Why do constructor functions put methods on F.prototype instead of creating them with this.method = function () {…}?

    Choose one answer.

    Show the answer to question 5

    Answer: One function on the prototype serves every object, while a function made in the constructor is a new object for every instance

    A method on the prototype exists once and is found through the prototype chain, with this set to the object it was called on. Creating it in the constructor works too, but it makes new function objects for every instance; the lesson's run counted 20,000 of them for 10,000 accounts.

Interview questions

Fresher · What does the new operator do?

new F(...args) performs four steps. It creates a new, empty object; it sets that object’s prototype to F.prototype, or to Object.prototype when F.prototype is not an object; it calls F with this bound to the new object and with the arguments; and it returns the new object, unless F returned an object of its own, which then replaces it, while a primitive return value is ignored. Because of the second step, methods stored on F.prototype are shared by every object that new F() creates, and obj instanceof F is true. Only constructors can be used with new: ordinary functions and classes can, while arrow functions, methods, async functions and generators throw a TypeError. Inside the function, new.target is F; in a plain call it is undefined.

Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.

  1. Fresher to mid

    A classic script on a shop page defines function Tracker(name) { this.name = name; this.events = []; }. One call site reads const t = Tracker('checkout'). That line throws nothing, but the next, t.events.push(e), throws a TypeError, and window.name is now 'checkout'. Explain both symptoms, fix the code and stop it happening again.

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  2. Mid to senior

    A data grid makes one Row object per record with a constructor that defines six methods inside its body, such as this.format = function () { … }. With 200,000 records, a heap snapshot shows about 1.2 million function objects, and the tab uses far more memory than the data needs. Explain why, estimate the cost, and fix it.

    Model answer, Pro. First step and follow-ups

Key takeaways

  • new F(args) creates an object, links it to F.prototype, runs F with this set to it, and returns it, unless F returns another object. A primitive return value is ignored.
  • A function declared with function has a prototype object with a constructor property; methods stored there are shared by every instance, while methods made inside the constructor cost new functions for every object.
  • Pre-class code assigns methods to F.prototype (enumerable), adds helpers as properties of F, and sometimes replaces F.prototype, which loses constructor and breaks instanceof for objects made earlier.
  • Without new, a constructor in sloppy code writes to the global object; in strict code it throws a TypeError, and a class always throws. new.target and capitalised names help catch the mistake.
  • Arrow functions, shorthand methods, async functions and generators cannot be called with new. Built-ins such as Date and String give different results with and without it; avoid wrapper objects.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.