JS的作用域和闭包

2018/01/16

一. 作用域

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函数就是一个闭包,他可以读取到匿名函数内部的变量。

而闭包的用处主要有两个:读取函数内部的变量、函数内部变量的值始终保持在内存中。

三. 相关题目

  1. 经典的循环绑定事件问题
<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);
}

文章导航