The underlying object that the runtime creates in memory can have a property or not. If it has a property, that property can have no value (undefined). It can also be null, which is a value.
So, you end up with two different runtime memory representations that when you query an object's property can both result in an "undefined". Cause of this is Javascript's laissez-faire approach to type safety. The implicit conversions (e.g. string+number+anyType works) are one, and not being strict about objects and their properties is another. In a strict language accessing a non-existent property would be an error and not even make it through the compiler.
Somewhat unrelated to the question I responded to:
Typescript is not a different language at all, it really only adds type annotations and the actual language is 100% ECMAscript apart from namespaces (no longer necessary, they are from pre-ES2015 times when JS was lacking features) or enums and maybe some minor class helper stuff nothing at all needs to be transpiled to get from Typescript to ECMAScript. You just need to remove the type annotations. That is why TS ends up with still having issues because it cannot deny the easy-going attitude of the code.
Typescript is not the best architecture: It is a monolith software that supports completely different things in one giant piece of software. We used to have compiler, linker, profiler, code analysis tools, all separate tools.
Typescript in one piece bundles type checking and transpiling in one, and on top of that a quite elaborate and extensive editor support (language service for editors to use). For no good reason, you can not use the transpiling feature at all and only use TS with an IDE to give type advice, and then use Babel to remove the Typescript annotations. Transpiling is really from Javascript to Javascript - working around newer ECMAScript features that a targeted older runtime does not have.
I think Microsoft did a great disservice to their own product by crating this monolith, which unfortunately also created the impression that Typescript is a different language.
If they had it split, and/or made that split clear, that type annotations are just added on top of whatever the latest ECMAScript is, and that transpiling is separate and does not work on a whole new language, and is only necessary when targeting platforms that don't support newer ECMAscript features used in the code, there might be less confusion about what TS is. Flow, the other type system, always made it clear it's a type-annotation add-on. TS is actually not different, they just chose to also bundle the transpiler.
This monolith also makes TS a lot less flexible: If you only had type annotations and checking and transpiling separate you could have more flexibility. You can extend Babel with your own modules easily, e.g. to add a module that removes debug code in a production build. With TS you are limited to using exactly the features they implemented. This is less about the type stuff, more about the environment. For example, I have code that keeps different platform versions at once, and in the build only one is used. I have no way of telling TypeScript that. If the tool was split and as flexible as Babel it would be easy to write a tiny module or plugin.
> For no good reason, you can not use the transpiling feature at all and only use TS with an IDE to give type advice, and then use Babel to remove the Typescript annotations.
Babel has had extensions (including semi-official ones supported by the Typescript team at this point) supporting the removal of Typescript type annotations and there are many projects in the wild today that use Babel entirely for transpilation and Typescript only for language services (IDE).
Relatedly, a bunch of linting that Typescript did but was more generally useful has moved to upstream ESLint, and upstream ESLint also more directly supports Typescript type annotation removal.