JavaScript不支持块级作用域,即变量定义的作用域并不是离其最近的封闭语句或代码块,而是包含他们的函数。
bug1: 重声明
function isWinner(player,others){ var highest = 0; for(var i = 0,n = others.length;i < n; i++){ var player = others[i]; if(player.score > highest){ highest = player.score; } } return player.score > highest; }
该程序在for循环体内声明了一个局部变量player。但是由于JavaScript中变量是函数级作用域(function-scoped),而不是块级作用域,所以在内部声明的player变量只是简单地重声明了一个已经存在于作用域内的变量(即参数player)。该循环的每次迭代都会重写同一变量。因此,return语句将player看作others的最后一个元素,而不是此函数最初的player参数。
理解JavaScript变量声明行为的一个好办法是把变量声明看作由两部分组成,即声明和赋值。
JavaScript隐式地提升(hoists)声明部分到封闭函数的顶部,而将赋值;留在原地。换句话说,变量的作用域是整个函数,但仅在var语句出现的位置进行赋值。如图提供变量声明提升的可视化图。
图 变量声明提升
bug2: 变量声明也可能导致变量重声明的混淆。在同一函数中多次声明相同变量是合法的。这在多个循环时会经常出现。
function trimSections(header,body,footer){ for(var i = 0,n = header.length; i < n; i ++){ header[i] = header[i].trim(); } for(var i = 0,n = body.length; i < n; i ++){ body[i] = body[i].trim(); } for(var i = 0,n = footer.length; i < n; i ++){ footer[i] = footer[i].trim(); } }
trimSections函数好像声明了6个局部变量(3个变量i,3个变量n),但经过变量声明提升后其实只声明了2个。
换句话说,经过变量声明提升后,trimSections函数等同于下面这个重写的版本。
for(var i = 0,n = body.length; i < n; i ++){ body[i] = body[i].trim(); } for(var i = 0,n = footer.length; i < n; i ++){ footer[i] = footer[i].trim(); }
因为重声明会导致截然不同的变量展现,一些程序员喜欢通过有效地手动提升变量将所有的var声明放置在函数的顶部,从而避免歧义。
无论你是否喜欢这种风格,重要的是,不管是写代码还是读代码,都要理解JavaScript的作用域规则。
例外:异常处理。try...catch语句将捕获的异常绑定到一个变量,该变量的作用域只是catch语句块。
function test(){ var x = "var", result = []; result.push(x); try{ throw "exception"; }catch(){ x = "catch"; } result.push(x); return result; } test(); //["var", "var"]
提示:
- 在代码块中的变量声明会被隐式地提升到封闭函数的顶部。
- 重声明变量被视为单个变量。
- 考虑手动提升局部变量地声明,从而避免混淆。