var关键字的作用是什么,我何时应该使用它(或省略它)?

1664

注意:该问题是从 ECMAScript 版本 3 或 5 的视角提出的。随着 ECMAScript 6 发布新功能,答案可能会过时。

var 关键字在 JavaScript 中起到了什么作用?与之相比,letconst 又有什么区别?

var someNumber = 2;
var someFunction = function() { doSomething; }
var someObject = { }
var someObject.someProperty = 5;

someNumber = 2;
someFunction = function() { doSomething; }
someObject = { }
someObject.someProperty = 5;

你什么时候会使用其中之一,为什么/它有什么作用?


6
在链接变量声明时,逗号后面加换行符是否会影响行为?var x = 1, y = 2, [return]z = 3; - Alfabravo
6
如果不使用"var",你选择的变量名称可能会是之前定义的全局变量,这会让你处于暴露的风险中。在这里您可以查看我的悲痛历程:http://stackoverflow.com/questions/16704014/why-do-chrome-and-firefox-handle-javascript-variable-set-inside-jquery-ajax-ca - Scott C Wilson
7
@Ray Toal的瓜卡博客文章(绝对值得一读)已经迁移到http://blog.safeshepherd.com/23/how-one-missing-var-ruined-our-launch/。 - Hephaestus
2
使用 constlet 吧!var 不是现代的 JS。 - Gibolt
3
@Gibolt 但是看一下问题的日期,召唤一个2009年的问题有点不公平。尽管如此,对于可维护性来说,仍然在当前日期是有效的,因为有很多不是“现代JS”的代码存在。 - Andre Figueiredo
显示剩余3条评论
19个回答

1410
如果你在全局作用域中,那么区别不大。请阅读Kangax的答案以获取解释。
如果你在一个函数中,var会创建一个局部变量,“没有var”的情况下将查找作用域链,直到找到变量或达到全局作用域(此时将创建它):
// These are both globals
var foo = 1;
bar = 2;

function()
{
    var foo = 1; // Local
    bar = 2;     // Global

    // Execute an anonymous function
    (function()
    {
        var wibble = 1; // Local
        foo = 2; // Inherits from scope above (creating a closure)
        moo = 3; // Global
    }())
}

如果您不是在做一个任务,那么您需要使用var

var x; // Declare x

35
“not really much difference”与“No Difference”意思并不完全相同,前者表示差别不是很大,而后者则表示没有任何差别。 - Alex
69
实际上是的,这两者不同 :) 这种差异是否重要是另一个问题。请参见我在下面的答案:https://dev59.com/VnM_5IYBdhLWcg3wPAZF - kangax
6
我想这可能是Alex的观点,这就是他使用“等于”运算符来表达它的原因! - James Bedford
19
就像用电磁轨道炮朝自己射击一样...忘记在变量前面加上一个 "var ",结果修改了作用域链中的某个变量...试图说服一个Java/C/Python等开发人员相信JavaScript是有价值的。哈!与之相比,C/C++的陷阱看起来很好玩。想象一下不得不调试JavaScript...当然,有些人确实这样做了。而且有这么多的代码(而且不是简单的代码)是用JavaScript编写的... - Albus Dumbledore
9
如果您在全局作用域中,那么就没有区别。>> 在下面的答案中解释了其中的区别。 - Max Koretskyi
显示剩余10条评论

774

有所不同

var x = 1声明变量x在当前作用域(也称为执行上下文)中。如果该声明出现在函数内,将声明局部变量;如果该声明位于全局作用域,则会声明一个全局变量。

另一方面,x = 1仅仅是一个属性赋值。首先,它试图对作用域链进行解析,并查找其中是否有x。如果在作用域链的任何位置找到了它,就会执行赋值操作;如果没有找到x,那么它会在全局对象上创建x属性(全局对象是作用域链中的顶层对象)。

现在,请注意它并没有声明一个全局变量,而是创建了一个全局属性。

这两者之间的差异很微妙,可能很难理解,除非您知道变量声明也会创建属性(仅在变量对象上),并且JavaScript(即ECMAScript)中的每个属性都具有某些标志,描述它们的属性 - ReadOnly、DontEnum和DontDelete。

由于变量声明创建带有DontDelete标记的属性,因此在全局范围内执行var x = 1x = 1的区别在于,前者——变量声明——创建了不可删除的属性,而后者则没有。因此,通过这种隐式赋值创建的属性可以从全局对象中删除,而通过变量声明创建的属性则无法删除。

但这只是理论,在实践中两者之间甚至有更多差异,因为各种实现中存在各种错误(例如IE中的错误)。

希望这一切都讲得通:)


[更新2010/12/16]

在ES5(ECMAScript 5,最近标准化的第五版语言)中,有一个所谓的“严格模式”-一种选择性的语言模式,它稍微改变了未声明赋值的行为。在严格模式下,对未声明的标识符进行赋值将触发ReferenceError。这样做是为了捕获意外的赋值,防止创建不需要的全局属性。一些较新的浏览器已经开始支持严格模式。例如,请参见我的兼容性表格


3
他可能在谈论可删除的eval声明变量。我曾经写过一篇关于这个问题的博客文章,地址是http://perfectionkills.com/understanding-delete/。 - kangax
1
有点偏题,但提及此处供参考。"let"与"var"非常相似,并且在Mozilla中得到支持。主要区别在于var变量的作用域是整个封闭函数,而"let"仅限于其块。 - mac
1
@kangax所称的_DontDelete_标志的规范名称为_configurable(= false)_,您可以在Object.definePropertyObject.getOwnPropertyDescriptor中了解相关信息。 - Paul S.
很高兴看到这个答案的一个额外更新,详细说明了 let 如何影响 OP 的问题。 - Snekse
@Snekse let 不会影响答案中的 x = 1 部分,所以唯一的区别在于 var x = 1let x = 1。这已经在许多地方有记录,但主要的两个区别是 let 是块作用域并创建 TDZ(暂时性死区),这意味着你不能在声明之前使用或引用它。 - kangax
显示剩余6条评论

142

说 "局部的(local)全局的(global)的区别" 并不完全准确。

更好的理解是 "局部的(local)最近的(nearest)" 的区别。 最近的可以是全局的,但并不总是这样。

/* global scope */
var local = true;
var global = true;

function outer() {
    /* local scope */
    var local = true;
    var global = false;

    /* nearest scope = outer */
    local = !global;

    function inner() {
        /* nearest scope = outer */
        local = false;
        global = false;

        /* nearest scope = undefined */
        /* defaults to defining a global */
        public = global;
    }
}

4
最近的作用域不是你定义 var global = false;外部 作用域吗? - Snekse
1
@Snekse:当声明<code>var global = false;</code>时,“nearest”不适用。在该声明中,由于使用了“var”,因此将“global”放置在outer()的范围内。因为在inner()中没有使用“var”,所以它会更改上一级别(即outer())中的值。 - Mitch
1
我不知道你的评论会不会因为将那行代码改成 var global = local; 而改变,这种情况下 local 的最近作用域是正在被定义的“local”外部作用域。但是如果你将同一行代码改成 var global = global,那么当查找 global 的值时,最近的作用域将在全局窗口范围上一级。 - Snekse

85
当在浏览器中执行JavaScript代码时,所有的代码都被一个with语句包装起来,如下所示:
with (window) {
    //Your code
}

更多信息请参考with - MDN

var声明一个变量在当前作用域,在window内部声明它和不声明它没什么区别。

区别在于当你不直接在window内部时,比如在函数或块内部。

使用var可以隐藏具有相同名称的外部变量。这样你就可以模拟一个“私有”变量,但这是另一个话题。

经验法则是始终使用var,否则可能会引入微妙的错误。

编辑: 根据我收到的批评,我想强调以下几点:

  • var声明一个变量在当前作用域
  • 全局作用域是window
  • 不使用var会隐式地在全局作用域(window)中声明var
  • 使用var在全局作用域(window)中声明一个变量与省略它相同。
  • 在不同于window的作用域中使用var声明一个变量与不使用var不同
  • 始终显式声明var是良好的习惯


3
我没有给你的帖子点踩,但是"scope"(范围)可能比"window"(窗口)更准确。你的整个解释有点晦涩。 - Robert Harvey
6
我只是简单地称呼事物的名称,你想称它为“全局作用域”,那也可以。但从客户端的角度来看,按照惯例,它指的是窗口对象,即作用域链的最后一个元素。这就是为什么你可以在不写“window.”的情况下调用窗口中的每个函数和每个对象。 - kentaromiura
4
+1 这是一个非常好的解释——我以前从没听说过 var/no var 问题被这样描述过(无意双关)。 - doug
2
大部分的这个答案在ES6中已经被let所取代。 - Evan Carroll
5
这个回答在技术上也是不正确的,因为省略var并不声明任何变量,而是创建了全局对象上可删除的属性。此外,在ES5的"use strict"模式下,大部分的回答显然都是不正确的。此外,这个回答没有考虑到let,因为在问题被提出时,并没有提到JavaScript版本的参考(昨天添加),这意味着当时的参考标准是ECMA 262第3版。 - kentaromiura
语句“all your code is surrounded by a with statement”是完全错误的。如果这是真的,变量名将在本地作用域之前在window对象上解析。 - RobG

51

始终使用 var 关键字声明变量。为什么?好的编码实践应该足以成为原因,但省略它意味着它在全局作用域中声明(这样的变量称为“隐含”全局变量)。 Douglas Crockford 建议永远不要使用隐含全局变量,根据Apple JavaScript 编码指南

任何没有使用 var 关键字创建的变量都是在全局作用域中创建的,当函数返回时不会被垃圾回收(因为它不会超出作用域),从而可能导致内存泄漏。


20
“好的编码习惯”本身不应该成为充分的理由。这相当于“某些人在互联网上说这是我的代码应该看起来的样子”。这甚至比“我的老师说”的理由更加无效,除非有人至少隐约理解规则背后的原因。 - cHao
@cHao,我认为“良好的编码实践”始终是充分的理由,如果这是推荐的最佳实践,并且被几位JavaScript作者所推荐。 - Chris S
11
@ChrisS:不,"良好的编码实践"本身并不是理由。被认为是良好实践的原因才是重要的。除非这些作者告诉你为什么他们推荐这样做,否则他们的建议就不应该有任何分量。如果你不同意这些理由,那么你可以自由地认为这是错误的建议。如果你在从未询问原因的情况下遵循它,那就是如何开始盲目模仿的方式。 - cHao

33

以下是一个很好的例子,展示了如果不使用 var 声明本地变量时可能会遇到的问题:

<script>
one();

function one()
{
    for (i = 0;i < 10;i++)
    {
        two();
        alert(i);
    }
}

function two()
{
    i = 1;
}
</script>

(i 在每次循环迭代时重置,因为它没有在 for 循环中局部声明而是全局声明),最终导致无限循环


1
哎呀!我能想象出那个拼写错误可能引起的所有漏洞。 - BonsaiOak
3
为什么在 for 循环中将 i 作为参数传递给两次调用函数?这是否多余? - kalin
1
在one()函数中封装的two()函数中,参数被忽略了,因为two()函数是没有定义参数的。你说得很对,这个参数并不需要,因为它没有任何作用。 - KK.
1
Bug还是特性? - TheMaster

17

我认为在大多数情况下使用var更好。

局部变量总是比全局范围内的变量更快。

如果您没有使用var来声明一个变量,那么该变量将位于全局范围内。

如需了解更多信息,请在Google中搜索“JavaScript作用域链”。


如果您使用 var 关键字声明变量,它将在运行时创建,那么它不应该更慢吗?因为另一个是在解析时创建的。 - Barış Velioğlu
@RyuKaplan - 嘿,那是真的吗?我尝试过谷歌搜索但没找到任何关于这个主题的信息!你有这个断言的权威来源吗?谢谢。 - mike rodent
@RyuKaplan 解析/编译与实际运行代码是不同的。 - gcampbell

13

不要使用 var

var 是 ES6 之前声明变量的方式。现在我们已经进入了未来,你应该按此编程。

使用 constlet

const 应该用于 ~95% 的情况。它使得变量引用无法更改,因此数组、对象和 DOM 节点属性可以更改,并且应该使用 const

let 应该用于任何需要重新赋值的变量。这包括 for 循环内部。如果你在初始化后写了 varName =,请使用 let

两者都具有块级作用域,就像大多数其他语言一样。


3
将所有的 'var' 替换为 'const'(全部替换)。你很快就会注意到哪些变量被重新赋值了。如果你有太多这样的变量,那么你可能在反模式编码:大多数可重新分配的变量可以嵌入闭包或作为对象属性。如果只有一些变量需要重新赋值,则应使用 'let'。最后,如果某些变量没有用 'var' 声明,则它们仍然存在于全局空间中并且未声明,要小心。至于 @Gibolt 的评论“在 for 循环内”,推荐在“95%的情况下”避免这样的循环;数组方法非常好用。 - allez l'OM
说“在95%的情况下应该使用const”,似乎我们正在远离良好的实践,走向教条主义。 - Agamemnus
2
在一个问题上大胆而强烈地写着“不要使用var”,而替代方案是完全没有关键词,这是一种危险的答案结构和格式方式。不要低估一个人对读取你的第二段落的无兴趣状态。有些人可能因为它的结构和格式而误解了这个答案,因为他们可能很懒或匆忙。你并没有明确提到你不赞成把变量放在全局作用域中。 - Wyck
@Agamemnus,“dogma”是否指的是“常数纯洁性”争论,即const仅应用于“数学”常数(如π),或魔术数字或字符串?如果是这样:编程源自数学,包括一些术语,但仍然与之不同。在JS中,const关键字创建零个或多个不可变绑定(如果没有错误发生)。绑定是标识符和值之间的链接。_绑定_是“常量”,而不是值。在实际代码中,在95%的情况下,只是没有重新分配变量的意图,因此const确实有意义。 - Sebastian Simon

12

另一个区别,例如

var a = a || [] ; // works 

a = a || [] ; // a is undefined error.

1
你能解释一下为什么使用“var”定义的变量和没有使用“var”定义的变量都能工作吗?在使用“var”时,赋值语句右侧的变量是否在评估之前就已经创建了? - matt
6
因为var a会被提升到作用域顶部并设置为null,这声明但不初始化变量,在赋值时你引用了一个未定义的null变量,它会被评估为false,并将赋值设置为[]。在后面的代码中,你对属性a的属性a进行了赋值。你可以对不存在的属性进行赋值--在赋值时创建它,但是如果尝试读取不存在的属性,则会抛出ReferenceError - Evan Carroll
1
@EvanCarroll:它被提升到作用域的顶部,并被设置为undefined而不是null。 - Mithun Satheesh

11

使用 var 始终是一个很好的习惯,可以防止变量污染全局作用域和变量冲突,导致意外覆盖。


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