skip to Main Content

This one was short enough to keep in the title but I’ll also add my understanding.

My understanding is any variable (regardless of using ‘var’, ‘let’, or ‘const’), if you referenced without any declaration results in a ‘Reference Error’

This is the behavior of functions as well (arrow and regular).

So why does the following:

console.log(typeof foo)

return ‘undefined’. If I never declared it? Why isn’t it a ‘Reference Error’?

2

Answers


  1. typeof is an exceptional case. In addition to doing a normal thing on a value that’s passed to it like any other operator, only when its operand is a plain name, it has the extra weird behavior of checking whether that name is in scope and evaluating to "undefined" without throwing an error if it isn’t.

    The relevant part of the language specification is ECMAScript 2025 13.5.3:

    1. If val is a Reference Record, then

      a. If IsUnresolvableReference(val) is true, return "undefined".

    When you don’t know whether something’s in scope and that something isn’t a property of the global object, it can be necessary1 to rely on this behavior – in a universal module definition (UMD) kind of pattern, for example, where you might check if typeof module === "undefined" to decide how to export a library (because your code might be inside a (function (module) { /* your code */ })(…) kind of wrapper). This is a pretty rare situation.

    Other times, you might want to check whether some API exists on the global object, and since this typeof behavior exists, it can be an easy way to get the job done.

    if (typeof SharedWorker === "undefined") {
        // no shared worker support in this environment
    } else {
        // good to go
    }
    

    This isn’t a situation that requires the use of this typeof quirk, since it was always possible to get the global object in a generic way even before globalThis was introduced for a "SharedWorker" in globalThis-style test. But since the typeof quirk is a thing, may as well use it here too, depending on your preferences.

    1 Well, you could also use a try, but that would be more verbose, and has some subtleties if you want to wrap it in a function. Maybe still arguably better language design in the end, though.

    Login or Signup to reply.
  2. typeof has special behavior when the argument is an identifier, so you can test whether the variable has been declared and initialized.

    From MDN

    typeof is generally always guaranteed to return a string for any operand it is supplied with. Even with undeclared identifiers, typeof will return "undefined" instead of throwing an error.

    However, using typeof on lexical declarations (let const, and class) in the same block before the place of declaration will throw a ReferenceError. Block scoped variables are in a temporal dead zone from the start of the block until the initialization is processed, during which it will throw an error if accessed.

    Here’s a code snippet to demonstrate:

    "use strict";
    
    {
      try {
        console.log("never declared in scope:", typeof foo);
      } catch (cause) {
        console.error(cause);
      }
    }
    
    {
      try {
        console.log("declared later in the same scope:", typeof foo);
      } catch (cause) {
        console.error(cause);
      }
      let foo;
    }
    
    {
      let foo;
      try {
        console.log("declared before and initialized later:", typeof foo);
      } catch (cause) {
        console.error(cause);
      }
      foo = null;
    }
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search