0

TypeScript has two string interfaces called String and StringConstructor.

Also, the TypeScript language specification provides this code example, in chapter 1, section 1.3:

interface JQuery {
  text(content: string);
}

interface JQueryStatic {
  get(url: string, callback: (data: string) => any);
  (query: string): JQuery;
}

What's the difference between the JQuery/JQueryStatic and the String/StringConstructor interfaces and do Static and Constructor interfaces related?

Note: yes I'm aware that the page is ~6-7 years old outdated.

Malekai
  • 4,765
  • 5
  • 25
  • 60

1 Answers1

1

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 numbers, while the type C represents a constructor function which takes a string parameter and produces an array of numbers:

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!

jcalz
  • 264,269
  • 27
  • 359
  • 360