声明不带 var 关键字的变量

Posted

技术标签:

【中文标题】声明不带 var 关键字的变量【英文标题】:Declaring variables without var keyword 【发布时间】:2011-10-16 20:15:15 【问题描述】:

在 w3schools 上写着:

如果你声明一个变量,而不使用“var”,这个变量总是会变成 GLOBAL。

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

【问题讨论】:

【参考方案1】:

不,没有 RAM 好处或类似的东西。

w3schools 所说的是我称之为The Horror of Implicit Globals 的东西。考虑这个函数:

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 访问)上的(新)属性的隐式赋值。

这只是松散模式 JavaScript 的一个“特性”,分配给一个完全未声明的标识符并不是错误;相反,它在全局对象上创建了一个属性,而全局对象上的属性是全局变量。 (在 ES5 之前,所有全局变量都是全局对象的属性。不过,在 ES2015 中,添加了一种新的全局变量,它不是全局对象的属性。全局范围 letconstclass 创建新的全局类型。)

我的例子是一个错字,但当然,如果你愿意,你可以故意这样做。毕竟,这是语言中明确定义的部分。所以:

myNewGlobal = 42;

...任何未声明 myNewGlobal 的地方都会创建新的全局变量。

但我强烈建议不要故意这样做:它使代码难以阅读和维护,并且当 JavaScript 模块变得更加普遍和广泛时,这些代码将与它们不兼容。如果您确实需要在运行时从函数中创建全局变量(已经是一个危险信号,但有正当理由),请通过分配给 window 上的属性(或任何引用全局对象的内容)显式地执行此操作在您的环境中;在浏览器上是window):

window.myNewGlobal = 42;

事实上,我建议使用 ES5 的strict mode。严格模式使分配给未声明的标识符成为错误,而不是默默地创建一个全局。如果我们一直使用严格模式,上面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());

有点切线,但总的来说,我建议尽可能避免使用全局变量。全局命名空间在浏览器上已经非常非常混乱。浏览器使用id 为DOM 中的每个元素创建一个全局变量,为大多数使用name 的元素创建一个全局变量,并且有几个自己的预定义全局变量(如title),它们很容易与您的代码发生冲突。

相反,只需为自己定义一个很好的作用域函数并将符号放入其中:

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

...如前所述,它的优点是将对未声明标识符的赋值转换为错误(以及various other helpful things)。

请注意,在 JavaScript module 中(在 ES2015 中添加,但现在才开始流行),默认启用严格模式。 (class 定义也是如此,在 ES2015 中也是新的。)

【讨论】:

虽然我同意我不喜欢全局变量,但没有错误。您的代码只是按预期将 x + y 返回为 11。 @Roland:答案中没有出现“bug”这个词,不太清楚你指的是什么。但是隐式全局变量通常是意外创建的(拼写错误),这肯定会导致错误。这就是我使用严格模式(和/或 lint 工具)的部分原因。 如果没有bug,程序运行正常。全局变量的问题是心理问题。我在调试一个实际问题时来到这里,想知道是不是一个未声明的变量导致了它,现在我知道这不是问题所在。 @Roland:好吧,我很高兴你的问题不是这个。对很多人来说,这是因为他们不小心分配给ietm 而不是item 或类似的,并且在任何地方都没有任何迹象(除了代码不起作用)。 我费了很大的力气才发现,长答案和 w3schools 链接并没有说未声明的 var 本身就是一个错误。您对拼写错误的观察和错误(错误)的可能性应该是足够的答案。【参考方案2】:

全局变量的唯一用途是您需要全局访问它们。在这种情况下,您应该在函数外部使用 var 关键字声明它们,以明确您确实想要创建全局变量,并且在尝试声明局部变量时不要忘记 var

通常,您应该尝试对代码进行范围划分,以便在全局范围内尽可能少地需要。您在脚本中使用的全局变量越多,您将其与另一个脚本一起使用的机会就越小。

函数中的变量通常应该是局部变量,以便在您退出函数时它们会消失。

【讨论】:

奇怪的是,javascript 允许使用无用的构造,但只有在我们输入错误时才会造成麻烦。 @xralf:所有语言都允许可能被滥用的结构。 while (true); 浮现在脑海中。【参考方案3】:

有时在函数内部创建新的全局可访问属性很有用,这些属性以后可以通过引用窗口对象轻松访问(所有全局声明的属性都附加到窗口对象)。

但是,通常情况下,将任何内容声明为全局可访问的,这可能会导致以后出现问题,因为这些属性很容易被覆盖等。最好将值作为参数简单地传递给函数并检索它们的结果。

【讨论】:

【参考方案4】:

主要问题是其他人可能已经在使用同名的全局变量。

然后,当您更改全局值时,您将覆盖它们的值。

以后下次使用全局时,它会神秘地改变。

【讨论】:

“其他人”,可能是你,但你忘记了你已经在其他地方使用过这个名字。 甚至你的页面上有一个元素使用id,因为几乎所有浏览器都会将所有这些元素都放在window 对象上。 (我认为 Firefox 是唯一的坚持。)【参考方案5】:

忘记 var 时的副作用

隐含的全局变量和显式定义的全局变量之间存在细微差别。 不同之处在于可以使用删除运算符取消定义这些变量:

• 使用 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 严格模式下,对未声明变量的赋值(例如两个反模式 在前面的 sn-p) 中会抛出错误。

JavaScript 模式,作者 Stoyan Stefanov (O'Reilly)。版权所有 2010 Yahoo!, Inc., 9780596806750。

【讨论】:

在//变量结果类型中需要稍作修改当我尝试在 w3school 编译器中运行变量声明时,我得到了警报(typeof global_var); //数字警报(typeof global_novar); //数字警报(typeof global_fromfunc);//未定义 @Anurag_BEHS - 不确定您在 w3schools tryit 中输入了什么代码,以获取 global_novar 的“数字”,但我刚刚在那里进行了测试,并得到了与答案中所示相同的结果。我推荐alert(delete global_novar); - 是返回true 还是false?如果它返回false,那么您所做的事情与此答案中显示的不同。 Re“隐含的全局变量在技术上不是真正的变量,但它们是全局对象的属性”。我会用不同的方式来描述它。 “在全局范围内使用的变量”和“隐含全局变量”都将属性附加到窗口对象。唯一的区别(如果var 声明在全局范围内,而不是在函数内),是使用var,属性具有configurable: false。 MDN delete Operator. 这不是唯一的区别。在全局上下文中使用 var 声明的变量表现为变量(例如提升),而全局对象上的属性表现为......嗯,就像属性一样。它们是两种不同的东西。【参考方案6】:

我会说这可能会损害您的安全性甚至代码的稳定性。

如上所述,您可能会因为变量拼写错误而犯错,解决方法是关键字"use strict"; 如果声明了这个关键字,它会抛出一个错误:Uncaught ReferenceError: foo is not defined

它还指代安全代码: 1. 在编写安全代码时,我们不希望我们的变量在它们实际声明的地方之外的任何地方被访问。不要在没有需要的情况下声明全局变量。 2. 始终仔细阅读警告并解决它们。使用"use strict";、JSlint 和其他工具来查看和解决警告,以使您的代码更好。

【讨论】:

【参考方案7】:

在函数内部声明一个变量而不使用 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);
诚然,我确信 w3schools 知道函数内部的隐式全局声明不是在函数调用之前进行的,但是对于 javascript 新手来说,从给定的信息中可能不清楚。

关于先前答案的微妙之处,一旦调用了 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);

【讨论】:

【参考方案8】:

对于它的意义,可以将值“变量”附加到函数的参数列表中。

如果为参数分配新值,则不会影响调用者对该参数的看法,(即使使用对象,指向对象本身的变量对于函数也是唯一的。该对象的属性可以修改,但是完全替换对象对原对象没有影响)。

为命名参数分配一个新值会临时将其替换为当前作用域(和派生作用域)。

从解释器的角度来看,参数和变量在这方面没有区别。即使调用者不提供值,每个未使用的参数都隐含一个空变量

此外,您可以通过简单地将值分配给命名函数来创建外部可用的“持久”变量 - 它们实际上是对象本身。这甚至可以在函数内部完成。


  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 中的值在每次调用开始时都是“空容器”。参数可以由调用者预设,否则它们是“未定义的”。

这可能被视为滥用 agument 系统,我认为它是以非标准方式使用它。停止此工作的 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;
  ;

【讨论】:

以上是关于声明不带 var 关键字的变量的主要内容,如果未能解决你的问题,请参考以下文章

javascript 函数的变量

变量声明关键字var ,let,const

jdk11新特性——局部变量类型推断(var ”关键字”)

jdk11新特性——局部变量类型推断(var ”关键字”)

JavaScript - 在 for 循环中使用 let 和 var 关键字声明迭代变量有啥区别? [复制]

js中加“var”和不加“var”的区别