Values & Types

As we asserted in Chapter 1, JavaScript has typed values, not typed variables. The following built-in types are available:

  • string
  • number
  • boolean
  • null and undefined
  • object
  • symbol (new to ES6)

JavaScript provides a typeof operator that can examine a value and tell you what type it is:

  1. var a;
  2. typeof a; // "undefined"
  3. a = "hello world";
  4. typeof a; // "string"
  5. a = 42;
  6. typeof a; // "number"
  7. a = true;
  8. typeof a; // "boolean"
  9. a = null;
  10. typeof a; // "object" -- weird, bug
  11. a = undefined;
  12. typeof a; // "undefined"
  13. a = { b: "c" };
  14. typeof a; // "object"

The return value from the typeof operator is always one of six (seven as of ES6! - the “symbol” type) string values. That is, typeof "abc" returns "string", not string.

Notice how in this snippet the a variable holds every different type of value, and that despite appearances, typeof a is not asking for the “type of a“, but rather for the “type of the value currently in a.” Only values have types in JavaScript; variables are just simple containers for those values.

typeof null is an interesting case, because it errantly returns "object", when you’d expect it to return "null".

Warning: This is a long-standing bug in JS, but one that is likely never going to be fixed. Too much code on the Web relies on the bug and thus fixing it would cause a lot more bugs!

Also, note a = undefined. We’re explicitly setting a to the undefined value, but that is behaviorally no different from a variable that has no value set yet, like with the var a; line at the top of the snippet. A variable can get to this “undefined” value state in several different ways, including functions that return no values and usage of the void operator.

Objects

The object type refers to a compound value where you can set properties (named locations) that each hold their own values of any type. This is perhaps one of the most useful value types in all of JavaScript.

  1. var obj = {
  2. a: "hello world",
  3. b: 42,
  4. c: true
  5. };
  6. obj.a; // "hello world"
  7. obj.b; // 42
  8. obj.c; // true
  9. obj["a"]; // "hello world"
  10. obj["b"]; // 42
  11. obj["c"]; // true

It may be helpful to think of this obj value visually:

Values & Types - 图1

Properties can either be accessed with dot notation (i.e., obj.a) or bracket notation (i.e., obj["a"]). Dot notation is shorter and generally easier to read, and is thus preferred when possible.

Bracket notation is useful if you have a property name that has special characters in it, like obj["hello world!"] — such properties are often referred to as keys when accessed via bracket notation. The [ ] notation requires either a variable (explained next) or a string literal (which needs to be wrapped in " .. " or ' .. ').

Of course, bracket notation is also useful if you want to access a property/key but the name is stored in another variable, such as:

  1. var obj = {
  2. a: "hello world",
  3. b: 42
  4. };
  5. var b = "a";
  6. obj[b]; // "hello world"
  7. obj["b"]; // 42

Note: For more information on JavaScript objects, see the this & Object Prototypes title of this series, specifically Chapter 3.

There are a couple of other value types that you will commonly interact with in JavaScript programs: array and function. But rather than being proper built-in types, these should be thought of more like subtypes — specialized versions of the object type.

Arrays

An array is an object that holds values (of any type) not particularly in named properties/keys, but rather in numerically indexed positions. For example:

  1. var arr = [
  2. "hello world",
  3. 42,
  4. true
  5. ];
  6. arr[0]; // "hello world"
  7. arr[1]; // 42
  8. arr[2]; // true
  9. arr.length; // 3
  10. typeof arr; // "object"

Note: Languages that start counting at zero, like JS does, use 0 as the index of the first element in the array.

It may be helpful to think of arr visually:

Values & Types - 图2

Because arrays are special objects (as typeof implies), they can also have properties, including the automatically updated length property.

You theoretically could use an array as a normal object with your own named properties, or you could use an object but only give it numeric properties (0, 1, etc.) similar to an array. However, this would generally be considered improper usage of the respective types.

The best and most natural approach is to use arrays for numerically positioned values and use objects for named properties.

Functions

The other object subtype you’ll use all over your JS programs is a function:

  1. function foo() {
  2. return 42;
  3. }
  4. foo.bar = "hello world";
  5. typeof foo; // "function"
  6. typeof foo(); // "number"
  7. typeof foo.bar; // "string"

Again, functions are a subtype of objectstypeof returns "function", which implies that a function is a main type — and can thus have properties, but you typically will only use function object properties (like foo.bar) in limited cases.

Note: For more information on JS values and their types, see the first two chapters of the Types & Grammar title of this series.

Built-In Type Methods

The built-in types and subtypes we’ve just discussed have behaviors exposed as properties and methods that are quite powerful and useful.

For example:

  1. var a = "hello world";
  2. var b = 3.14159;
  3. a.length; // 11
  4. a.toUpperCase(); // "HELLO WORLD"
  5. b.toFixed(4); // "3.1416"

The “how” behind being able to call a.toUpperCase() is more complicated than just that method existing on the value.

Briefly, there is a String (capital S) object wrapper form, typically called a “native,” that pairs with the primitive string type; it’s this object wrapper that defines the toUpperCase() method on its prototype.

When you use a primitive value like "hello world" as an object by referencing a property or method (e.g., a.toUpperCase() in the previous snippet), JS automatically “boxes” the value to its object wrapper counterpart (hidden under the covers).

A string value can be wrapped by a String object, a number can be wrapped by a Number object, and a boolean can be wrapped by a Boolean object. For the most part, you don’t need to worry about or directly use these object wrapper forms of the values — prefer the primitive value forms in practically all cases and JavaScript will take care of the rest for you.

Note: For more information on JS natives and “boxing,” see Chapter 3 of the Types & Grammar title of this series. To better understand the prototype of an object, see Chapter 5 of the this & Object Prototypes title of this series.

Comparing Values

There are two main types of value comparison that you will need to make in your JS programs: equality and inequality. The result of any comparison is a strictly boolean value (true or false), regardless of what value types are compared.

Coercion

We talked briefly about coercion in Chapter 1, but let’s revisit it here.

Coercion comes in two forms in JavaScript: explicit and implicit. Explicit coercion is simply that you can see obviously from the code that a conversion from one type to another will occur, whereas implicit coercion is when the type conversion can happen as more of a non-obvious side effect of some other operation.

You’ve probably heard sentiments like “coercion is evil” drawn from the fact that there are clearly places where coercion can produce some surprising results. Perhaps nothing evokes frustration from developers more than when the language surprises them.

Coercion is not evil, nor does it have to be surprising. In fact, the majority of cases you can construct with type coercion are quite sensible and understandable, and can even be used to improve the readability of your code. But we won’t go much further into that debate — Chapter 4 of the Types & Grammar title of this series covers all sides.

Here’s an example of explicit coercion:

  1. var a = "42";
  2. var b = Number( a );
  3. a; // "42"
  4. b; // 42 -- the number!

And here’s an example of implicit coercion:

  1. var a = "42";
  2. var b = a * 1; // "42" implicitly coerced to 42 here
  3. a; // "42"
  4. b; // 42 -- the number!

Truthy & Falsy

In Chapter 1, we briefly mentioned the “truthy” and “falsy” nature of values: when a non-boolean value is coerced to a boolean, does it become true or false, respectively?

The specific list of “falsy” values in JavaScript is as follows:

  • "" (empty string)
  • 0, -0, NaN (invalid number)
  • null, undefined
  • false

Any value that’s not on this “falsy” list is “truthy.” Here are some examples of those:

  • "hello"
  • 42
  • true
  • [ ], [ 1, "2", 3 ] (arrays)
  • { }, { a: 42 } (objects)
  • function foo() { .. } (functions)

It’s important to remember that a non-boolean value only follows this “truthy”/“falsy” coercion if it’s actually coerced to a boolean. It’s not all that difficult to confuse yourself with a situation that seems like it’s coercing a value to a boolean when it’s not.

Equality

There are four equality operators: ==, ===, !=, and !==. The ! forms are of course the symmetric “not equal” versions of their counterparts; non-equality should not be confused with inequality.

The difference between == and === is usually characterized that == checks for value equality and === checks for both value and type equality. However, this is inaccurate. The proper way to characterize them is that == checks for value equality with coercion allowed, and === checks for value equality without allowing coercion; === is often called “strict equality” for this reason.

Consider the implicit coercion that’s allowed by the == loose-equality comparison and not allowed with the === strict-equality:

  1. var a = "42";
  2. var b = 42;
  3. a == b; // true
  4. a === b; // false

In the a == b comparison, JS notices that the types do not match, so it goes through an ordered series of steps to coerce one or both values to a different type until the types match, where then a simple value equality can be checked.

If you think about it, there’s two possible ways a == b could give true via coercion. Either the comparison could end up as 42 == 42 or it could be "42" == "42". So which is it?

The answer: "42" becomes 42, to make the comparison 42 == 42. In such a simple example, it doesn’t really seem to matter which way that process goes, as the end result is the same. There are more complex cases where it matters not just what the end result of the comparison is, but how you get there.

The a === b produces false, because the coercion is not allowed, so the simple value comparison obviously fails. Many developers feel that === is more predictable, so they advocate always using that form and staying away from ==. I think this view is very shortsighted. I believe == is a powerful tool that helps your program, if you take the time to learn how it works.

We’re not going to cover all the nitty-gritty details of how the coercion in == comparisons works here. Much of it is pretty sensible, but there are some important corner cases to be careful of. You can read section 11.9.3 of the ES5 specification (http://www.ecma-international.org/ecma-262/5.1/) to see the exact rules, and you’ll be surprised at just how straightforward this mechanism is, compared to all the negative hype surrounding it.

To boil down a whole lot of details to a few simple takeaways, and help you know whether to use == or === in various situations, here are my simple rules:

  • If either value (aka side) in a comparison could be the true or false value, avoid == and use ===.
  • If either value in a comparison could be one of these specific values (0, "", or [] — empty array), avoid == and use ===.
  • In all other cases, you’re safe to use ==. Not only is it safe, but in many cases it simplifies your code in a way that improves readability.

What these rules boil down to is requiring you to think critically about your code and about what kinds of values can come through variables that get compared for equality. If you can be certain about the values, and == is safe, use it! If you can’t be certain about the values, use ===. It’s that simple.

The != non-equality form pairs with ==, and the !== form pairs with ===. All the rules and observations we just discussed hold symmetrically for these non-equality comparisons.

You should take special note of the == and === comparison rules if you’re comparing two non-primitive values, like objects (including function and array). Because those values are actually held by reference, both == and === comparisons will simply check whether the references match, not anything about the underlying values.

For example, arrays are by default coerced to strings by simply joining all the values with commas (,) in between. You might think that two arrays with the same contents would be == equal, but they’re not:

  1. var a = [1,2,3];
  2. var b = [1,2,3];
  3. var c = "1,2,3";
  4. a == c; // true
  5. b == c; // true
  6. a == b; // false

Note: For more information about the == equality comparison rules, see the ES5 specification (section 11.9.3) and also consult Chapter 4 of the Types & Grammar title of this series; see Chapter 2 for more information about values versus references.

Inequality

The <, >, <=, and >= operators are used for inequality, referred to in the specification as “relational comparison.” Typically they will be used with ordinally comparable values like numbers. It’s easy to understand that 3 < 4.

But JavaScript string values can also be compared for inequality, using typical alphabetic rules ("bar" < "foo").

What about coercion? Similar rules as == comparison (though not exactly identical!) apply to the inequality operators. Notably, there are no “strict inequality” operators that would disallow coercion the same way === “strict equality” does.

Consider:

  1. var a = 41;
  2. var b = "42";
  3. var c = "43";
  4. a < b; // true
  5. b < c; // true

What happens here? In section 11.8.5 of the ES5 specification, it says that if both values in the < comparison are strings, as it is with b < c, the comparison is made lexicographically (aka alphabetically like a dictionary). But if one or both is not a string, as it is with a < b, then both values are coerced to be numbers, and a typical numeric comparison occurs.

The biggest gotcha you may run into here with comparisons between potentially different value types — remember, there are no “strict inequality” forms to use — is when one of the values cannot be made into a valid number, such as:

  1. var a = 42;
  2. var b = "foo";
  3. a < b; // false
  4. a > b; // false
  5. a == b; // false

Wait, how can all three of those comparisons be false? Because the b value is being coerced to the “invalid number value” NaN in the < and > comparisons, and the specification says that NaN is neither greater-than nor less-than any other value.

The == comparison fails for a different reason. a == b could fail if it’s interpreted either as 42 == NaN or "42" == "foo" — as we explained earlier, the former is the case.

Note: For more information about the inequality comparison rules, see section 11.8.5 of the ES5 specification and also consult Chapter 4 of the Types & Grammar title of this series.