一. 作用域
JS没有块级作用域,而是函数级作用域。
var name="global";
if(true){
var name="local";
console.log(name); // 输出:local
}
console.log(name);// 输出:local
for (var i = 0; i < 10; i++) {
}
console.log(i); // 输出 10
另外需要注意的是,如果不使用var声明变量,那么这个变量将是一个全局变量。(容易导致错误,因此要尽量避免不写var声明变量)
function test (){
s = "abcd";
};
test();
console.log(s); // 输出:abcd
由于这一特性,当我们直接操作全局变量容易出现命名冲突的问题,因此常使用自执行匿名函数来创造自己的作用域。
(function(){
window.test = function() {
// 对外函数,可以获取到匿名函数内的私有变量
};
})();
二. 闭包
闭包是能够读取其他函数内部变量的函数。上个例子中的test函数就是一个闭包,他可以读取到匿名函数内部的变量。
而闭包的用处主要有两个:读取函数内部的变量、函数内部变量的值始终保持在内存中。
三. 相关题目
- 经典的循环绑定事件问题
<ul>
<li>0</li>
<li>1</li>
<li>2</li>
</ul>
<script>
var items = document.querySelectorAll('li');
for(var i=0;i<items.length;i++){
items[i].onclick = function(){
alert(i)
}
}
</script>
最后不管点击哪个li元素,都会输出2。类似的题目包括下面这个
for(var i=0;i<3;i++){
setTimeout(function(){
console.log(i)
},0);
}
原因都是异步执行的函数在执行的时候,循环都已经结束了,而console.log(i)中的 i 实际上是外部的变量 i,上述代码可以被改写成如下代码,输出多少就很明显了。这块实际上不是闭包的问题,而是异步函数会在主程序运行完毕后,才会去调用的问题。
var i = 0;
i++;
i++;
i++;
setTimeout(function() {
console.log(i);
}, 0);
setTimeout(function() {
console.log(i);
}, 0);
setTimeout(function() {
console.log(i);
}, 0);
为了实现我们想要的效果,可以使用匿名自执行函数,这样每次循环时都会执行一次自执行函数,当e被传递到setTimeout中时,他就拥有了对e的引用,这个值的作用域是自执行函数,而不再是外部变量e,因此不会被循环所改变。
for(var i=0; i<3;i++) {
(function(e) {
setTimeout(function() {
console.log(e);
}, 0);
})(i);
}
也可以这么写
for(var i=0; i<3;i++) {
setTimeout((function(e) {
return function(){
console.log(e);
}
})(i), 0);
}