块级作用域和闭包

2,294 阅读3分钟

几道常见的面试题,知识点涉及块级作用域、闭包等。

块级作用域

由花括号{}包裹的部分是块级作用域,如 if(){}、for(){}等

for(let i = 0; i < 3; i++){
  console.log(i); 
} // 0,1,2
console.log(i) // error

这里let提升到块级作用域里的顶部


for(var i = 0; i < 3; i++){
  console.log(i)
}   // 0,1,2
console.log(i) //3

在ES6之前没有块级作用域,for循环属于全局作用域,都可以被访问。var提升到全局作用域的顶部,即for循环的前面

题目1:
for(var i = 0; i < 3; i++){
    setTimeout(function(){
        console.log(i);
    },200);
} // 3,3,3

看到var可知这里不是块级作用域,而是全局作用域,var i 变量提升,这里的i是全局变量的 i,即3次循环更改的都是同一个全局变量 i 的值。

setTimeout是异步操作,for循环是同步操作,需要等同步操作结束后执行异步操作。当200毫秒后执行function时,查找 i 的值,发现 i 是全局变量,此时值为3

for(let i = 0; i < 3; i++){
    setTimeout(function(){
        console.log(i);
    },200);
} // 0,1,2

看到let可知这是块级作用域,每次循环产生一个作用域,共有3个 i ,每个 i 属于每次循环产生的作用域中。

整个执行过程如下:

{
  let i=0setTimeout(function(){
    console.log(i);
  },200);
} //0

{
  let i=1setTimeout(function(){
    console.log(i);
  },200);
} //1

{
  let i=2setTimeout(function(){
    console.log(i);
  },200);
} //2

当200毫秒后执行每个function时,查找 i 的值,i 是每个块级作用域里的“全局变量”

其他办法实现块级作用域效果:
for(var i = 0; i < 3; i++){
    f(i);
}
function f(n){
    setTimeout(function(){
        console.log(n);
    },200);
}

调用函数,用函数作用域代替块级作用域,每次循环把 i 作为实参传给函数 f,执行过程如下:

// 函数的形参也是局部变量,首先声明
function f(n){
  var n=n;
  setTimeout(function(){
    console.log(n);
  },200)
}

//第一次循环时,执行f(0),即:
{var i =0;
setTimeout(function(){
    console.log(i);
  },200)
}
for(var i = 0; i < 3; i++){
    (function(j){
      setTimeout(function(){
        console.log(j);
      },200);
    })(i)
} 

使用立即执行函数,本质和上一种方法一样。把每次for循环的 i 记录下来,作为实参赋值给 j

闭包
function createFunctions(){
    var result = new Array();
    for(var i = 0; i < 10; i++){
        result[i] = function(){
            return i;
        };
    }
    return result;
}
console.log(createFunctions()[0]());   //10

执行createFunctions(),做for循环,数组result每个元素赋值为一个匿名函数。for循环结束后,i=10,调用匿名函数 function() { return i; },在函数内查找 i,没找到,因为闭包的存在,可以访问createFunctions()作用域,查找到 i 为10

解决:

1.变成块级作用域

function createFunctions(){
    var result = new Array();
    for(let i = 0; i < 10; i++){
        result[i] = function(){
            return i;
        };
    }
    return result;
}
console.log(createFunctions()[0]());   //0

看到let表明for循环变成了块级作用域,此时 i 不再是函数createFunctions内的“全局变量”。每次循环都会产生一个作用域,每个 i 值保存在每个作用域里。当调用匿名函数return i 时,在对应的作用域里查找,即:

//每次循环都会产生如下一个作用域,比如第一次循环时:
{
  let i=0;
  result[0]= function(){
    return i;
  }
}

2.使用立即执行函数

function createFunctions(){
    var result = new Array();
    for(let i = 0; i < 10; i++){
        result[i] = (function(num){
          return function(){
            return num;
          }; 
        } )(i)     
    }
    return result;
}
console.log(createFunctions()[0]());   //0

通过立即执行函数,把每一轮的 i 值以实参的形式传递给了num保存下来。当调用 function() { return i; }时,查找 i 值为num对应的值。