为什么全局变量被认为是不良实践?

44

我不断看到有关JavaScript中不要使用全局变量的警告,但似乎人们这么说的唯一原因是因为它会堵塞全局命名空间。我可以想象通过将所有变量放入一个大对象中来轻松解决这个问题。现在问题是:除了方便之外,是否还有其他原因不使用全局变量?它们是否涉及性能或兼容性问题?


2
可能是“全局变量为什么不好”和其他一些类似问题的重复。请参考此链接 - Felix Kling
2
JavaScript或其他编程语言并不重要。这是“全局变量为什么不好”的规范答案 - Dan Dascalescu
9个回答

48

全局变量会占用全局命名空间,查找速度比局部变量慢。

首先,拥有许多全局变量总是不好的,因为很容易忘记在某个地方声明了一个变量,并意外地在其他地方重新声明它。如果你的第一个变量是局部的,那么你就没有问题。如果它是全局的,那么它刚刚被覆盖了。当你进入暗示全局变量时(例如当你说someVar = someValue而没有使用var关键字声明someVar时),这种情况会变得更糟。

其次,全局变量的查找时间比局部变量长。速度上的差异并不是特别大,但确实存在。

有关更深入的阅读和解释全局变量为何被视为不良做法的内容,您可以查看此页面


如果你重新声明它,你会得到一个编译器错误。但我知道你的意思,你的意思是如果你在错误的地方重复使用它,你的代码很可能会失败,你会感到困惑,不知道出了什么问题。 - user3069232

17

全局变量会显著增加耦合,从而严重降低代码的可扩展性和可测试性。一旦使用全局变量,您现在必须知道变量在哪里和如何被修改(即破坏封装)。大部分文献和惯例都会认为在使用全局变量时性能是最不用担心的问题。

这是一篇精彩的文章,概述了全局变量为什么会导致麻烦。


8
简而言之,全局变量会导致以下问题(以及更多)。
1)变量命名冲突 - 如果你正在团队中工作,并且你和你的同事都在全局范围内使用相同的变量名,后定义的变量将覆盖最初的变量。显然,这可能会产生灾难性的后果。
2)安全性 - 特别是在Web上,每个用户都可以访问Window(或全局)对象。将变量放在全局范围内,您使任何用户都能够查看或更改您的变量。
3)速度较慢 - 这可能可以忽略不计,但它仍然存在。 JavaScript变量查找的工作方式是JavaScript引擎将在正在查找变量的当前范围中进行查找。如果找不到它,它将在下一个父范围上进行查找。如果在那里找不到它,它将继续向上查找,直到找到全局对象为止查找该变量。如果所有变量都位于全局范围内,则JavaScript引擎始终必须经过每个范围才能最终达到全局范围以查找变量。

5
如果您的脚本非常长,并且从许多函数中使用这些变量,那么调试时间将会增加,因为全局变量的值可以从任何地方更改,所以如果您正在跟踪此变量何时更改为非预期值,则必须检查所有这些变量。
如果不同的程序员从页面中包含的其他脚本修改了此变量,则此情况甚至更加痛苦。

2
更难调试只是全局变量不好的原因之一。其他原因列在为什么全局变量那么邪恶中。 - Dan Dascalescu

2
只要您将全局变量包装在唯一的命名空间/对象中(以避免与不属于您的脚本发生冲突),在代码中使用全局变量应该没有任何问题。
在JavaScript中使用全局变量的一个优点是,它源于JavaScript不是强类型语言。因此,如果将某些复杂对象作为参数传递给函数,则可能会丢失所有这些对象的智能提示(在函数范围内)。而使用全局对象则可以保留该智能提示。当您拥有智能提示时,它实际上可以提高调试时间(与其他人说的相反...)
我个人认为这非常有用,并且肯定有在我的代码中的位置。
(当然,应始终在本地变量和全局变量之间取得正确的平衡)

1
为了在JavaScript中添加另一个全局危险,它们可能会隐藏标准API。

var focus = false;

const elem = document.querySelector('#name');
elem.addEventListener('focus', _ => { focus = true; show(); });
elem.addEventListener('blur', _ => { focus = false; show(); });

function show() { console.log('focus:', focus); }
<input id="name" type="text">

在上面的代码中,我创建了一个名为focus的全局变量,但它覆盖了window上的focus函数。如果我以后包含一个调用window.focus的库,它将失败。
新的函数和属性会不时地添加到window中,因此有可能您会命名某些与浏览器后来相冲突的内容。
同样地,我看到(也写过)像这样的代码
if (!window.MyAPI) {
  window.MyAPI = ...
}

...
MyAPI.doTheThing();

或者简称
window.MyAPI = window.MyAPI || ...

...
MyAPI.doTheThing();

这种模式的问题在于浏览器可能会发布一个名为MyAPI的新API,这种情况下检查将不会替换全局变量,doTheThing也将不存在。
类似的问题也发生在Reporting API上。一个非常流行的库有一个名为Report的类或函数,它试图像上面那样全局和有条件地安装。当浏览器尝试发布Reporting API时,使用该库的每个站点都会崩溃。
有两种解决方案。
  1. (poor), don't conditionally assign the global

    window.MyAPI = window.MyAPI || ...  // bad!
    window.MyAPI = ...                  // still bad but better
    

    In this case you'll at least override the new browser API with your own. Of course now you have the issue that some other library may want to use the new API end you've effectively hidden it.

  2. (good), Don't use globals.

上面的模式可以说是ES6模块之前遗留下来的。现在,把你的库放到一个模块中可能更好。

然后你就可以将它导入到本地或至少模块级变量中。

import MyAPI from './MyAPI.js';

MyAPI.doTheThing()

注意:在这个例子中,MyAPI 是和不是全局变量。它只能被当前模块访问,所以对于该模块来说它是全局的,但对于其他模块则不是。
注2:上述建议的一个例外是标准库的 polyfill。
注3:如果不清楚,在 JavaScript 中全局变量会被分配为全局对象的属性,在浏览器中全局对象是 window。此外,添加到全局对象的属性也会成为全局变量。
示例:

console.log('foo' in window);  // prints false

console.log('foo' in window);  // prints true
var foo = 123;

window.bar = 456;
console.log(bar);  // prints 456


1
基本上,因为它们可以从页面上的任何脚本访问,并且因为您可以在相同的范围内重复它的名称。这就是为什么很多Javascript引擎使用此代码的原因:
(function(){
    var foo = 'foo',//Local
    bar = 'bar';//Local
    window.globalVar = foo + bar;//Global
})();
alert(foo);//Error
alert(bar);//Error
alert(globalVar );//'foobar'

0

你创建的全局变量可能会覆盖现有的窗口对象属性。因为全局变量是在全局上下文中访问,即window对象。


0
通常情况下,我们应该将相似的代码和变量分组,并在它们周围建立栅栏。这就是所谓的类,它真正有助于组织程序。良好的组织使得他人更容易阅读您的代码,更容易随着程序的增长维护代码,更容易编写单元测试,并且更容易重用代码和避免代码重复。
当从一位维护者的小型程序跳转到多人操作的中型或大型程序时,所有这些都是必不可少的。
全局变量没有围墙,也没有与任何其他代码分组。它们可能会影响程序的任何其他部分,而且一眼看去很难看出它们影响了哪些部分。全局变量需要在单元测试中进行模拟,并且仅从方法定义上看不清它们需要被模拟。

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