JavaScript's Type System
00:00 In the previous lesson, I gave you a quick tour of JavaScript inside of the web browser. In this lesson, I’ll begin the deeper journey into JavaScript by discussing how its type system works.
00:12 Different programming languages deal with the declaration, management, and manipulation of data differently. The design of a language determines what it means to be an integer and what happens when integers interact with floats or strings.
00:25 There are several different ways of thinking about data typing with some subtle differences. One of the clearest distinctions is between dynamic typing and static typing.
00:35 Dynamic typing means the type of the data is decided at runtime, whereas static typing means it is determined at compile time. Dynamic typing is a bit more flexible, whereas static typing allows the compiler to catch problems that you would not discover until running a dynamically-typed program.
00:53 Your preference between these often dictates which is your favorite programming language. Gradual typing, or type hinting, is a compromise between these ideas.
01:03 It was introduced by TypeScript and was added in Python 3. Type hinting is a mechanism for annotating a piece of data with a type but then using a separate tool to determine compliance. Your IDE or a code checker like mypy can find the same kinds of problems that static typing would if you’ve been disciplined enough to annotate. This compromise allows you to get the benefits of static typing without mandating it and tying your hands when you want the more dynamic features of your language.
01:33 A second way of distinguishing between languages is how the data type is determined. In static typing languages, you usually need to explicitly declare what data type something is. In dynamic languages, you may or may not have to do this. Both Python and JavaScript infer the data type of a variable by examining what is first assigned to it.
01:54 Another distinction between the management of data types in a language is the rules around converting between types. This conversion is called typecasting. Python and JavaScript are both dynamically typed languages and they both support duck typing. Duck typing is a subset of dynamic typing where the methods and attributes of an object are determined by whether or not they have that method or attribute. In static typed languages, the compiler will often prevent you from using an object in a different context, or at least make you do something special to convert it. Under duck typing, the calling context is unimportant.
02:31 The language tries to call the method or access the parameter and raises an error if it isn’t there. The term duck typing comes from the old saying “If it walks like a duck and talks like a duck, it must be a duck.” Neither Python nor JavaScript check the calling context before calling a method on an object. They just try it and then error out if the object has no such method.
02:55 Although both Python and JavaScript are dynamically typed languages that support duck typing, their typecasting mechanisms are different. Python is much more strict than JavaScript when it comes to typecasting.
03:07 If you try to add a string and an integer in Python, you will get an error. JavaScript is less strict and will attempt to convert from one to the other.
03:16 This can be a feature or a foot gun. Some of JavaScript’s automatic conversion behaviors can be quite surprising to programmers coming from other languages.
03:25 If you feel like starting a good flame war on the web, log into a coding forum and declare your favorite language as strongly typed. There isn’t a well agreed upon definition of what strongly typed means, and everyone brings their own bias to such a conversation. Flame war or no, Python’s typing system is definitely more strict than JavaScript due to the nature of the automatic typecasting.
03:49 Let’s look at the typing system in practice. On the left-hand side, I will show Python, and on the right-hand side, JavaScript in a Node.js session. First, the familiar declaration of an integer.
04:10 This is a string with a type hint,
04:18 which is only metadata and doesn’t impact the string itself. Now, on the right-hand side, some JavaScript. Here’s an integer.
04:29 Notice that inside of Node.js, it automatically shows you the result of the declaration. This is different from the Python REPL on the left, where I had to explicitly show you the contents of data. Here’s a string.
04:46 So far, so good. Now back on the Python side, I’ll do something that doesn’t make sense.
04:54
You can’t add a string and a number, and Python tells you so with an exception. If you’re trying to get the 3
out of the string, you need to convert it to an actual integer, then you can do the addition.
05:08
Or, if you were trying to go the other way, you can convert the 2
to a string, and then addition means string concatenation.
05:18
With me so far? Well, this isn’t how JavaScript does it at all. Back on the right-hand side, same edition. Funny math, huh? JavaScript is automatically typecasting the 2
into a string because the line begins with a string. If you’re not expecting this, this can be a bit disconcerting. It can also be a tricky source of bugs.
05:41 The typecasting is based on the first item and continues to typecast in order.
05:48
The 3
and the 2
here don’t get added. They each get cast to a string and then concatenated. Not only can this be surprising, but it isn’t terribly consistent either.
06:01
The plus sign (+
) means both addition and string concatenation. By contrast, the minus sign (-
) only means subtraction. Even though this line begins with a string, because minus always means math, JavaScript automatically typecasts the '3'
into an integer and does the math for you.
06:20 I’ve been telling a little white lie. I’ve been calling these numbers “integers.” JavaScript actually doesn’t have the concept of an integer. Let me show you what it does have.
06:30 Everything in Python is an object. Not so in JavaScript. JavaScript has two kinds of types: primitive and reference. Primitive types are stored differently and passed differently than reference types.
06:43 Primitive types are immutable, similar to strings in Python. They don’t have methods or attributes, but that’s a technicality that I’ll come back to in a second.
06:53
There are six primitive types: boolean
. null
, which is kind of like None
in Python. number
—this is the lie I told you before. JavaScript doesn’t distinguish between integers and floats.
07:05
Everything is a number, which is stored the same way as a float in Python. This can cause issues as floats are inexact. I’ll talk more about that in a later lesson. string
.
07:17
symbol
. This is an odd little meta-object. Each one of these you create is guaranteed unique. You can associate a name with them, but two symbols with the same name are still considered different.
07:27
They can be used as keys inside of JavaScript objects. And finally, undefined
, which is also kind of like None
in Python. Yes, there are two ways of saying “empty.” I’ll talk more about this in a later lesson as well.
07:43
Reference types are closer in nature to objects in Python. Reference types do have methods and attributes. The built-in reference types are: Array
, Boolean
, Date
, Map
, Number
, Object
, RegEx
, Set
, String
, Symbol
, and BigInt
.
08:06 This was introduced in ES11 to get around those pesky “everything is a float” problems I talked about earlier. Notice that many of these are object versions of the primitives.
08:16 This is similar to Java, and there’s a concept called autoboxing that will automatically change primitives into their object equivalents in certain situations.
08:25 When I said earlier that primitive types don’t have methods or attributes, that’s true but you can use methods and attributes on them anyway and JavaScript will automatically change your instance into the reference type equivalent before accessing the value.
08:40 Let’s take a look at some of these in the console.
08:45
Here’s a string. Let me do that again, this time accessing the .length
attribute that it technically doesn’t have.
08:56
JavaScript has autoboxed this string
primitive into a String
reference type and accessed the attribute all automatically. When I asked for the .length
attribute, JavaScript acted as if I had done the following.
09:13
The new
keyword here creates a new instance of the object. This is done by calling the object’s constructor. A constructor constructs a new instance of the object. Primitive types are stored on the calling stack.
09:26 Reference types are stored in the memory heap. This means that autoboxing is actually causing the underlying data to be moved in memory. Where the real difference between primitive and reference types is shown is how they are passed around. Let me create a primitive.
09:43
Now I will create another variable pointing at the first. What happens when I change the first item? The ++
is a shortcut for +=1
.
09:54
Notice the value of x
and the value of y
. When you refer to a primitive type like I did when I created y
, you get a copy of the value.
10:05
Because y
is a copy, changing x
doesn’t change y
. Like Python strings, each of these are an immutable object. Technically, I didn’t change the value of x
but replaced it with a new value. For numbers, this is no big deal, but like strings in Python, if you do an operation on a string in JavaScript, the whole thing is replaced with a new copy.
10:27
This can be expensive in memory and speed if you’re not careful. So, that’s primitive types. Now let’s look at how reference types are different. Here, I will create an object with an attribute called name
.
10:43 Objects in JavaScript are like a hybrid between Python dictionaries and Python objects. I’ll get into the details in a later lesson. For now, just think of it like a dictionary.
10:54
Like before, I’m going to create a variable called y
pointing to x
. And here’s the value of .name
on x
and on y
.
11:05
Now let me change the value of the .name
attribute on x
.
11:12
You can see it’s changed. As objects are reference types, they are passed as references—hence the name. This is distinct from how primitives behave. In this case, y
pointed to x
.
11:25
It didn’t get a copy. Therefore, when I modified x
, y
is still pointing to the same thing, which in this case is the modified x
.
11:35 If you dig deep into Python, it actually does something similar. Although everything in Python is an object, it does treat immutable objects different from mutable ones.
11:45
If you do the x
, y
thing I just did before on a string in Python, you will find that the underlying variables point to different parts of memory.
11:53
You could sort of say that Python has auto-un-boxing. If you want to know whether you’re working with a primitive type or a reference type, you can use the typeof
keyword.
12:06 Primitive types return a string with the type of the primitive inside of it.
12:15
Reference types all return a string with the word 'object'
inside of it. You can also dig a bit more information out of an object. Here’s a new Date
object.
12:33
The .name
property of the .constructor
property tells you what the object is. This is kind of like .__class__.__name__
in Python.
12:45
JavaScript also has an instanceof
keyword, and kind of like the .__class__
idea in Python is the .prototype
property.
12:59
This isn’t exactly the same, and I’ll go into more details in a future lesson on classes, but it’s a close enough analogy for now. The .isPrototype()
method on the .prototype
property will tell you whether the object in question inherits from a given parent.
Become a Member to join the conversation.