闭包,块级作用域,执行上下文

159 阅读2分钟

看到一篇讲闭包的文章,有如下代码,这是讲块级作用域一个很经典的代码

function t() {
  for(var i = 0;i<2;i++) {
    var res = i
    let block = i
    setTimeout(()=> {
      console.log(" this is res :" + res)
      console.log(" this is block :" + block)
    },0)
  }
  // res可以输出
  console.log(res);
  // console.log(block);
  console.log(i);
}

文章说,for中使用let创建了块级作用域,导致产生不同的执行上下文

在最开始我想,变量res在词法环境中,两次定时器执行的函数(闭包)都共享了这个变量(这里两个Closure指向同一地址),而变量block应该在变量环境中但是怎么总觉得怪怪的呢?

首先我们要明确几点

  • for(){},这里的大括号并不能创建执行上下文,执行上下文只包含三种“全局执行上下文”,“函数执行上下文”,“eval执行上下文”
  • for(){}会创建临时的文本执行环境
  • 上面的代码等价于
function t() {
  {
    var res = 0
    let block = 0
    setTimeout(()=> {
      console.log(" this is res :" + res)
      console.log(" this is block :" + block)
    },0)
  }
  {
    var res = 1
    let block = 1
    setTimeout(()=> {
      console.log(" this is res :" + res)
      console.log(" this is block :" + block)
    },0)
  }
  console.log(res);
  // console.log(block);
  console.log(i);
}

针对这里在执行时候到底发生了什么?块级作用域和闭包和作用域又是怎么一回事?我们可以打断点进行观察

下图为运行完block的赋值时,我们可以看到左边变量有三个对象,Block表示块级作用域,Local表示当前作用域,Global表示全局作用域。

image.png 我们在闭包打断点,这里是其作用域,Closure表示闭包

image.png 稍微改写一下代码,可以得到这样的结果

image.png 在函数m的[[scope]]中存在这三个对象,[[scope]]是函数的私有对象,在函数申明初始化时,根据其上下文生成了。这是函数作用域链构建的第一步。

最后我在这个函数打断点,解决了我的一些疑惑

function t3() {
  var res = 0
  {
    let m = 3
    let h = 4
    let f1 = function() {
      console.log(m);
    }
    function f2() {
      console.log(m);
    }
    f1()
    g2()
  }
  let no = "hh"
  let block = 1
  setTimeout(()=> {
    console.log(" this is res :" + res)
    console.log(" this is block :" + block)
  })
}
t3()
  • 在运行到{},变量对象(作用域)里就包含了Block,里面的变量在该区域内可以访问了,当大括号结束,这个临时的空间消失
  • 两个函数,一个是let f1一个是直接申明,直接申明的函数相当于 var f2,所以f2在外层作用域
  • 查看f1 的Block可以看到只含有变量m,没有变量h。说明其也和Closure一样,只存储函数中使用到的变量
  • 这里的let block没有被大括号圈起来,所以在Loacl中,而不在Block中。(待查证问题:这里的let到底有没有创建块级作用域啊??)