Values as Types

In JavaScript, variables don’t have types — values have types. Variables can hold any value, at any time.

Another way to think about JS types is that JS doesn’t have “type enforcement,” in that the engine doesn’t insist that a variable always holds values of the same initial type that it starts out with. A variable can, in one assignment statement, hold a string, and in the next hold a number, and so on.

The value 42 has an intrinsic type of number, and its type cannot be changed. Another value, like "42" with the string type, can be created from the number value 42 through a process called coercion (see Chapter 4).

If you use typeof against a variable, it’s not asking “what’s the type of the variable?” as it may seem, since JS variables have no types. Instead, it’s asking “what’s the type of the value in the variable?”

  1. var a = 42;
  2. typeof a; // "number"
  3. a = true;
  4. typeof a; // "boolean"

The typeof operator always returns a string. So:

  1. typeof typeof 42; // "string"

The first typeof 42 returns "number", and typeof "number" is "string".

undefined vs “undeclared”

Variables that have no value currently, actually have the undefined value. Calling typeof against such variables will return "undefined":

  1. var a;
  2. typeof a; // "undefined"
  3. var b = 42;
  4. var c;
  5. // later
  6. b = c;
  7. typeof b; // "undefined"
  8. typeof c; // "undefined"

It’s tempting for most developers to think of the word “undefined” and think of it as a synonym for “undeclared.” However, in JS, these two concepts are quite different.

An “undefined” variable is one that has been declared in the accessible scope, but at the moment has no other value in it. By contrast, an “undeclared” variable is one that has not been formally declared in the accessible scope.

Consider:

  1. var a;
  2. a; // undefined
  3. b; // ReferenceError: b is not defined

An annoying confusion is the error message that browsers assign to this condition. As you can see, the message is “b is not defined,” which is of course very easy and reasonable to confuse with “b is undefined.” Yet again, “undefined” and “is not defined” are very different things. It’d be nice if the browsers said something like “b is not found” or “b is not declared,” to reduce the confusion!

There’s also a special behavior associated with typeof as it relates to undeclared variables that even further reinforces the confusion. Consider:

  1. var a;
  2. typeof a; // "undefined"
  3. typeof b; // "undefined"

The typeof operator returns "undefined" even for “undeclared” (or “not defined”) variables. Notice that there was no error thrown when we executed typeof b, even though b is an undeclared variable. This is a special safety guard in the behavior of typeof.

Similar to above, it would have been nice if typeof used with an undeclared variable returned “undeclared” instead of conflating the result value with the different “undefined” case.

typeof Undeclared

Nevertheless, this safety guard is a useful feature when dealing with JavaScript in the browser, where multiple script files can load variables into the shared global namespace.

Note: Many developers believe there should never be any variables in the global namespace, and that everything should be contained in modules and private/separate namespaces. This is great in theory but nearly impossible in practicality; still it’s a good goal to strive toward! Fortunately, ES6 added first-class support for modules, which will eventually make that much more practical.

As a simple example, imagine having a “debug mode” in your program that is controlled by a global variable (flag) called DEBUG. You’d want to check if that variable was declared before performing a debug task like logging a message to the console. A top-level global var DEBUG = true declaration would only be included in a “debug.js” file, which you only load into the browser when you’re in development/testing, but not in production.

However, you have to take care in how you check for the global DEBUG variable in the rest of your application code, so that you don’t throw a ReferenceError. The safety guard on typeof is our friend in this case.

  1. // oops, this would throw an error!
  2. if (DEBUG) {
  3. console.log( "Debugging is starting" );
  4. }
  5. // this is a safe existence check
  6. if (typeof DEBUG !== "undefined") {
  7. console.log( "Debugging is starting" );
  8. }

This sort of check is useful even if you’re not dealing with user-defined variables (like DEBUG). If you are doing a feature check for a built-in API, you may also find it helpful to check without throwing an error:

  1. if (typeof atob === "undefined") {
  2. atob = function() { /*..*/ };
  3. }

Note: If you’re defining a “polyfill” for a feature if it doesn’t already exist, you probably want to avoid using var to make the atob declaration. If you declare var atob inside the if statement, this declaration is hoisted (see the Scope & Closures title of this series) to the top of the scope, even if the if condition doesn’t pass (because the global atob already exists!). In some browsers and for some special types of global built-in variables (often called “host objects”), this duplicate declaration may throw an error. Omitting the var prevents this hoisted declaration.

Another way of doing these checks against global variables but without the safety guard feature of typeof is to observe that all global variables are also properties of the global object, which in the browser is basically the window object. So, the above checks could have been done (quite safely) as:

  1. if (window.DEBUG) {
  2. // ..
  3. }
  4. if (!window.atob) {
  5. // ..
  6. }

Unlike referencing undeclared variables, there is no ReferenceError thrown if you try to access an object property (even on the global window object) that doesn’t exist.

On the other hand, manually referencing the global variable with a window reference is something some developers prefer to avoid, especially if your code needs to run in multiple JS environments (not just browsers, but server-side node.js, for instance), where the global object may not always be called window.

Technically, this safety guard on typeof is useful even if you’re not using global variables, though these circumstances are less common, and some developers may find this design approach less desirable. Imagine a utility function that you want others to copy-and-paste into their programs or modules, in which you want to check to see if the including program has defined a certain variable (so that you can use it) or not:

  1. function doSomethingCool() {
  2. var helper =
  3. (typeof FeatureXYZ !== "undefined") ?
  4. FeatureXYZ :
  5. function() { /*.. default feature ..*/ };
  6. var val = helper();
  7. // ..
  8. }

doSomethingCool() tests for a variable called FeatureXYZ, and if found, uses it, but if not, uses its own. Now, if someone includes this utility in their module/program, it safely checks if they’ve defined FeatureXYZ or not:

  1. // an IIFE (see "Immediately Invoked Function Expressions"
  2. // discussion in the *Scope & Closures* title of this series)
  3. (function(){
  4. function FeatureXYZ() { /*.. my XYZ feature ..*/ }
  5. // include `doSomethingCool(..)`
  6. function doSomethingCool() {
  7. var helper =
  8. (typeof FeatureXYZ !== "undefined") ?
  9. FeatureXYZ :
  10. function() { /*.. default feature ..*/ };
  11. var val = helper();
  12. // ..
  13. }
  14. doSomethingCool();
  15. })();

Here, FeatureXYZ is not at all a global variable, but we’re still using the safety guard of typeof to make it safe to check for. And importantly, here there is no object we can use (like we did for global variables with window.___) to make the check, so typeof is quite helpful.

Other developers would prefer a design pattern called “dependency injection,” where instead of doSomethingCool() inspecting implicitly for FeatureXYZ to be defined outside/around it, it would need to have the dependency explicitly passed in, like:

  1. function doSomethingCool(FeatureXYZ) {
  2. var helper = FeatureXYZ ||
  3. function() { /*.. default feature ..*/ };
  4. var val = helper();
  5. // ..
  6. }

There are lots of options when designing such functionality. No one pattern here is “correct” or “wrong” — there are various tradeoffs to each approach. But overall, it’s nice that the typeof undeclared safety guard gives us more options.