• JavaScript中尽量别用全局变量
  • 发布于 2个月前
  • 169 热度
    0 评论
  • 弄潮儿
  • 1 粉丝 30 篇博客
  •   
浏览器之前一直有个奇怪的设定:带有 ID 的 DOM 元素可以直接在 JavaScript 中作为全局变量进行访问。不知道大家之前了不了解这个,反正我是最近才知道,但是它一直在 Web 环境中是个固定存在的事实。你可以直接在浏览器做个测试,找到一个带有 ID 的元素:
<div id="ConardLi"></div>
通常,我们会使用 querySelector("#ConardLi") 或 getElementById("ConardLi") 定义一个新变量来选择这个元素:
var element = querySelector("#ConardLi");
但我们实际上已经可以直接在没有这种繁琐代码的情况下访问 #ConardLi:

所以,HTML 中的任何 id(或 name 属性)都可以在 JavaScript 中使用 window[ELEMENT_ID] 直接访问。再强调一次,这并不是一个 “新的” 功能,但确实不是很常见。很显然,这使用命名引用来访问全局作用域并不是个好主意 ,它很容易对我们的全局作用域造成 “污染”。

HTML 规范中概述了这种方法,其中将其描述为 “Named access on the Window object”。

Internet Explorer 是第一个实现这个功能的浏览器,后来所有其他浏览器也对它提供了支持。Gecko 内核是当时唯一不直接在标准模式下支持它的浏览器,而是选择将其作为实验性功能。Gecko 甚至试图说服  WebKit 将其移出标准模式,但最终还是妥协了,并最终在 Firefox 14 中进入标准模式。

因为有全局变量污染的风险,浏览器必须要采取一些预防措施来确保生成的全局变量不会破坏我们的网站,其中一项措施叫 “Variable shadowing”。

听起来挺高大上,实际上就是命名元素生成的全局变量引用不会覆盖现有的全局变量,所以如果 DOM 元素具有 ID 已定义为全局的元素,它不会把现有的变量覆盖掉,比如:
<head>
  <script>
    window.foo = "17";
  </script>
</head>
<body>
  <div id="foo">I won't override window.foo</div>
  <script>
    console.log(window.foo); // "17"
  </script>
</body>
反之亦然:
<div id="foo">I will be overridden :(</div>
<script>
  window.foo = "17";
  console.log(window.foo); // "17"
</script>
这种行为是至关重要的,因为它会使 <div id="alert" /> 这样危险的代码不会覆盖掉原生的 alert API 。正是因为这种保护技术的存在,很有可能就是我们最近才了解到这一点的原因。

简单搜了一下,其实有很多文章批评过这种写法,比如这篇 12 年的老文章说的就比较中肯:

如果 DOM 发生变化,那么引用也会发生变化」:这会引申出一些非常 “脆弱” 的代码;
意外引用太容易了」:一个简单的拼写错误很可能会引用一个命名的全局变量,并给你带来意想不到的结果。
浏览器中的实现方式有所不同」:id例如,我们应该能够使用- 例如-访问锚点,<a id="cool">但某些浏览器(即 Safari 和 Firefox)ReferenceError在控制台中返回 a 。
它可能不会返回您的想法」。根据规范,当 DOM 中有多个相同命名元素的实例时 (例如 <div class="cool">的两个实例),浏览器应该返回一个包含实例数组的 HTMLCollection。但是,Firefox 只返回第一个实例。
可能有性能成本」:浏览器必须要单独创建引用去维护这些变量,实际上它们可能永远不会被用到。
另外我还想到一些其他可能引发的问题,比如在我们实现某个 Polyfill  的时候,我们首先会去判断当前的浏览器环境支不支持它:
<!-- 堆代码 duidaima.com -->
<body>
  <img id="cookieStore"></img>
  <script>
    // Polyfill the CookieStore API if not yet implemented.
    // https://developer.mozilla.org/en-US/docs/Web/API/CookieStore
    if (!window.cookieStore) {
      window.cookieStore = myCookieStorePolyfill;
    }
    cookieStore.set("foo", "bar");
  </script>
</body>
这段代码在 Chrome 中运行得很好,但在 Safari 中会抛出以下错误:
TypeError: cookieStore.set is not a function
Safari 目前没有对 cookieStore 提供支持,当我们试图去给他添加一个 Polofill 时,会因为页面上已经有了一个 id 为 cookieStore 的 img 标签而无法添加成功。另外,浏览器 JavaScript API 的更新也是可能会破坏命名元素的全局引用的,例如:
<body>
  <input id="BarcodeDetector"></input>
  <script>
    window.BarcodeDetector.focus();
  </script>
</body>
当前这段代码是正常工作的,但是我们无法预测它还能工作多久,加入浏览器增加了一个原生 API 就叫 BarcodeDetector ,那这段代码就 GG ...
所以今天的结论:就算你知道了通过全局变量可以直接访问页面上的 DOM ,也尽量别用!
用户评论