如果我全局定义变量`closed`为`0`,为什么它被记录为`false`?

4
我知道这可能是非常基础的东西,但我不理解作用域是如何工作的。我希望closed变量在整个JavaScript文件中都能被访问。
我有一个类似这样的代码(使用jQuery):
var closed = 0;

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

但是closed被记录为false。我尝试了很多关于loadonload函数的方法,但我失败了。


“closed” 指全局范围内的 “window.closed”。不要在全局定义变量。如果必须,将所有内容包装在 IIFE 中。 - Sebastian Simon
看起来像是一个IIFE,你在它里面定义的变量外部无法访问。 - Aravind S
2个回答

5

问题:全局只读属性

首先需要注意的是,这个问题存在于Web环境中,也就是在浏览器中。其他JS环境可能没有这个问题。

在全局作用域中,var closed = 0;并不会创建一个绑定,它仍然是布尔值false

原因是在这种情况下,closed指的是window.closed,它始终是一个布尔值,重新定义它会产生类似于“重新定义closed”的linter警告。

这个只读属性表示引用的窗口是否关闭。

像这样表现的变量名称可以在这些列表中找到:

在MDN上也曾有关于一个名为WindowOrWorkerGlobalScope的混合内容的文档,其属性将适合于此列表;但是,这只是指那些既在Window中又在WorkerGlobalScope中的内容。 一点点背景:MDN对其文档进行了重构,删除了所有这些“混合内容”,采用标准接口名称。 您可以在归档版本的此列表中找到它们。

WindowWorkerGlobalScope是Web环境中globalThis可能是其实例的两个接口。

运行代码片段以获取不能在全局范围内安全使用的变量名称的完整列表:

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>


你会注意到,这是很多变量名简短且常见的形式,例如namelength [1][2]status [1][2]selftopmenubarparent。 此外,在设置器分配时调用某些函数,例如var location = "Podunk, USA";实际上会重定向到位置./Podunk, USA

有一个相关问题,即在事件属性(如onclick)中使用函数名称,例如lang, checked, autocomplete, evaluateanimate:在那里,不仅包括window属性在作用域链中,还包括当前HTMLDocument和当前特定Element的整个原型链上的所有可作用域的属性(例如,使用<a onclick=""></a>提供对从HTMLAnchorElement.prototype开始的所有内容的访问),以及可能的一些其他内容,例如来自form属性(如果存在)的任何内容(表单元素)。

所有这些内容也在相关问题的答案中有解释。

当依赖于具有id成为全局属性的DOM节点时,也会出现此问题。

解决方案

有几种解决方案。 我将按照最好到最差的主观顺序列出它们。

使用模块

将JavaScript代码作为模块运行,而不是脚本,不仅是新潮流,它还没有这个问题

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 });

模块还带有一些其他有用的功能,例如与deferment相对应的隐式DOM解析

将所有内容包装在新范围内

在脚本的全局范围内,您会遇到这个问题。 在新的函数范围内,您不会遇到这个问题:

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

您也可以创建一个更有用的范围,例如jQuery的$(function(){});,它等待DOM加载完成。顺便说一下,这可能是兼容性最高的选项之一,同时仍然是较好的选项之一。
由于我不使用jQuery,我更喜欢将所有内容包装在DOMContentLoaded监听器中,在那里我可以限定所有需要的变量,并且还可以使用"use strict";
addEventListener("DOMContentLoaded", () => { "use strict";
  // Code goes here.
});

这样可以避免全局属性和全局变量之间的冲突。

始终使用 const。如果无法使用 const,则使用 let。如果无法使用 let,则使用 var

…但你永远不需要使用 var1

Ankit Agarwal’s answer 所建议,在脚本中使用 constlet 可以缓解这个问题:

const closed = 0;

console.log(closed); // 0

然而,这仅适用于大多数变量名称,但并非全部——const closed = 123; let history = "Hello, world!";都可以工作,但const window = 123;, const top = 123;let document;则不行。

1: 虽然可能会有一种特殊情况。 显然,旧版浏览器仍需要使用var

只需使用不同的变量名称

听起来很明显,但这并不是很健壮。

新的全局属性可能随时出现,因此全局变量可能随时与它们冲突。由于这显然会破坏互联网上的多个脚本,现在添加的全局属性并不会真正引起太大问题。例如const history = "Regency Era";可以正常工作,并使得history可用作指向该字符串的变量。var history =;仍然不行,在严格模式下会抛出错误,在松散模式下会默默失败。这在历史上导致了兼容性问题,这就是为什么这不是一个强大的选项,特别是在使用var时,这又是反对var的另一个有力论据。
重新分配旧的属性(如document)将在某些环境中的脚本中失败,但每个变量名都可以在模块中运行,在每个环境中都可以,这就是为什么这是最佳推荐选项的原因。

我目前不理解为什么(至少)windowtopdocument在全局范围使用constlet时与所有其他属性的行为不同。也许我或其他人可以弄清楚这一点,并完善本答案中给出的解释。 - Sebastian Simon

0

在编写代码时,使用let替代var。因为closed是JavaScript运行时使用的全局变量,所以为了使其局限于代码范围内,可以使用let替代var,它将变量设置为全局作用域,考虑到全局closed属性。

let closed=0;
$( function() {
    console.log(closed);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>


正是我所需要的,谢谢。 - user9940465
1
@MinirockAkeru 这个回答没有解释为什么会得到 false。并不是所有的变量都会有这样的行为。 - Sebastian Simon
在大多数情况下,这甚至都不起作用。它适用于“closed”,但即使它们都是window上的非可写、可配置、可枚举、非setter getter,也不适用于“top”。 - Sebastian Simon

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接