Javascript中变量声明语法(包括全局变量)之间的区别?

Posted

技术标签:

【中文标题】Javascript中变量声明语法(包括全局变量)之间的区别?【英文标题】:Difference between variable declaration syntaxes in Javascript (including global variables)? 【发布时间】:2011-06-19 05:33:12 【问题描述】:

声明变量有什么区别:

var a=0; //1

...这样:

a=0; //2

...或:

window.a=0; //3

在全球范围内?

【问题讨论】:

AFAIK var a = 0;通过在另一个 js 文件中声明的另一个外部 js 文件访问变量时在 IE 中不起作用 我不知道 window.a 但其他两种方式在全局范围内是相同的。 @AivanMonceller 真的吗?请给个链接。 @Raynos,我在自己的网站上体验过。 IE6 要具体。我无法让我的 var 枚举出现在外部 js 文件上,我将它作为 html 文件上的内联 javascript 引用 @Ashwini 在全局范围内,window 是全局对象(在浏览器中)。变量 a = 1;控制台.log(a); console.log(赢 【参考方案1】:

是的,有一些差异,但实际上它们通常不是很大的差异。

还有第四种方式,从 ES2015 (ES6) 开始,还有两种方式。我在最后添加了第四种方式,但在 #1 之后插入了 ES2015 方式(你会明白为什么),所以我们有:

var a = 0;     // 1
let a = 0;     // 1.1 (new with ES2015)
const a = 0;   // 1.2 (new with ES2015)
a = 0;         // 2
window.a = 0;  // 3
this.a = 0;    // 4

这些陈述解释

#1 var a = 0;

这会创建一个全局变量,它也是global object 的属性,我们在浏览器上以window 的形式访问(或通过this 全局范围,在非严格代码中)。与其他一些属性不同,该属性不能通过delete 删除。

在规范方面,它为global environment对象Environment Record 上创建一个标识符绑定。这使它成为全局对象的属性,因为全局对象是全局环境对象环境记录的标识符绑定所在的位置。这就是该属性不可删除的原因:它不仅仅是一个简单的属性,它还是一个标识符绑定。

绑定(变量)在第一行代码运行之前定义(请参阅下面的“当var 发生时”)。

请注意,在 IE8 及更早版本上,在 window 上创建的属性不可枚举(不会出现在 for..in 语句中)。在 IE9、Chrome、Firefox 和 Opera 中,它是可枚举的。


#1.1 let a = 0;

这会创建一个全局变量,它不是全局对象的属性。这是 ES2015 的新事物。

在规范方面,它在声明性环境记录上为全局环境而不是对象环境记录创建标识符绑定。全局环境的独特之处在于有一个拆分的环境记录,一个用于全局对象上的所有旧内容(object 环境记录),另一个用于所有新内容(let,@ 987654342@,以及由class 创建的函数)不会在全局对象上运行。

绑定是在其封闭块中的任何分步代码执行之前创建的(在这种情况下,在任何全局代码运行之前),但它不可访问 以任何方式直到逐步执行到达let 语句。一旦执行到达let 语句,就可以访问该变量。 (请参阅下面的“当letconst 发生时”。)


#1.2 const a = 0;

创建一个全局常量,它不是全局对象的属性。

constlet 完全相同,只是您必须提供初始化程序(= value 部分),并且一旦创建常量就不能更改它的值。在幕后,它与let 完全一样,但在标识符绑定上带有一个标志,表明它的值不能更改。使用const 为您做了三件事:

    如果您尝试分配给常量,则会导致解析时错误。 为其他程序员记录其不变的性质。 让 JavaScript 引擎在不改变的基础上进行优化。

#2 a = 0;

这会在全局对象implicitly 上创建一个属性。由于它是普通属性,您可以将其删除。我建议不要这样做,以后阅读您的代码的任何人都可能不清楚。如果你使用 ES5 的严格模式,这样做(分配给不存在的变量)是错误的。这是使用严格模式的几个原因之一。

有趣的是,同样在 IE8 和更早版本上,创建的属性不是可枚举(不会出现在for..in 语句中)。这很奇怪,特别是考虑到下面的#3。


#3 window.a = 0;

这会在全局对象上显式创建一个属性,使用引用全局对象的 window 全局(在浏览器上;一些非浏览器环境具有等效的全局变量,例如在 NodeJS 上的 global)。由于是普通属性,可以删除。

此属性可枚举的,在 IE8 及更早版本以及我尝试过的所有其他浏览器上。


#4 this.a = 0;

与 #3 完全一样,只是我们通过 this 而不是全局 window 引用全局对象。但是,这在严格模式下不起作用,因为在严格模式下的全局代码中,this 没有对全局对象的引用(取而代之的是 undefined 值)。


删除属性

“删除”或“移除”a 是什么意思?正是:通过delete 关键字(完全)删除属性:

window.a = 0;
display("'a' in window? " + ('a' in window)); // displays "true"
delete window.a;
display("'a' in window? " + ('a' in window)); // displays "false"

delete 完全删除对象的属性。您无法通过 var 间接添加到 window 的属性来执行此操作,delete 要么被静默忽略,要么引发异常(取决于 JavaScript 实现以及您是否处于严格模式)。

警告:IE8 再次出现(可能更早,IE9-IE11 处于损坏的“兼容”模式):它不会让您删除 window 对象的属性,即使您应该被允许。更糟糕的是,当您尝试时它会抛出一个异常(在 IE8 和其他浏览器中为try this experiment)。所以当从window对象中删除时,你必须要防御:

try 
    delete window.prop;

catch (e) 
    window.prop = undefined;

这会尝试删除属性,如果抛出异常,它会执行次优操作并将属性设置为undefined

适用于window 对象,并且仅(据我所知)适用于 IE8 及更早版本(或处于损坏“兼容”模式的 IE9-IE11)。其他浏览器可以删除window属性,但要遵守上述规则。


var 发生时

通过var 语句定义的变量是在执行上下文中的任何分步代码运行之前创建的,因此该属性很好地存在之前 var 声明。

这可能会令人困惑,所以让我们来看看:

display("foo in window? " + ('foo' in window)); // displays "true"
display("window.foo = " + window.foo);          // displays "undefined"
display("bar in window? " + ('bar' in window)); // displays "false"
display("window.bar = " + window.bar);          // displays "undefined"
var foo = "f";
bar = "b";
display("foo in window? " + ('foo' in window)); // displays "true"
display("window.foo = " + window.foo);          // displays "f"
display("bar in window? " + ('bar' in window)); // displays "true"
display("window.bar = " + window.bar);          // displays "b"

实例:

display("foo in window? " + ('foo' in window)); // displays "true"
display("window.foo = " + window.foo);          // displays "undefined"
display("bar in window? " + ('bar' in window)); // displays "false"
display("window.bar = " + window.bar);          // displays "undefined"
var foo = "f";
bar = "b";
display("foo in window? " + ('foo' in window)); // displays "true"
display("window.foo = " + window.foo);          // displays "f"
display("bar in window? " + ('bar' in window)); // displays "true"
display("window.bar = " + window.bar);          // displays "b"

function display(msg) 
  var p = document.createElement('p');
  p.innerHTML = msg;
  document.body.appendChild(p);

如您所见,符号 foo 定义在第一行之前,但符号 bar 没有。 var foo = "f"; 语句所在的位置实际上有两件事:定义符号,这发生在运行第一行代码之前;并对该符号进行分配,这发生在分步流程中的行所在的位置。这被称为“var 提升”,因为var foo 部分被移动(“提升”)到范围的顶部,但foo = "f" 部分保留在其原始位置。 (请参阅我贫血的小博客上的Poor misunderstood var。)


letconst 发生时

letconst 在几个方面与 var 不同。与问题相关的方式是,尽管他们定义的绑定是在任何分步代码运行之前创建的,但在到达 letconst 语句之前它是不可访问的。可访问

所以当它运行时:

display(a);    // undefined
var a = 0;
display(a);    // 0

这会引发错误:

display(a);    // ReferenceError: a is not defined
let a = 0;
display(a);

letconstvar 不同的另外两种与问题无关的方式是:

    var 始终适用于整个执行上下文(整个全局代码,或出现在函数中的整个函数代码),但 letconst 仅适用于 他们出现的地方。也就是说,var 具有函数(或全局)范围,但 letconst 具有块范围。

    在同一上下文中重复 var a 是无害的,但如果您有 let a(或 const a),则有另一个 let aconst avar a 是语法错误。

这是一个示例,说明 letconst 在该块中的任何代码运行之前立即在其块中生效,但在 letconst 语句之前无法访问:

var a = 0;
console.log(a);
if (true)

  console.log(a); // ReferenceError: a is not defined
  let a = 1;
  console.log(a);

请注意,第二个console.log 失败,而不是从块外部访问a


题外话:避免弄乱全局对象 (window)

window 对象的属性变得非常非常混乱。只要有可能,强烈建议不要增加混乱。相反,将您的符号包装在一个小包中,并在最多一个符号导出到window 对象。 (我经常不将 任何 符号导出到 window 对象。)您可以使用一个函数来包含所有代码以包含您的符号,如果您使用该函数可以是匿名的喜欢:

(function() 
    var a = 0; // `a` is NOT a property of `window` now

    function foo() 
        alert(a);   // Alerts "0", because `foo` can access `a`
    
)();

在该示例中,我们定义了一个函数并立即执行它(末尾的())。

以这种方式使用的函数通常称为作用域函数。作用域函数中定义的函数可以访问作用域函数中定义的变量,因为它们是对该数据的闭包(请参阅我贫血的小博客上的Closures are not complicated)。

【讨论】:

我可以用window['a']=0来说明我正在使用窗口作为地图吗? window 是否特殊,以至于某些浏览器不允许这样做并强迫我使用window.a 关于 #3 的一个注意事项可能值得澄清:window.a = 0; 仅适用于浏览器环境,并且仅按惯例。将全局对象绑定到名为 window 的变量不在 ES 规范中,因此在 V8 或 Node.js 中不起作用,而 this.a = 0;(在全局执行上下文中调用时)将在任何环境,因为规范 确实 指定必须有 a 全局对象。如果像 Off-topic 部分那样将代码包装在 IIFE 中,则可以将 this 作为名为 windowglobal 的参数传递,以获取对全局对象的直接引用。 @Sherlock_HJ:我添加了“在浏览器上;”这也在答案的前面,但我添加了它以防人们跳到那个。它is in the spec now;虽然它只是顺便说一句,但您不会找到不这样做的浏览器。我有点惊讶它不在Annex B。 @T.J.Crowder,所以,用var a = 0; 声明的全局变量自动成为全局对象的属性。如果我在函数声明中声明var b = 0;,它是否也是某个底层对象的属性? @ezpresso:不,是的。它们确实成为对象的属性(它们出现的 ExecutionContextVariableEnvironmentEnvironmentRecord;详情 here 和 here) ,但无法从程序代码中直接访问该对象。【参考方案2】:

保持简单:

a = 0

上面的代码给出了一个全局范围变量

var a = 0;

这段代码将给出一个在当前范围内使用的变量,并在它下面

window.a = 0;

这通常与全局变量相同。

【讨论】:

你的陈述“上面的代码给出了一个全局范围变量”“这个代码将给出一个在当前范围内使用的变量,并在它下面” ,综合起来,建议你不能使用第一行,在当前作用域访问a。你可以。此外,您对“全局变量”的使用有点偏离——您说“全局变量”的两个地方并不比您不说它的地方更全局。 global 本身意味着您可以在任何地方访问/读取/写入变量,包括我提到当前范围的地方,这很明显。如果你建议 window.a 和 'a' 在脚本中不是全局的,那么你就 100% 错了。 @Umair:“全局本身意味着你可以在任何地方访问/读取/写入变量” 对。再次,您似乎将第一个和最后一个称为比中间更“全局”,当然它们不是。 中间的被认为是在函数内部使用的,如果在主范围内使用,它们都是一样的。在函数中使用 var 是我的假设 @Umair:“在函数中使用 var 是我的假设” 啊,好吧。但这不是问题。这个问题非常清楚地表明“在全球范围内”。如果您要更改假设(这很公平,可以扩展和解释更一般的观点),您需要清楚这就是您在回答中所做的。【参考方案3】:
<title>Index.html</title>
<script>
    var varDeclaration = true;
    noVarDeclaration = true;
    window.hungOnWindow = true;
    document.hungOnDocument = true;
</script>
<script src="external.js"></script>

/* external.js */

console.info(varDeclaration == true); // could be .log, alert etc
// returns false in IE8

console.info(noVarDeclaration == true); // could be .log, alert etc
// returns false in IE8

console.info(window.hungOnWindow == true); // could be .log, alert etc
// returns true in IE8

console.info(document.hungOnDocument == true); // could be .log, alert etc
// returns ??? in IE8 (untested!)  *I personally find this more clugy than hanging off window obj

是否存在默认情况下所有变量都挂起的全局对象?例如:'globals.noVar 声明'

【讨论】:

非常好的探索。使用window.* 声明的明确指南。这个声明看起来对复制粘贴代码最安全,而且也很清楚。【参考方案4】:

基于 T.J. 的出色回答。 Crowder:(题外话:避免混乱window

这是他想法的一个例子:

HTML

<!DOCTYPE html>
<html>
  <head>
    <script type="text/javascript" src="init.js"></script>
    <script type="text/javascript">
      MYLIBRARY.init(["firstValue", 2, "thirdValue"]);
    </script>
    <script src="script.js"></script>
  </head>

  <body>
    <h1>Hello !</h1>
  </body>    
</html>

init.js(基于this answer)

var MYLIBRARY = MYLIBRARY || (function()
    var _args = ; // private

    return 
        init : function(Args) 
            _args = Args;
            // some other initialising
        ,
        helloWorld : function(i) 
            return _args[i];
        
    ;
());

script.js

// Here you can use the values defined in the html as if it were a global variable
var a = "Hello World " + MYLIBRARY.helloWorld(2);

alert(a);

这是plnkr。希望对您有所帮助!

【讨论】:

【参考方案5】:

在全局范围内没有语义差异。

但您确实应该避免使用a=0,因为您将值设置为未声明的变量。

同时使用闭包来避免编辑全局范围

(function() 
   // do stuff locally

   // Hoist something to global scope
   window.someGlobal = someLocal
());

始终使用闭包,并在绝对必要时始终提升到全局范围。无论如何,您应该对大部分通信使用异步事件处理。

正如@AvianMoncellor 提到的,有一个IE 错误,var a = foo 只声明了一个全局文件范围。这是 IE 臭名昭著的损坏解释器的问题。这个错误听起来很熟悉,所以可能是真的。

所以坚持window.globalName = someLocalpointer

【讨论】:

“在全局范围内没有语义差异。” 实际上,存在巨大的语义差异,定义属性的机制完全不同——但实际上归结为只有很小的实际差异(因为你不能deletevar)。 @T.J.克劳德我不知道。我认为变量声明是在变量对象上设置属性。不知道那些不能删除。 是的。如果您使用var,它们也会在前面定义。它们只是具有相同实际结果的完全不同的机制。 :-) @T.J.克劳德我忘了提到var 会跳转到范围的终点。

以上是关于Javascript中变量声明语法(包括全局变量)之间的区别?的主要内容,如果未能解决你的问题,请参考以下文章

javascript

javascript基础05

javascript基础05

变量的作用域JavaScript 内部函数

全局变量局部变量闭包详解

JavaScript 声明全局变量和局部变量