I'm going to switch from String
to RegExp
because there is a wrinkle with String
I will mention later*.
An instance interface (like RegExp
and JQuery
) usually represents an object type where multiple different instances can exist. An associated static interface (like RegExpConstructor
and JQueryStatic
) usually represents the type of object that creates or returns these instances; and often there is only one of these static objects in existence. So there is just one RegExpConstructor
object that can make many RegExp
objects, and just one JQueryStatic
object that can make many JQuery
objects.
One common source of confusion in practice is name collision between values and types. The name of the single static object (e.g., RegExp
or jQuery
) tends to be the same as the name of the instance interface. But the type of that static object is not the type of the instance interface. So, the value named RegExp
at runtime has the type RegExpConstructor
, not RegExp
. And the value named jQuery
at runtime has the type JQueryStatic
, not JQuery
. This is confusing but is probably for the best, since it lets you say things at runtime like x instanceof Y
and at compile time that the type of x
is Y
.
Anyway, if there is a property or method whose behavior depends on a particular instance, that usually sits on the instance interface. If there is some property or method whose behavior does not depend on a particular instance, that usually sits on the static interface.
A constructor interface is a static interface that specifically allows you to use the new
operator on it to make new instances. In TypeScript this is represented similarly to a function call signature, but with the name new
, like this:
type F = (x: string) => number[];
type C = new(x: string) => number[];
The type F
represents a function which takes a string
parameter and produces an array of number
s, while the type C
represents a constructor function which takes a string
parameter and produces an array of number
s:
declare const f: F;
declare const c: C;
const arr1 = f("hey"); // number[]
const oops1 = new f("hey"); // error, f is not newable
const arr2 = new c("hey"); // number[]
const oops2 = c("hey"); // error, c is not callable
Having a static interface that is also a constructor interface is common; all class
static interfaces are constructor interfaces. But not every static interface is a constructor interface. The JQueryStatic
interface is an example of one which is not. To get a JQuery
instance from a JQueryStatic
object, you call it like a function (that's the what the (query: string): JQuery;
signature means).
So that's the main difference between the JQueryStatic
/JQuery
pair and the RegExpConstructor
/RegExp
pair, and the end of the main answer to the question.
*Back to the String
wrinkle. The type named String
specifically refers to an object constructed by calling the new
operator on the String
constructor. There is also the type named string
(with a lowercase 's') which refers to the primitive data type. Virtually all of the strings you deal with are such primitives of type string
, whereas a String
is a relatively uncommon wrapper object which holds a string
value. A string
and a String
are mostly interchangeable:
const stringPrimitive = "hello"; // type is string
const stringObject = new String("hello"); // type is String
console.log(stringPrimitive+"!"); // "hello!"
console.log(stringObject+"!"); // "hello!"
console.log(stringPrimitive.charAt(4)); // "o"
console.log(stringObject.charAt(4)); // "o"
except when they're not interchangeable:
console.log(typeof stringPrimitive); // "string"
console.log(typeof stringObject); // "object"
console.log(stringPrimitive instanceof String); // false
console.log(stringObject instanceof String); // true
The situation gets even more muddled when you realize that the StringConstructor
can also be called like a function and produces a primitive string
:
console.log(typeof "hey"); // "string"
console.log(typeof new String("hey")); // "object"
console.log(typeof String("hey")); // "string"
So it's quite a mess. The rule here is pretty much always use string
; never use String
. And that's why I changed the code example away from String
to the always-an-object RegExp
, for which there is no primitive data type (typeof /foo/ === "object"
) to get in the way.
Okay, hope that helps. Good luck!