Type safety is a complex topic and there's no one agreed definition of what exactly a "type-safe" language is. But by almost any definition of it, no, JavaScript is not type-safe. :-) In that particular example, though, JavaScript did provide runtime type safety: It didn't actually try to call i
and cause some kind of memory access exception or similar; instead, when your code tried to call it, the first thing the JavaScript engine did was check to see if it was callable and, since it isn't, it raised a protective error.
But a type-safe language tries to discourage or prevent errors or undesireable behavior due to using an incorrect type, through type enforcement (both at the compilation/parsing stage and when the code runs). JavaScript mostly doesn't do that (the above notwithstanding); in general, JavaScript tends to coerce instead.
For instance, in a type-safe language, this would probably fail:
console.log("hi there" * 4);
...assuming *
isn't a defined operator for strings. (I believe there's at least one language where it is and that would result in "hi therehi therehi therehi there"
).
But in JavaScript, *
doesn't have a defined meaning for strings. But rather than causing an error (at the compilation/parsing stage or when run), the string is implicitly converted to a number n, and then used in the expression n * 4
. In the case of the string "hi there"
, the coercion results in the value NaN
("not a number") rather than causing an error (and then NaN * 4
also results in NaN
).
Type-safe languages also typically (though I don't think always) have typed variables/parameters/properties and similar and do at least some type checking at compilation/parsing stage rather than when the relevant code runs. In those languages, i
would have had a type associated with it (e.g., int i
rather than var i
), and the code trying to call it as a function would have failed at the compilation/parsing stage, rather than later when it was run as it does in JavaScript. JavaScript, on the other hand, doesn't have typed variables/parameters/properties at all. A variable can hold an object one moment and a primitive number the next.
One of the benefits of that is that JavaScript is friendly to duck-typing (if it looks like a duck and quacks like a duck, it's a duck). For instance, suppose you have a function that, notionally, requires a string:
function capitalize(str) {
return str.charAt(0).toUpperCase() + str.substring(1);
}
In JavaScript, the following code calling it:
capitalize(42);
is perfectly correct and will not raise any error when the code containing that call is compiled/parsed. But it will raise an error when the code is called — not because 42 isn't a string (it isn't, but that's not the point), but because 42 doesn't have a charAt
method.
In language with static type safety (e.g., compilation/parsing stage type safety), there'd be type information associated with the str
argument and the error would be when the code was compiled/parsed.
But in JavaScript, not only is it happy to compile/parse that code, but it's happy to run it on a non-string provided whatever you give it meets these criteria:
It has a charAt
method that returns something with a toUpperCase
method, and
It has a substring
method.
As long as you give it something meeting those criteria, whether that thing is a string or not, it'll work.
function capitalize(str) {
return str.charAt(0).toUpperCase() + str.substring(1);
}
var thingy = {
charAt: function() {
return {
toUpperCase: function() {
return 40;
}
}
},
substring: function() {
return 2;
}
};
console.log(capitalize(thingy)); // 42
;-)