Hi everyone, I'm Kelvyn. Yesterday I did a deep dive regarding the
constructor signature side. Today we will go through this topic to
clarify some questions related to:
1/ What is the diff between new (constructor signature) vs executing a function (call signature)?
2/ Even if we know these definitions, how can we declare their typings in TypeScript?
3/ Even if we understand the diff and know how to declare typings following the docs, why do we have the new keyword? What is an instance? Why do primitives and built-in objects / object prototypes exist?
Great! Let's start.
Prerequisite
If you still haven't read about call constructors, you can quickly
review it here:\
👉 https://dev.to/kelvynthai/build-a-simple-mini-dayjs-clone-logger-middleware-by-call-signatures-in-typescript-8pj
Well, sometimes you might see code like this in your codebase:
new Boolean();
new Date();
new Array();
new Promise(() => {});
We can call these built-in objects in JavaScript, and they are
supported by the runtime environment. But what is really happening
behind the scenes? How do we have them? How do we declare their typings?
1/ What is the diff between new (constructor signature) vs executing a function (call signature)?
It's quite simple --- with a constructor signature, we must use the
new keyword. Too easy!
Such as:
new Date();
new Promise(() => {});
new Number();
new Error();
How about the call signature (you can review the call constructor link)?
Examples include dayjs() or logger().
One is executed as a function, the other is used with new.
Sounds good?
2/ How can we declare their typings in TypeScript?
type ConstructDate = {
new (dateStr: string): string;
};
const main = (constructDate: ConstructDate) => {
const myConstructDate = new constructDate("Sat 7 Feb 2026");
};
Following this way, we can easily declare typings for a constructor
signature. Too easy, right?
Yeah --- are we done? Can we close the laptop and go to sleep?
But if you're like me and don't understand how to mix call and
constructor signatures, calm down and continue scrolling 🙂
I spent a lot of time reading this in TypeScript, and to be honest, it
was quite confusing. From the call signature, we have clear examples of
how to define it. But with constructors, the docs usually only simplify
the declaration of typings and don't give us a method to implement them.
So let me guide you.
Let's try printing this:
console.log(typeof String); // function
console.log(typeof String.prototype); // object
console.log(typeof new String()); // object
As you can see, the root cause is that using the new keyword returns
an object, not a primitive value. But why is that?
*** Why does new return an object instead of a primitive? ***
Because the purpose of new is to create an instance.
And an instance must be an object so it can:
• hold properties
• inherit methods
• link to a prototype
• support polymorphism
The instanceof operator tests to see if the prototype property of a constructor appears anywhere in the prototype chain of an object. The return value is a boolean value. Its behavior can be customized with Symbol.hasInstance.
A primitive cannot do these things.
So this is not accidental — it is by language design.
Learn more at:
- Object prototypes: https://developer.mozilla.org/en-US/docs/Learn_web_development/Extensions/Advanced_JavaScript_objects/Object_prototypes
- Instance https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/instanceof
Prototypes are the mechanism by which JavaScript objects inherit features from one another. In this article, we explain what a prototype is, how prototype chains work, and how a prototype for an object can be set.
In JavaScript, we have 3 ways to implement prototype inheritance:
object, constructor, and class. Among these, classes are very
straightforward.
For example:
class Animal {
eat() {
console.log("nhoammmm");
}
}
class Human extends Animal {
work() {
console.log("Working and earn money!");
}
}
const kelvynThai = new Human();
console.log(typeof Human, typeof Human.prototype); // function, object
console.log(kelvynThai instanceof Human); // true
console.log(kelvynThai instanceof Animal); // true
console.log(Object.getPrototypeOf(kelvynThai) === Human.prototype); // true
console.log(Object.getPrototypeOf(Human.prototype) === Animal.prototype); // true
console.log(typeof kelvynThai); // object
kelvynThai.work(); // Working and earn money!
kelvynThai.eat(); // Nhoammmm
Now let's move to constructor inheritance:
function Animal() {}
Animal.prototype.eat = function () {
console.log("Nhoammmm");
};
function inheritPrototype(proto: unknown) {
function ChainLink() {}
ChainLink.prototype = proto;
return new ChainLink();
}
function Human() {}
Human.prototype = inheritPrototype(Animal.prototype);
Human.prototype.work = function () {
console.log("Coding and earning money!");
};
const kelvynThai = new Human();
console.log(typeof Human, typeof Human.prototype); // function, object
console.log(kelvynThai instanceof Human); // true
console.log(typeof kelvynThai); // object
console.log(Object.getPrototypeOf(kelvynThai) === Human.prototype); // true
console.log(Object.getPrototypeOf(Human.prototype) === Animal.prototype); // true
kelvynThai.work(); // Coding and earning money!
kelvynThai.eat(); // Nhoammmm
That means our ConstructDate type should be adjusted from the
primitive 'string' → built-in object String.
type MyDate = {
// eslint-disable-next-line
new (dateStr: string): String; // constructor -> object
(dateNum?: number): string; // call -> primitive
};
const MyDate: MyDate = function (this: unknown, d?: string | number) {
if (new.target) {
// always ensure constructor returns an object
return new String(`PARSED_DATE:${d}`);
}
return !!d ? `TIMESTAMP:${d}` : "DEFAULT_DATE";
// OR: throw new TypeError(`Class constructor ShiftedDate cannot be invoked without 'new'`)
} as MyDate;
const dateWithNew = new MyDate("Sat 7 Feb 2026");
// String object
const dateWithoutNew = MyDate(123456);
// primitive string
console.log(dateWithNew, typeof dateWithNew);
// [String: 'PARSED_DATE:Sat 7 Feb 2026'] object
console.log(dateWithoutNew, typeof dateWithoutNew);
// TIMESTAMP:123456 string
console.log(new Date(), typeof new Date());
// 2026-02-07T13:58:57.212Z object
Conclusion
Understanding the difference between constructor signatures and call
signatures helps us model JavaScript behavior more accurately in
TypeScript.
TypeScript constructor typings become much easier to reason about.
This knowledge is especially useful when:
- Reading built-in APIs\
- Designing flexible libraries\
- Modeling function behaviors in TypeScript\
- Understanding how JavaScript works under the hood
Hope this helps clarify the differences. Happy coding 🚀
Top comments (0)