JavaScript中在函数体顶部声明变量的好处

7

我正在阅读道格拉斯·克罗克福德的书“Javascript:The Good Parts”。他在讲JS的范围并表示JS没有块级作用域:

在许多现代语言中,建议变量尽可能晚地在第一次使用时声明。这对JavaScript来说是错误的建议,因为它缺少块级作用域。所以最好在函数体的顶部声明函数中使用的所有变量。

然而,我无法想到一个好的例子说明这个声明是有道理的,如果能看到一些例子来验证这个声明将会非常好。


在同一作用域中使用相同的 i 变量来考虑 for 循环。首先声明 i 并重复使用它似乎更加正确或习惯化。 - elclanrs
如果你使用var声明变量,它将在局部范围内。否则它将是全局的。因此,为了明确var是局部的,请在函数开头显式声明它。 - mplungjan
1
@mplungjan,据我理解,这个问题不是关于是否声明变量,而是在函数中声明变量的位置。 - user663031
1
@Tarik,你在询问变量声明和var就是变量声明的方式,那么它怎么会是一个完全不同的话题呢? - user663031
1
在JS中,变量总是在任何操作发生之前声明。这是由运行时编译器为您完成的。 - Derek 朕會功夫
显示剩余6条评论
4个回答

6

在顶部声明变量可以帮助你避免像这样的情况:

function outer() {
  var i = 50;

  function inner() {
    alert(i);
    var i= 30;
  }
  inner();
}

outer();

许多人会期望警报显示 50,但看到 undefined 时会感到惊讶。这是因为变量 iinner 函数中声明,但直到警报后才初始化。因此,它具有完整的函数作用域,即使在初始使用之后声明。


这只是一个简单的例子,但 i 是一个常见的循环变量。C 程序员可能会看到 for(var i = ...) 并错误地认为 i 具有块级作用域。在顶部声明变量可以帮助可读性和可维护性,但我必须承认我很少在使用前声明循环变量。 - Rick Hitchcock
2
只是为了澄清,这是由于变量提升的原因,所以函数inner实际上是var i; alert(i); i = 30; - Assaf
正确。在看到@torazaburo的帖子之后,我才想起“提升”这个术语。 - Rick Hitchcock
这只是因为你使用了 var i = 30;。去掉 var 就会得到 50。当你在函数内使用关键字 var 时,它将该 var 作用域限定在该函数内。 - StackSlave
@Assaf:你的解释非常好,谢谢! - Tarik

3

将变量在函数顶部声明是一种记录所有使用变量的方式。

这也可以避免因为有人错误地想象变量是块作用域而导致混淆,例如下面的情况:

var i=0;
if (true) {
   var i=1;
}
// what is i? C programmer might imagine it's 0.

如果在声明变量时也进行初始化操作,将声明放在顶部可以避免与初始化的时间产生潜在问题:

console.log(foo);
var foo = 1;

在这种情况下,foo被提升,因此在console.log发生时声明,但尚未初始化。因此,这实际上就像
var foo;
console.log(foo); // no ReferenceError, but undefined
foo = 1;

我真的很喜欢这个答案。 - Tarik

0

从我的角度来看,由于JavaScript没有块级作用域,在函数顶部声明所有变量可以更容易地发现您不必使用的变量,因为您可以重复使用其他变量。

例如:

function test(){
    var pages = [
        'www.stackoverflow.com',
        'www.google.com'
    ];

    var users = [
        'John',
        'Bob'
    ];

    for (var page = 0; page < pages.length; page++) {
        console.log(pages[page]);
    };

    for (var user = 0; user < users.length; user++) {
        console.log(users[user]);
    };
}

可以更改为:

function test(){
    var index, pages, users;

    pages = [
        'www.stackoverflow.com',
        'www.google.com'
    ];

    users = [
        'John',
        'Bob'
    ];

    for (index = 0; index < pages.length; index++) {
        console.log(pages[index]);
    };

    for (index = 0; index < users.length; index++) {
        console.log(users[index]);
    };
}

并从内存中节省一个变量空间。

在这样一个小函数中,主要点可能不太明显,但想象一下有数千行代码的整个项目。这可以使您的代码运行更快。


你现在不应该从你的for循环中移除var吗? - user663031
这可能有些相关,但我仍然不认为这是道格拉斯推荐它的原因。如果是这样,那么我们应该始终这样做,即使在现代语言中也是如此。 - Tarik
这里也有一些想法:https://dev59.com/v2445IYBdhLWcg3wE2Ne - GramThanos
在循环中使用var。如果您的循环现在在一个函数中,并且您的增量是全局的,则可能会出现问题。您可能没有考虑到这一点,意外地重新定义了一个变量在更高级别的范围内。 - StackSlave

0

那并不总是一个好主意。它取决于你在做什么。当他们说不需要声明“顶级”变量时,意思是无所谓。但在函数内部可能有影响。请考虑以下示例:

varOne = 1; varTwo = 2; varThree = 3; // undeclared
function lameFunc(){
  var varOne = 'Something'; // does no affect varOne at the top level
  return varOne;
}

同上:

var varOne = 1, varTwo = 2, varThree = 3; // declared
function lameFunc(){
  var varOne = 'Something'; // does no affect varOne at the top level
  return varOne;
}

当然,使用关键字var更容易看到变量,并且由于在"顶层"没有副作用,因此建议使用。
请注意,无论如何,当我更改lameFunc()时,都会影响较高级别的var
function lameFunc(){
  /* varOne is effected at the higher level whether or not `var` is declared
    above as in `varOne = 1` or `var varOne = 1` */
  varOne = 'Something';
  return varOne;
}

此外,如果你在事件之外声明了一个变量,比如var someInputVal = document.getElementById('someInput').value;,然后假设你想要在某个元素的点击事件中获取该变量的值。你应该将var声明在Element.onclick = function(){/* in here */}内部,因为在点击Element之前可能会改变输入的值。如果该元素没有变成undefined,那么在处理事件的函数之外声明var someInput = document.getElementById('someInput');也是可以的,你可以像这样访问它:
var doc = document, bod = doc.body;
function E(e){
  return doc.getElementById(e);
}
var someInput = E('someInput'), someInputVal = someInput.value;
E('clickButton').onclick = function(){
  console.log(someInputVal); // will not be what document.getElementById('someInput').value is if it was changed before clicking
  var declared = someInput.value;
  console.log(declared);
}

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