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

2

4

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.

Tacnaarica answered 27/6, 2018 at 12:18 Comment(2)
closed refers to window.closed in global scope. Don’t define variables globally. Wrap everything in an IIFE if you must.Stadtholder
it looks like an IIFE, your outside variable won't have access inside it.Glorygloryofthesnow
M
0

Use let instead of var as closed is a global variable used by JavaScript runtime so to make it local to the scope of the code you can use let instead of var which set the variable to global scope considering the global closed property.

let closed=0;
$( function() {
    console.log(closed);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
Mausoleum answered 27/6, 2018 at 12:20 Comment(3)
Exactly what I needed Thanks.Tacnaarica
@MinirockAkeru This answer didn’t explain why you get false though. Not all variables behave like this.Stadtholder
This doesn’t even work in most cases. It works for closed, but wouldn’t work for top, even if they’re both non-writable, configurable, enumerable, non-setter getters on window.Stadtholder
S
5

The problem: global read-only properties

First, note that this problem exists in a Web environment, i.e. in a browser. Other JS environments might 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:

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) and perhaps some more, e.g. anything from the form property (of form elements) if it exists.

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

This problem is also exhibited when relying on DOM nodes with an id to become global properties.

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 = "Podunk, 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 fail in scripts, in certain environments, but every variable name will work in a module, in every environment, which is why this is the top recommended option.

Stadtholder answered 27/6, 2018 at 12:33 Comment(1)
I currently don’t understand why (at least) window, top and document behave differently from all other properties when const or let is used in global scope. Maybe I or someone else can figure this out and refine the explanations given in this answer.Stadtholder
M
0

Use let instead of var as closed is a global variable used by JavaScript runtime so to make it local to the scope of the code you can use let instead of var which set the variable to global scope considering the global closed property.

let closed=0;
$( function() {
    console.log(closed);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
Mausoleum answered 27/6, 2018 at 12:20 Comment(3)
Exactly what I needed Thanks.Tacnaarica
@MinirockAkeru This answer didn’t explain why you get false though. Not all variables behave like this.Stadtholder
This doesn’t even work in most cases. It works for closed, but wouldn’t work for top, even if they’re both non-writable, configurable, enumerable, non-setter getters on window.Stadtholder

© 2022 - 2024 — McMap. All rights reserved.