I very much concur that it is better NOT to introduce TS when explaining JS fundamentals. I've seen smart engineers with a C++ background get tripped up on and very confused working with TS, because it's not clear to them what concepts are "language fundamentals" and what concepts are "the TS transpiler".
(Like expecting that just because you declared something as type X, that it guarantees at runtime it will always be type X, but it won't. You may get data from an API that _says_ it returns type X but the contents don't match. That can be valid code that compiles and has weird runtime behavior)
> I've seen smart engineers with a C++ background get tripped up on and very confused working with TS, because it's not clear to them what concepts are "language fundamentals" and what concepts are "the TS transpiler".
This is exactly why I never recommend TypeScript to new developers.
A similar problem (that used to be worse) with people learning JavaScript is the lack of separation between JavaScript and the DOM + browser APIs. 10+ years ago, people have told me how much they hated JavaScript and when probed about it further, would admit that it’s actually the DOM or new/inconsistent browser APIs that have caused issued.
JS has a number of its own flaws and quirks, yes, but there are two fundamental issues that make it harder to approach as a new learner (as opposed to, to say, Python) are how tightly coupled it has historically been to it’s primarily application case and how high or low level is this language?
What helped me understand the Runtime behaviour of TS is to understand that TS doesn't actually have strong typing. If it was named "LintScript", its name would be much closer to the truth.
This statement is quite questionable starting from the fact that there is no definition for what strong typing is.
Care to provide what you mean?
Strict TS won't compile pretty much any type-unsafe operation.
It's not perfect, the standard libraries are a bit too unsafe type wise, exceptions are untyped and need Either/Result-like data types to be handled but it's an extremely powerful language if you know it and it's ecosystem.
Most people though don't even bother reading the docs and can't tell you what a mapped type is or what is a disjointed union, etc.
If you are in a completely self-defined world, then yes you can trust the compiler. But I once built a react component which was called from library code and it simply did not adhere to the method signature (this was for a material UI table extension).
As soon as you have third party code calling your methods all bets are off. It could be JS calling your methods, or there simply is a hidden cast somewhere.
This is the moment that you realize that type annotations really are just a compile-time fiction, much like Python. At least in my definition, this is weak typing as the variable is physically capable of changing to any type at runtime, despite its type annotations.
TBF, this can also be true in C++, C#, probably most languages that can interoperate with other systems not written 100% in the same language + same runtime. After a while, you just get used to not trusting anything at the boundary - types are for your convenience and your internal logic, nothing more.
You are talking about something entirely different. Having weird interop is one thing. But having your internal state compromised because the language does not perform runtime checks is something entirely different.
In case of C++ this would lead to desastrous memory corruption. If all data is dynamic you can't have a safe program as data on the stack must be monomorphic or you corrupt nearby memory.
Naturally, you can defend against hostile input via excessive defensive programming (asserting against nulls, asserting against wrong types etc.). Or you simply use a strong static typed language.
Yes, that's why I used C++ as an example. It's very easy to create a scenario where the memory layout your logic (and your type-checker) assume exists just... doesn't. Core dumps from the field with "impossible" values, vtables missing entries, etc.
Do you mean runtime type checking? Coming from compiled type safe languages, it was difficult to wrap my head around full type erasure too. Your name would have definitely helped.
> I've seen smart engineers with a C++ background get tripped up on and very confused working with TS,
The idea that someone is good at another language so they'll automatically be good at another is a common misconception. In fact, they're likely to be worse because they're less likely to spend time trying to learn things from the ground up and less likely to write idiomatic code.
It's especially bad with js/ts because of it's popularity (so lots of new programmers that complain about how NaN doesn't equal itself), and because it's the defacto web language so lots of people are forced to use it as a secondary language that don't want to spend time learning it.
I wholeheartedly agree. At most, I introduce JSDoc[1] to newer developers as standardising how parameters and whatnot are commented at least gets you better documentation and some safety without adding any TS knowledge overhead.
It is (or should be) common practice to parse/validate any external data you depend on. You should be doing this for Javascript too. I find that the library https://zod.dev/ is quite helpful for this
(Like expecting that just because you declared something as type X, that it guarantees at runtime it will always be type X, but it won't. You may get data from an API that _says_ it returns type X but the contents don't match. That can be valid code that compiles and has weird runtime behavior)