Why is the variable `closed` being logged as `false`, if I define it globally as `0`?

I know this must be really basic stuff but I don’t understand how the scope is working. I want the closed variable be known in the entire JavaScript file.

I have something like that (in jQuery):

var closed = 0;

$(function(){
  console.log(closed);
});

But closed is getting logged as false. I tried many things with load and onload functions, but I failed.


Solution 1:

The problem: global read-only properties

First, note that this problem exists in a Web environment, i.e. in a browser. Other JS environments may not have this problem.

var closed = 0; in the global scope doesn’t create a binding that refers to a number and remains being the boolean false.

The reason for this is that closed refers to window.closed in this circumstance; it’s always a boolean, and redefining it produces a linter warning like “Redefinition of closed.

This read-only property indicates whether the referenced window is closed or not.

Variable names that behave like this can be found in these lists:

  • Window properties
  • WorkerGlobalScope properties

There also used to be documentation on MDN about a mixin called WindowOrWorkerGlobalScope, whose properties would fit into this list; however, this just refers to things that are both in Window and in WorkerGlobalScope. Tiny piece of context: MDN did a refactoring of their documentation where they removed all these “mixins” in favor of standard interface names. You can find archived versions of this list.

Window or WorkerGlobalScope are two of the interfaces that globalThis can be an instance of in Web environments.

Run the snippet to get a full list of variable names that you can’t safely use in the global scope:

const props = Object.entries(Object.getOwnPropertyDescriptors(globalThis)),
  undesirable = {
    set: (desc) => desc,
    configurable: (desc) => !desc,
    writable: (desc) => !desc
  };

Array.from(document.querySelectorAll("[id]"))
  .forEach((span) => span.innerHTML = props
    .filter(([prop, {[span.id]: desc}]) => undesirable[span.id](desc))
    .map(([prop]) => `<code>${prop}</code>`)
    .join(", "))
code{
  background: #eee;
  padding: 1px 3px;
}
<p>Properties that have a setter which may change the type or invoke some function, when a value is set to it:</p>
<span id="set"></span>

<hr/>

<p>Properties that are not configurable:</p>
<span id="configurable"></span>

<hr/>

<p>Properties that are read-only:</p>
<span id="writable"></span>

You’ll notice, it’s a lot. It’s also a lot of short, common variable names like name, length [1], [2], status [1], [2], self, top, menubar, and parent. Furthermore, regarding the invocation of some function when assigning to a setter, something like var location = "Podunk, USA"; actually redirects you to the location ./Podunk, USA.

There’s a related issue of using function names like lang, checked, autocomplete, evaluate, or animate in event attributes like onclick: there, not only the window properties are included in the scope chain, but also all scopable properties of the entire prototype chain of the current HTMLDocument and of the current specific Element (e.g. using <a onclick=""></a> provides access to everything starting at HTMLAnchorElement.prototype).

All this is also explained in this answer to a related question.

Solutions

There is a handful of solutions to this. I’ll list them in a subjective order of best to worst.

Use modules instead

Running your JavaScript code as modules instead of scripts not only is the new cool thing to do, it also just doesn’t have this problem.

index.html:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <title>Your site</site>
    <script type="module" src="main.mjs"></script>
  </head>
  <body>
    <!-- Content. -->
  </body>
</html>

main.mjs:

const top = { spinning: true },
  window = { type: "clear", frame: "wood" };
let document = { type: "LaTeX", content: "\\documentclass[a4]{…} …" },
  closed = 0;
var location = "Pudunk, USA";

// All of these are the variables defined above!
console.log({ top, window, document, closed, location });

Modules also come with a few other helpful features like implicit deferment in relation to DOM parsing.

Wrap everything in a new scope

In a script’s global scope, you will encounter this problem. In a new function scope, you don’t have this problem:

(function(){
  const closed = 0; // Or `let` or `var`.
  
  $(function(){
    console.log(closed);
  });
})();

You can also create a more useful scope, for example jQuery’s $(function(){}); which waits for the DOM to be loaded. This, by the way, is probably the option with the highest compatibility while still being one of the better options.

Since I don’t use jQuery, I prefer to wrap everything in a DOMContentLoaded listener where I can scope all the variables I need, and, in addition, use "use strict";:

addEventListener("DOMContentLoaded", () => { "use strict";
  // Code goes here.
});

This avoids the clash between global properties and global variables.

Always use const. If you can’t use const, use let. If you can’t use let, use var.

… But you’ll never need var!1

Using const or let in scripts, as Ankit Agarwal’s answer suggests, mitigates this problem:

const closed = 0;

console.log(closed); // 0

However, this only works for most variable names, but not all — const closed = 123; let history = "Hello, world!"; all work, but const window = 123;, const top = 123; or let document; don’t.

1: Although there may be one edge-case. And obviously, older browsers still need var.

Just use different variable names

Sounds obvious, but this isn’t very robust.

New global properties may always pop up, so global variables may clash with them at any time. Since this would obviously break several scripts all over the Internet, global properties which are added nowadays don’t really create much of an issue. const history = "Regency Era"; works just fine and makes history usable as a variable that refers to this string. var history =; still doesn’t, and it throws an error in strict mode, and fails silently in loose mode. This has historically led to compatibility issues which is why this isn’t a robust option, especially when using var, which in turn is another strong argument against var.

Reassigning older properties, such as document, will always fail in scripts.