The simple answer is: Because Java and TypeScript are different languages, thus they can behave differently. However, I myself like to understand why language constructs are how they are. So, let's take a dive!
To start of, we need to know that TypeScript is a superset of JavaScript. In essence, every valid JavaScript program is also a valid TypeScript program. In fact, TypeScript gets transpiled to JavaScript, so in the end, everything is JavaScript. What TypeScript does provide, however, is compile-time type checking.
Next point to notice is that both JavaScript and TypeScript are duck-typed languages. The duck-typing is, however, in both instances, different. Let us first look at a JavaScript example:
var person = {
name: 'John Doe'
};
console.log(person.age);
In the code above, the property of age
of person
is accessed. But this property does not exist. For this, JavaScript has the undefined
value and this is what is returned. This is an example of the extremely typing in JavaScript.
Now TypeScript gives additional type-checking, making it more Java-like with respect to typing. Let's consider the following TypeScript code:
interface Person {
name: string
age: number
}
function printPerson(person: Person) {
console.log(person);
}
class PersonClass {
name: string
age: number
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
}
const personObject = new PersonClass('Jane Doe', 23);
printPerson(personObject);
const person = {
name: 'John Doe',
age: 42
};
printPerson(person);
const notAPerson = {
name: 'Oops'
}
printPerson(notAPerson);
Playground example
First, there is an interface Person
defined. It also enforces that only object having a string
-attribute name
and number
-attribute age
are considered to be Person
s.
Next, we define a method to print Person
s.
Then, we define a class PersonObject
with a name
- and an age
-attribute. Notice that we do not reference the Person
-interface in the PersonObject
-class.
Now when we instantiate a PersonObject
, we can pass is to the printPerson(...)
-class since every PersonObject
conforms to the Person
-interface and gets converted.
We can do the same with inline-declared objects (const person = { name: 'John Doe', age: 42 };
): it also conforms to the interface and thus can be passed to printPerson(...)
.
Finally, we have an inline-object having a name
, but no age
(const notAPerson = { name: 'Oops' }
) and thus does not satisfy the Person
-interface. The transpiler will complain. Notice, however, that the transpiler may still produce JavaScript code (depending on the transpiler settings) and the produced JavaScript code will execute the "faulty" call since there is no possibility in JavaScript to prevent the call.
Edit:
Thanks to @kaya3 who pointed out the following to me.
TypeScript's typing system is based on structural typing. It is based on compile-time type checking, not run-time type checking (which is how duck typing does it).