不使用var关键字声明变量

106

在w3schools上有这样的写道:

如果你声明一个变量,而没有使用“var”,那么这个变量总是会变成全局变量。

在函数内部声明全局变量有用吗?我可以想象在某些事件处理程序中声明一些全局变量,但这有什么好处呢?更好地利用RAM?

8个回答

165

不,没有RAM的好处或其他类似的好处。

w3schools所谈论的是我所谓的隐式全局变量的恐怖。考虑以下函数:

function foo() {
    var variable1, variable2;

    variable1 = 5;
    varaible2 = 6;
    return variable1 + variable2;
}

看起来很简单,但它返回NaN而不是11,这是因为在varaible2 = 6;行上有一个打字错误。并且它创建了一个名字错别字的全局变量:

function foo() {
    var variable1, variable2;

    variable1 = 5;
    varaible2 = 6;
    return variable1 + variable2;
}
console.log(foo());     // NaN
console.log(varaible2); // 6?!?!?!

这是因为该函数给varaible2赋值(请注意错别字),但varaible2没有在任何地方声明。通过 JavaScript 中作用域链的机制,这最终会成为全局对象上一个(新的)属性的隐式赋值(您可以在浏览器中使用 window 访问它,或在所有现代环境中使用 globalThis 访问)。

这只是松散模式 JavaScript 的“功能”,对完全未声明的标识符进行赋值不会出错;相反,它会在全局对象上创建一个属性,全局对象上的属性是全局变量。(直到 ES5,所有全局变量都是全局对象的属性。自 ES2015 起,另一种新型全局变量被添加到了其中,它不是全局对象的属性。全局范围的 letconstclass 创建了新类型的全局变量。)

我的示例只是一个错别字,但是如果您愿意,您当然也可以故意这样做。但我强烈建议不要这样做。相反,我建议始终使用严格模式,直接或使用 ECMAScript 模块(ESM,在 ES2015 中添加的 JavaScript 的模块系统),默认情况下它们是严格的。严格模式使对未声明的标识符进行赋值变成错误,而不是默默地创建全局变量。如果我们在使用严格模式,上面的 foo 函数的问题会更加明显和容易诊断:

"use strict"; // Turns on strict mode for this compilation unit

function foo() {
    var variable1, variable2;

    variable1 = 5;
    varaible2 = 6;                 // <=== ReferenceError
    return variable1 + variable2;
}
console.log(foo());

建议尽量避免全局变量。在浏览器中,全局命名空间已经非常拥挤,这使得很容易意外创建冲突。浏览器为DOM中的每个元素创建一个全局变量,包括大多数具有name属性的元素,并具有其自己的几个预定义全局变量(例如nametitle),这些变量与您的代码很容易发生冲突。

取而代之的是使用JavaScript模块(ESM)。模块中的顶级声明不是全局变量,它们仅对该模块中的代码全局可见。然后,您可以使用export故意公开要让其他模块能够使用的代码部分(通过import)。

在2022年,几乎总是可以使用ESM;它得到现代浏览器和Node.js的广泛支持。如果您必须针对不支持它的过时环境(例如Internet Explorer),则可以使用打包程序将您的ESM代码编译成一个捆绑包。

如果出于某种原因无法使用ESM,则可以像在模块标准化之前一样使用一个作用域函数将代码包装起来:

(function() {
    var your, symbols, here, if_they_need, to_be_shared, amongst_functions;

    function doSomething() {
    }

    function doSomethingElse() {
    }
})();

如果您这样做,您可能希望启用严格模式:

(function() {
    "use strict";
    var your, symbols, here, if_they_need, to_be_shared, amongst_functions;

    function doSomething() {
    }

    function doSomethingElse() {
    }
})();

正如之前提到的那样,使用该方法可以将对未声明标识符的赋值转换为错误(以及其他一些有用的功能)。

如果你必须将某些内容设置为全局变量,可以将其赋值给window上的属性。(在现代环境中,我会建议你将其赋值给globalThis上的属性,但如果不能使用ESM,则很可能你所针对的环境不支持globalThis。)


30

忘记使用var的副作用

暗示全局变量和显式定义的全局变量之间有一个细微的区别。这个区别在于使用delete操作符将这些变量取消定义的能力:

• 使用var创建的全局变量(在程序中在任何函数之外创建的变量)无法被删除。

• 暗示的全局变量没有使用var创建(不管是在函数内部创建的),可以被删除。

这表明暗示的全局变量在技术上不是真正的变量,而是全局对象的属性。属性可以使用delete操作符进行删除,而变量则不行:

// define three globals
var global_var = 1;
global_novar = 2; // antipattern
(function () {
   global_fromfunc = 3; // antipattern
}());
// attempt to delete
delete global_var; // false
delete global_novar; // true
delete global_fromfunc; // true
// test the deletion
typeof global_var; // "number"
typeof global_novar; // "undefined"
typeof global_fromfunc; // "undefined"

在ES5的严格模式下,对未声明变量的赋值(例如前面片段中的两个反模式)将会抛出错误。

JavaScript Patterns,Stoyan Stefanov著(O'Reilly)。版权所有2010年Yahoo!Inc.,ISBN 9780596806750。


1
在//类型变量结果中需要进行轻微修改。当我尝试在w3school编译器中运行上述变量声明时,我得到了警告(typeof global_var); //number alert(typeof global_novar); //number alert(typeof global_fromfunc);//undefined - Anurag_BEHS
@Anurag_BEHS - 不确定您在w3schools tryit中输入了什么代码,以获取global_novar的“number”,但我刚刚在那里进行了测试,并获得了与答案中显示的相同结果。我建议使用alert(delete global_novar); - 它会返回true还是false?如果返回false,则您所做的事情与此答案中显示的不同。 - ToolmakerSteve
1
关于“隐含全局变量技术上并不是真正的变量,但它们是全局对象的属性”的描述,我有不同的看法。无论是“在全局范围内使用var”还是“隐含全局变量”,都会将属性附加到window对象上。唯一的区别(如果var声明在全局范围内而不是函数内部)是,使用var时,该属性具有“configurable: false”。[MDN delete Operator] (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/delete)。 - ToolmakerSteve
然而,这并不是唯一的区别。在全局上下文中声明使用var关键字的变量会像变量一样(例如提升),而全局对象上的属性则表现得像属性一样。它们是两个不同的东西。 - Ben Aston

6
唯一使用全局变量的情况是需要在全局范围内访问它们。在这种情况下,您应该使用“var”关键字在函数外部声明它们,以明确表明您确实想要创建全局变量,而不仅仅是在尝试声明本地变量时忘记了“var”。
通常,您应该尝试将代码限定在尽可能少的全局范围内。您在脚本中使用的全局变量越多,就越难以与另一个脚本共同使用。
通常情况下,函数中的变量应该是局部的,这样当您退出函数时它们就会消失。

很奇怪的是,JavaScript允许一些没有用处但容易因打错而引起麻烦的结构。 - xralf
3
所有语言都允许被误用的结构。while (true);是一个很好的例子。 - T.J. Crowder

4

在函数内部声明变量而不使用var、let或const是没有比使用var、let或const更有用的。正如前面回答这个问题时所指出的,函数本地的隐式全局声明可能会在它们被声明的函数作用域外部造成混淆和问题。

我想谈谈w3schools引用和以前回答这个问题的一些微妙之处。

首先,如果你从未调用生成隐式全局变量的函数,你就不会生成任何隐式全局变量。这与w3schools引用中的“总是”部分相比略有不同,因为它违背了他们的陈述。

function generateImplicitGlobals(){
  x = "x";
  window.y = "y";
}

// before calling the generateImplicitGlobals function, we can safely see that the x and y properties of the window object are both undefined:
console.log("before calling the generateImplicitGlobals function, properties x and y of the window object are: " + window.x + " and " + window.y);

// before calling the generateImplicitGlobals function, we can test for the existence of global variables x and y; note that we get errors instead of undefined for both.
try{
  console.log("before calling the generateImplicitGlobals function, x is: " + x);
}
catch(e){
  console.log("before calling the generateImplicitGlobals function, an attempt to reference some global variable x produces " + e);
}

try{
  console.log("before calling the generateImplicitGlobals function, y is: " + y);
}
catch(e){
  console.log("before calling the generateImplicitGlobals function, an attempt to reference the global variable b also produces " + e);
}

Admittedly, I am sure that w3schools is aware that the implicit global declaration inside a function isn't made before the function is called, but, for folks who are new to javascript, it may not be clear from the given information.

就先前答案的细微差别而言,一旦调用generateImplicitGlobals函数后,我们可以看到尝试访问window.x属性或全局变量x返回相同的值(并且window.y属性和全局y变量返回相同的值)。当从generateImplicitGlobals函数内部或外部调用时,这些语句都是正确的。

function generateImplicitGlobals(){
  x = "x";
  window.y = "y";
  console.log("inside the function, x and window.x are: " + x + " and " + window.x);
  console.log("inside the function, y and window.y are: " + y + " and " + window.y);
}

// now, call the generator, and see what happens locally and globally.
generateImplicitGlobals();
console.log("after calling the generateImplicitGlobals function, x, window.x, y, and window.y are: " + x + ", " + window.x + ", " + y + ", and " + window.y);


4
主要问题是其他人可能已经使用了相同名称的全局变量。
然后当你改变全局变量的值时,会覆盖他们的值。
稍后当全局变量再次被使用时,它会神秘地改变。

6
“Someone else”,可能是你,但你已经忘记在其他地方使用过这个名字。 - QuentinUK
甚至您在页面上使用该“id”的元素,因为几乎所有浏览器都将它们全部扔到了“window”对象上。(我认为Firefox是唯一的例外。) - T.J. Crowder

3
有时在函数内创建新的全局可访问属性是有用的,可以通过引用window对象轻松访问所有全局声明的属性。
然而,通常情况下,将任何东西声明为全局可访问可能会导致以后出现问题,因为这些属性很容易被覆盖等。更好的方法是将值作为参数传递给函数并检索其结果。

1
我认为这可能会损害您的安全性甚至代码的稳定性。
正如上面提到的,您可能会因简单地拼写错误而犯错,解决方案是关键字"use strict"。声明此关键字将抛出错误:Uncaught ReferenceError: foo is not defined
它还涉及到安全代码: 1.编写安全代码时,我们不希望我们的变量在声明之外的任何地方被访问。不要无需声明全局变量。 2.仔细阅读警告并解决它们。使用"use strict"、JSlint和其他工具查看和解决警告,使您的代码更加优秀。

1
对于函数的参数列表,可以附加“变量”作为参数。如果给一个参数赋新值,它不会影响调用者对该参数的看法,即使是一个对象,指向该对象的变量本身也是函数唯一的。该对象的属性可以被修改,但完全替换对象对原始对象没有影响。给定命名参数赋新值会暂时替换当前范围(和派生范围)中的参数。从解释器的角度来看,参数和变量在这方面没有区别。即使调用者没有提供值,每个未使用的参数都隐含一个空变量。此外,您可以通过简单地将值分配给命名函数来创建可外部访问的“持久”变量-它们实际上是对象本身。甚至可以从函数内部完成这项工作。

  function noVars(a1,/*vars=*/v1,v2,v3) {
    if (noVars.lastA1===a1) return noVars.lastAnswer;

    noVars.lastA1=a1;

    v1=a1*a1;
    v2=v1*v1;
    v3=v2*v2*v2;

    noVars.lastAnswer = a1+v1+v2+v3;

    return noVars.lastAnswer;
  }


主要区别在于这些“持久”值在每次调用之间保留,而var、let和arguments中的值在每次调用开始时都是“空容器”。参数可以由调用者预设,否则它们将是“未定义”的。
这可能被视为滥用参数系统,但我认为这是以非标准的方式使用它。任何改变JavaScript规范的修改都会使这种方法失效,即使涉及对象,将值传递给函数始终是“按值传递”的事实也会被破坏(对象本身是引用是无关紧要的)。
这也可以起作用:

  var noVars = function (a1,/*vars=*/v1,v2,v3) {
    if (noVars.lastA1===a1) return noVars.lastAnswer;

    noVars.lastA1=a1;

    v1=a1*a1;
    v2=v1*v1;
    v3=v2*v2*v2;

    noVars.lastAnswer = a1+v1+v2+v3;

    return noVars.lastAnswer;
  };


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