循环内部声明Javascript变量

9

我有一个习惯,可能是一种边缘强迫的习惯,但我觉得可能完全没有必要。类似这样的代码:

function abc(){
  var a,b;
  for(var i=0;i<10;i++){
    a=document.getElementsByTagName('LI').item(i).width;
    b=document.getElementsByTagName('DIV').item(i).width;
    // now do something with a and b
   }
   return;
}

我强烈建议在循环之前声明变量,而不是:

function abc(){
  for(var i=0;i<10;i++){
   var a=document.getElementsByTagName('LI').item(i).width;
   var b=document.getElementsByTagName('DIV').item(i).width;
    // now do something with a and b
   }
   return;
}

请注意第二个代码块中每次循环迭代时都使用var定义变量。我认为第一个代码块是为了可读性等最佳实践。但有时候我只是在试验一些东西,不需要遵循最佳实践。
我的问题是:
在循环内使用var关键字定义将被重新定义的变量是否有任何不适当之处?

4
不管怎样,包括 for 语句中的变量在内,var 关键字会被提升。我认为,在函数顶部声明它们(包括您不声明的那个)意味着 hoisting 不存在,并且只会让事情更难想,特别是未来的读者可能无法完全理解 JS。而且这只是噪音。 - Dave Newton
你只有在变量需要被保存在外部(全局)时才需要使用VAR。在函数内部这样做意味着无论你是否使用VAR,它都会在函数完成后消失。无论你是否在循环内部这样做都没有关系。因为它在函数内部,所以它是无用的。 - durbnpoisn
@durbnpoisn。嗯 - 也许我误解了你的意思,但那似乎与我的理解相反。如果我在函数内部不使用var关键字定义变量,它将被拉入全局范围 - 因此会创建与此问题无关的其他问题。 - dgo
@DaveNewton。那么它纯粹是为了外观而已吗?假设我在开发者控制台里进行深度修改,并且对可读性不太关心,那它从实质上来说是否相同呢? - dgo
可能是JavaScript变量在循环内外声明的问题?的重复。 - K Scandrett
2个回答

22
由于JavaScript中的变量提升,无论是将var放在函数顶部还是放在for循环内部,在执行时都没有技术上的区别。如果这是你关心的全部内容,那么你可以选择任何一种方式。
仅为了刷新记忆,JavaScript提升意味着像您的第二个代码块这样的代码会被解析,然后就像您的第一个代码块一样执行。函数中的所有var声明都会自动移动到执行之前的函数范围的顶部。对这些变量的赋值在代码中的位置保持不变 - 只有变量的声明被移动。
因此,差异更多地取决于您想让代码看起来如何。当您将var定义放在for循环内部时,它使代码看起来像变量在每次迭代for循环时都被重新创建,尽管事实并非如此。它们每次循环迭代时被赋予一个值,但并不创建新变量。如果您使用let而不是var,那么情况就会不同,因为let具有块级作用域,而var只有函数作用域。
一般来说,最好将实际需要放在循环内部的代码放在循环内部。虽然将var放在循环内或外并不会实际更改执行中的任何内容,但它只是一个好习惯的一部分,而将其他代码放在循环内或外可能会有所不同。
在您的情况下,我认为这将是更好的做法:
function abc(){
  var liTags = document.getElementsByTagName('LI');
  var divTags = document.getElementsByTagName('DIV');
  var len = Math.min(liTags.length, divTags.length);
  var a,b;

  for(var i = 0; i < len; i++){
    a = liTags[i].width;
    b = divTags[i].width;

    // now do something with a and b

   }

   return;
}

这里,你已经从循环中删除了两个调用 document.getElementsByTagName() 的代码,这将大大提高性能。


2017 年更新: JavaScript 版本 ES6 现在支持使用 constlet 声明变量。它们的作用域是块级作用域,而不像 var 那样是函数级作用域。所以如果你在 for 循环块内声明了其中一个变量,那么每次调用 for 循环都会创建一个新的独立变量。虽然这对于你展示的代码类型不会有任何显著的执行差异,但是在循环内部有引用所声明变量的异步代码时,就会产生不同。在循环体内使用 constlet 的情况下,每个异步调用都将获得其自己的变量副本,这有时非常方便。

  for(var i = 0; i < len; i++){
      let a = liTags[i].width;
      let b = divTags[i].width;
      
      $.get(someUrl).then(function(data) {
          // each call to $.get() here in the loop has it's own a and b
          // variables to use here, which would not be the case with var
      });

   }

你的回答的第一部分回答了我的初步问题,我认为。你是说这纯粹是表面的?(并不是说表面不重要,但在控制台中工作时并不关键)。但是你问题的第二部分又引发了我的一个问题。当 liTags 获取所有 LI 元素时,是否发生所有 DOM 查找?如果是这样,迭代和迭代任何其他“类似数组”的对象没有区别吗?请参阅我的评论 @bultack 的回答。 - dgo
3
getElementsByTagName() 返回一个动态的HTML集合。这意味着,它一开始就会找到所有元素,但如果在您使用数据结构时DOM发生更改,则结果将会实时更新。这既有好处也有坏处(我认为更多的是坏处),如果您在处理HTML集合时同时以影响HTML集合本身的方式改变DOM,则必须注意此问题。有时候最好将HTML集合复制到静态数组中,以防止其被更改。 - jfriend00
谢谢。这对未来的参考非常有用。 - dgo

3

我知道这个答案并不能解决你的问题,只是一个建议。但是,当你需要多次访问一个 DOM 元素时,将其放入变量中是一个好习惯。这样可以避免每次都要迭代整个 DOM

function abc() {

  var a = document.getElementsByTagName('LI'),
      b = document.getElementsByTagName('DIV');

  for ( var i = 0; i < 10; i++ ) {

    a.item(i).width;
    b.item(i).width;
    // now do something with a and b

   }

   return;

}

你似乎忽略了赋值语句中有一个“i”,所以赋值语句需要在“for”循环内部。 - jfriend00
@jfriend00。你说得对。虽然我的代码是伪代码 - 他的答案会抛出错误并且无法工作。 - dgo
@jfriend00 现在已经修复了 ;) - abaracedo

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