11

I'm reading through the code of the class-validator library and it has the following isInstance method in it:

/**
 * Checks if the value is an instance of the specified object.
 */
isInstance(object: any, targetTypeConstructor: new (...args: any[]) => any) {
    return targetTypeConstructor
        && typeof targetTypeConstructor === "function"
        && object instanceof targetTypeConstructor;
}

Any thoughts on how to go about understanding the type new (...args: any[]) => any? This is the first time I'm seeing this type of construct ...

Nelu
  • 16,644
  • 10
  • 80
  • 88
Ole
  • 41,793
  • 59
  • 191
  • 359

4 Answers4

31

Let's reduce the type to smaller, bite size pieces that are more easily understood and then build back up to the full thing.

First, let's drop the new from our mind and focus on the latter part of the definition:

(...args: any[]) => any

Next let's forget about the arguments for now:

() => any

Hopefully this is familiar as a function that returns type any.

Next we can add back in the args:

(...args: any[]) => any

...args: any[] is using the Rest Parameters construct which essentially says that there can be any number of parameters of the provided type any. Because there are an unknown amount of any parameters, the type of the argument is an array of any.

So hopefully now it makes sense that this is a function that accepts any amount of arguments (of type any) and returns type any.

Finally we can add back the new keyword to get:

new (...args: any[]) => any

The new keyword here specifies that this function can be treated as a class constructor function and called with the new keyword.

This gives us the whole picture that the function is a function that accepts any amount of arguments (of type any) that returns type any and can be used as a constructor function with the new keyword.

When taken in the context of the API, it is essentially allowing you to pass any class constructor to the function.

casieber
  • 7,264
  • 2
  • 20
  • 34
8

Breaking it down into pieces:

new
This keyword in TypeScript specifies what the constructor should look like, for the given property. Nice explanation here: https://stackoverflow.com/a/39623422/1678614.

(...args: any[]) => any
This syntax describe a function type (the constructor is a function).

...
is the ES6 spread operator. It's shorthand for listing out all array elements one by one.

any[]
means that args is an array and it's elements can be of any type.

=> any
specifies the return type of the function. In this case it allows the constructor to return any type.

Nelu
  • 16,644
  • 10
  • 80
  • 88
3
new (...args: any[]) => any

This type specifies a function that accepts any number of arguments that are any type, returns any value, and can be invoked with new.

A constructor is a special type of function that enforces invocation with the new keyword at runtime, but in TypeScript, that can be statically detected, so targetTypeConstructor is specifying an arbitrary constructor as the second parameter to isInstance().

To that end, it seems redundant to check

typeof targetTypeConstructor === "function"

as that's already enforced by targetTypeConstructor : new (...args: any[]) => any at compile-time by TypeScript.

If the passed value is null, it is already invalidated by the targetTypeConstructor && ... conditional, which is necessary at runtime to prevent object instanceof targetTypeConstructor from throwing TypeError in the event that targetTypeConstructor is actually null due to Step 4 in §12.10.4 of the ECMAScript Specification:

12.10.4 Runtime Semantics: InstanceofOperator ( V, target )

  1. If Type(target) is not Object, throw a TypeError exception.
  2. Let instOfHandler be ? GetMethod(target, @@hasInstance).
  3. If instOfHandler is not undefined, then

    a. Return ToBoolean(? Call(instOfHandler, target, « V »)).

  4. If IsCallable(target) is false, throw a TypeError exception.
  5. Return ? OrdinaryHasInstance(target, V).

NOTE

Steps 4 and 5 provide compatibility with previous editions of ECMAScript that did not use a @@hasInstance method to define the instanceof operator semantics. If an object does not define or inherit @@hasInstance it uses the default instanceof semantics.

Patrick Roberts
  • 49,224
  • 10
  • 102
  • 153
  • OK So effectively new (...args: ...) is typescripts way of saying that the function is a constructor function type? – Ole Jun 06 '18 at 17:47
  • 1
    Specifically, the `new (...) => ...` is what indicates a constructor. – Patrick Roberts Jun 06 '18 at 17:48
  • So if I understand correctly the typeof targetTypeConstructor === function is redundant because the last check object instanceOf targetTypeConstructor will be false if the always if hte targetTypeConstructor is not a function ... wonder if it's safer to just keep it in the event that the java runtime for some reason throws an exception in the future ... or in the past for certain runtimes ....? – Ole Jun 06 '18 at 19:04
  • No, it's redundant because TypeScript already enforces that type at compile-time, so the parameter will never be anything but `null` or `typeof === 'function'`. The check should just be `targetTypeConstructor && object instanceof targetTypeConstructor`, the first conditional being necessary to prevent the second one from throwing `TypeError` – Patrick Roberts Jun 06 '18 at 19:12
  • Don't we also have to consider the corresponding javascript runtime version, `function (object, targetTypeConstructor)`, and the runtime cases where perhaps targetTypeConstructor is something other than a function (User error scenarios)? – Ole Jun 06 '18 at 20:37
  • User error will result in a TypeScript compilation error -- it would never reach runtime. – Patrick Roberts Jun 06 '18 at 20:39
  • What happens if we publish the library to NPM, compile for both ES6 and Typescript use cases, and someone uses it for an ES6 library without typescript support? – Ole Jun 06 '18 at 20:44
  • Then they'd have a hell of a time going through the process of setting up a babel environment just to circumvent the use of TypeScript in the library and be able to use decorator functions like this, which have no native support. Simple solution. Don't release ES6 pre-compiled output, and specify that the point of these typings is to avoid the overhead of runtime checks in the first place. – Patrick Roberts Jun 06 '18 at 20:51
  • I agree with you on the recommendation, but try to always make libraries compatible with other runtimes anyways, because I know there are a ton of people (React ...) that will just use straight up ES without any typescript. I love typescript though, and I'm trying my best to stick with it! – Ole Jun 06 '18 at 20:55
1

Means that the parameter targetTypeConstructor is a function that accepts parameters and can be used as a constructor (you can use with new keyword and create instances). You can pass a simple function or a class that are not abstracts.

For more you can check an example in the Typescript Playground

Suren Srapyan
  • 66,568
  • 14
  • 114
  • 112