Now lets observe this piece of code:
let whatIsMyType = identity(666);
typeof whatIsMyType; // number
I have not lost type information. What kind of loss they refer to?
It's about the difference between static type information and runtime type information. TypeScript is all about static type information — the information available to the TypeScript compiler to help you keep your program code correct. But typeof (in that context¹) is a JavaScript runtime operation telling you (in very general terms) what kind of value is stored in the variable whatIsMyType at runtime as of when you use typeof on it.
If you paste that code into the playground and hover your mouse over the whatIsMyType variable, you'll see that it's TypeScript type is any, which means you can happily assign "foo" (or anything else) to it after the call to identity:
function identity(arg: any): any {
return arg;
}
let whatIsMyType = identity(666);
console.log(whatIsMyType);
whatIsMyType = "foo"; // No error here
console.log(whatIsMyType);
Because its type is any, the variable is loosely typed like JavaScript variables are.
TypeScript's goal, its raison d'être, is static (compile-time) type checking. The any type is an escape hatch for those situations where static type checking isn't useful (or even possible because of third-party integrations). (And those situations do exist, but are rare.)
once compiled to plain JS, they look both identical:
That's right. The static type information that TypeScript uses is not part of the final output. It's a compile-time thing, not a runtime thing. (Enums have a small runtime presence.) But notice how, at compile-time, using the generic changes things — if you run this code in the playground:
function identity<T>(arg: T): T {
return arg;
}
let whatIsMyType = identity(666);
console.log(whatIsMyType);
whatIsMyType = "foo"; // <== Error: Type '"foo"' is not assignable to type 'number'.
console.log(whatIsMyType);
...you can see that assigning "foo" to whatIsMyType is now a compile-time error.
¹ "..typeof (in that context)..." Somewhat confusingly, there is also a TypeScript typeof operator that you can use in a type context, which is different from JavaScript's runtime typeof keyword (used in your example) which is used in a value context. In TypeScript, there are places a type is expected (a type context), such as after the : in a variable declaration:
let a: type_expected_here;
...and other places a value is expected (a value context), such as the right-hand side of an assignment:
a = value_expected_here;
If you use typeof in a type context, it's TypeScript's typeof operator. For instance, if you have this declaration:
let foo: number = 42;
...you can use typeof bar to declare a variable whose type is the same as foo's, like this:
let a: typeof foo;
Since that usage of typeof is where a type is expected, a will have the type number because that's foo's type. If you change foo's declaration so it's a string instead, a's type will change to follow suit.
In contrast:
let a = typeof foo;
There, typeof is in a value context, so it's JavaScript's runtime typeof keyword, and a will get the value (not type) "number" (an in that specific example, a's type will be inferred as string, since that's what JavaScript's typeof's result always is).
That seems very confusing at first, but gets clearer with time.
Note that all of the above is from a TypeScript perspective. Whether you find static type checking a help or a hindrance is up to you. :-) But I'm assuming a TypeScript perspective for the purposes of this answer.