本文深入解析JavaScript中var声明的作用域特性,指出其仅以函数为界,不具备块级作用域。通过示例对比全局与局部变量访问差异,阐明变量提升机制。重点探讨异步回调中因共享外层作用域导致的变量值错乱问题,并提供利用立即执行函数创建独立作用域的正确解决方案。
JavaScript中var声明的作用域机制与异步闭包陷阱解析
首先观察以下代码片段:
具体代码如下:
for(var i=0;i<3;i++) {
console.log(j+","+k);
for(var j=0;j<3;j++) {
var k = j+1;
}
}
console.log(i); 运行上述代码,控制台输出结果如下:
undefined,undefined
3,3
3,3
3
对于熟悉C或Java等语言的开发者而言,可能会感到困惑:为何在循环内部声明的局部变量j和k,能够在循环外部的代码中被访问?
在JavaScript中,若使用var声明的变量被视为局部变量,那么能够访问该变量的区域即为其局部作用域。以上述代码为例,在console.log执行时,j和k的作用域依然有效;而在循环外部,i的作用域同样存在。据此,有人可能武断地认为JavaScript不存在真正意义上的局部作用域。然而,事实并非如此。
二、如何获得真正的局部作用域呢?一个写法引起了我的注意
函数作用域的独立性
许多开发者可能阅读过jQuery或Ext等框架的源码,对以下代码结构感到熟悉:
代码如下:
var a = 3,b=4;
var exports = (function() {
var a = 1,b=2;
return {a:a,b:b};
})();
console.log(""+a+","+b);
console.log(exports.a+","+exports.b); 执行上述代码,输出结果如下:
3,4
1,2
这一现象并不罕见,它表明函数内部拥有独立的作用域。即在函数内部使用var声明的变量,仅在函数内部可见。各大框架采用这种写法,旨在防止内部局部变量与外部变量(包括外层局部变量和全局变量)发生冲突。
基于此,修正前文的推断:
JavaScript的作用域以函数为界限,每个函数内部拥有独立的局部作用域;而其他所有代码块(包括普通代码块、for循环、if语句、while循环等)均不具备局部作用域。使用var声明的变量可以穿透这些代码块,被外部代码访问。
三、何时报错,何时undefined?var的声明机制
变量声明与提升机制
请看以下代码验证:
代码如下:
console.log(a) 运行结果如下:
ReferenceError: a is not defined
另一种情况下的输出结果:
undefined
代码如下:
var exports = (function() {
var a = 1,b=2;
return {a:a,b:b};
})();
console.log(a); 运行结果如下:
ReferenceError: a is not defined
由此得出的猜想结论:
JavaScript引擎在执行代码前,会扫描当前作用域内的所有代码(不包括function内部代码),并记录所有var声明的变量。在代码执行至赋值语句之前,这些变量的值为undefined。随后访问变量时,引擎优先查找局部变量;若未找到,则向上层局部作用域(如闭包环境)查找,直至全局变量。若全程未找到,则抛出异常。
四、题外话:闭包+异步,变量值错乱!如何确保异步情况下局部变量当前值的传递?
异步回调中的变量共享问题
通过代码进一步验证:
代码如下:
for(var i=0;i<3;i++) {
setTimeout(function() {
console.log(i);
},1);
} 输出结果如下:
3
3
3
原因在于:在闭包异步执行时,i始终引用外层作用域的i。由于异步执行发生在循环结束后,此时i的值已变为3,因此每次打印均为3。
为解决此问题,需将i转换为局部变量。
有人可能采用如下写法:
利用立即执行函数解决异步变量错乱
代码如下:
for(var i=0;i<3;i++) {
var j = i;
setTimeout(function() {
console.log(j);
},1);
} 输出结果如下:
2
2
2
原因分析:
如前所述,j与i处于同一作用域,均为外层局部变量。在异步执行时,循环已结束,此时j的值为2(比i少一次自增),因此打印结果为2。
那么,正确的解决方法是什么?
我们知道,函数参数属于函数的局部变量。因此,一种有效方法是将局部变量作为实参传递给函数,从而实现值传递的效果。
代码如下:
for(var i=0;i<3;i++) {
setTimeout((
function(j){
return function() {
console.log(j);
}
})(i)
,1);
} 输出结果:
0
1
2
通过上述匿名函数方式,成功解决了异步情况下变量值变化的问题。尽管这已超出本文核心主题,但值得了解。
总结:
此处省略详细总结,待日后补充。
上述结论可能在RFC文档中已有记载,但阅读英文文档较为耗时,故此处仅为个人推断,敬请谅解。