I wrote recently about javascript variables and how to create them in code. At the time, I didn't realize there would be a part 2 to that, but it occurred to me that I didn't say much (anything) in that post about the kinds of things you can put in the "containers" you make in javascript, so I wanted to cover that as well. There's basically two kinds of javascript variables: primitives and objects (although technically everything is an object).
Primitives
MDN says primitives are values that are not objects and that have no methods. That's I guess technically correct, although I don't know anyone that thinks of it this way because it feels like most javascript primitives do have methods because javascript has primitive type wrappers that expose methods (e.g. strings have methods like toUpperCase
and split
). It's complicated. More complicated than I want to get into in an introductory level post. Let's just say a primitive is a single, simple value. In javascript there are 6 primitives: null
and undefined
, plus strings, numbers, booleans, and (as of ES6) symbols. I'm going to ignore symbols for this article because their use is a little more advanced. All primitive values other than symbols have a "literal" syntax. Let's look at each one at a time.
null and undefined
Okay . . . mostly one at a time. null
and undefined
are very similar in that they both mean that a variable has no value, but the intent of each is different. undefined
indicates a variable that has never been assigned a value. For example:
let foo;
console.log(foo); // -> undefined
null
, most developers agree, should mean that a variable has explicitly been given no value. Like this:
let foo = 12;
// time passes
foo = null;
I say should because nothing stops you from doing this:
let foo = 12;
// time passes
foo = undefined;
And indeed, you will occasionally see that in code, but it's better to reserve undefined
for uninitialized values and use null
to indicate a variable whose value was explicitly removed.
Oh, and null
is weird because
console.log(typeof null); // -> "object"
and yet
null.toString() // Error! Cannot read property "toString" of null
which is why you'll sometimes see
if (typeof thing === 'object' && thing !== null)
It's bizarre.
booleans
A boolean is a binary variable, meaning it has only two possible values: true
and false
. For example:
let isTrue = true;
let isFalse = false;
There's not much more to say here. Hopefully you understand what true and false mean in common parlance. In programming they indicate the state of something and are often used in conditionals. As in:
if (userLoggedIn) {
// do logged in stuff
}
Booleans can be created with the literal true
or false
, but they can also be derived from other values using a comparison:
let isTrue = (1 - 1) === 0;
or from the Boolean constructor (which is the primitive type wrapper for booleans):
let isFalse = Boolean(0);
Because the Boolean constructor is a function, it can be used in some pretty handy ways, like filtering "falsy" values out of a list:
let list = [1, false, 0, null, undefined, '', NaN];
let truthyList = list.filter(Boolean); // -> [1]
I was going to try avoid writing about the concepts of "truthy" and "falsy" but it feels like they fit here. Javascript has true
and false
, which mean exactly what they sound like they mean. But there are also other values that evaluate to false (we call them "falsy" values because they are not false
but they can be coerced to false). Those values are: 0
, null
, undefined
, ''
, and NaN
("not a number"). Everything else is considered "truthy" (that is, it's not true
but can be coerced to true). Not all languages have the concepts of truthy and falsy, but in javascript you can use truthy and falsy values mostly the same way you use literal true
and false
. For example, javascript has no problem with this:
if (0) {
// This won't be run because 0 is "falsy"
}
if (1) {
// This WILL run because 1 is "truthy"
}
Non-boolean values used as booleans is acceptable in javascript because of how the interpreter coerces unlike values. But coercion definitely is too much to get into in this post. Suffice it to say, for now, that that list of falsy values can be treated like false
in most cases, and other values can be treated like true
in most cases.
numbers
Numbers are, well, numbers. But javascript numbers have some edge cases as well. Like NaN
(yes, "not a number" is considered a number) and Infinity
. Even -Infinity
and -0
. You can do math with them using traditional operators:
let a = 2;
let b = 3;
let c = a + b;
let d = a * c;
// etc.
And you can compare them to create boolean expressions:
if (theMeaningOfLife === 42) {
// Do stuff
}
They also have a number of methods that can be called, including toFixed
(which rounds to a certain number of decimal places) and toLocaleString
which formats a number the way a particular region would write it.
Numbers also have a useful constructor, Number
, which can be used to convert certain values to numbers. For example:
let a = Number('12') // -> 12
let b = Number(false) // -> 0 (because 0 is falsy, as described above)
I typically use this instead of the oft preferred parseInt
because 1) there's a general recommendation to always provide the second argument (radix) to parseInt, but also 2) parseInt
only parses integers, so you need to know before hand if your number has a decimal place so that you can instead call parseFloat
. Or you could just always call Number
which handles decimal points fine:
let a = Number('12.1232') // -> 12.1232
strings
Strings are a series of characters inside quotes:
let foo = 'strings can have numbers: 23849, and symbols: @#$%# and basically anything, even emojis: 😀';
Most other primitives (and objects) have a toString
method to convert a value to string. For example:
(42).toString() // "42"
true.toString() // "true"
Symbol(foo).toString() // "Symbol(foo)"
({}).toString() // "[object Object]"
[1,2,3].toString() // "1,2,3"
// even
(function(){}).toString() // "function(){}"
Note that a literal number requires parentheses because otherwise the interpreter thinks the dot is a decimal place. Similarly, {}.toString()
can't be done (at least in the dev tools console) because the interpreter considers {
to indicate the start of a block, not the start of an object (but let a = {}.toString()
works fine). The function requires parentheses for a similar reason.
Strings also have a variety of functions, including includes
, indexOf
, match
, replace
, split
, substring
, etc. etc. You can see a full list here.
Unlike other types, string can be formed in a variety of ways. There are 3 different literal string representations: single quotes, double quotes, and backticks. Single quotes and double quotes are identical in usage, except that in single quotes, you have to escape apostrophes (otherwise they'd terminate the string), and in double quotes, you have to escape other double quotes.
let str1 = 'This is a string.'
let str2 = "This is also a string."
let str3 = 'This string isn\'t safe for single quote.'
let str4 = "But \"isn't\" can be typed here, unlike double quotes."
Backticks were a new addition to javascript in ES6 and are also called "template literals." Backticks are strings that can contain variables and other expressions, which are interpolated at run time (interpreted and placed into the string). That let's you do this:
let name = 'Andrew'
let greeting = `Hello, ${name}!` // -> Hello, Andrew!
instead of this
let name = 'Andrew'
let greeting = 'Hello, ' + name + '!'
Backticks also let you include literal newlines:
let str = `This string
is broken over
multiple lines.`
and provide a nice way to avoid the "Am I going to need an apostrophe in this string?" dilemma.
That's about it for primitives. I spent a little more time here than I originally thought I would, so I'll save objects (etc.) for another post because there's lots to say there as well. As always, if any points need clarification or if you have questions, please leave a comment, and I'll try to respond quickly.