前置:


执行上下文

执行上下文EC(exec context) 就是当前代码的执行环境、作用域,和作用域链相辅相成。

ec = variable object变量对象 + 作用域链 sc + this

vo是一个底层概念,前端拿不到。

假定存在这个代码

  1. function fn(a,b){ return a+b }
  2. let r = fn(1,2)

todo 下面的描述需要整理

在这段代码中:

  • fn是全局变量,在全局上下文中
  • 调用fn。
    • 创建vo的sc,
    • 会创建一个ao,里面有arguments/a/b,作为sc上的第一个对象
  • this/fn/r 是第二个对象

作用域和作用域链

内部能访问外部,外部不能访问内部,这就是作用域。
内部使用了一个属性,内部找不到就一层层向上查找,最终找到或者报错。这就是作用域链。

如果使用 try/catch的catch eval with 会修改作用域。工作中用不到,忽略即可。

变量提升

  1. console.log(a) // undefined var a 提升
  2. var a =1
  3. fn(1) //1 function提升
  4. function fn(v){}

js在执行之前,会先预编译,把即将执行的变量和函数声明都拿出来:变量先给undefined,函数先声明等待使用。预编译完了才开始执行。

ES6增加了let和const,产生了块级作用域,作用域有了新的概念。由此,有了一个 暂时性死区Temporal Dead Zone 的概念:

在函数开头,到相关变量声明语句所在行,这个范围内不能使用let和const声明的变量。

  1. function bar1(){
  2. console.log(aa) // tdz
  3. let aa='fff'
  4. console.log(aa)
  5. }

闭包

在案例中,A函数执行返回新的函数B。

  • B通过作用域,可以访问自身变量、A的变量。
  • A函数执行,得到函数B,函数B继续执行,依然可以访问应该被销毁的A函数内容
  • 这也意味着A的活动对象没有被销毁,因为B还引用着。
  • 因为闭包会保留函数作用域,会占内存
  1. function F1(){
  2. var a =1
  3. return function(){
  4. console.log(a)
  5. }
  6. }
  7. var f1 = F1()
  8. var a = 200
  9. f1()

闭包有两个应用场景:

  • 函数作为返回值
  • 函数作为参数传递

给大家开个眼

  1. function a(){
  2. let a = 1
  3. window.b = function(){
  4. console.log(a)
  5. }
  6. }
  7. a()
  8. b()

注意,很多人误认为闭包就是,函数嵌套函数,然后返回函数,这是有误解。闭包的作用,让我们间接访问函数内部的变量。

用闭包解决问题

  1. for(var i =0;i<=5;i++){
  2. setTimeout(function timer(){console.log(i)},i*1000)
  3. }

运行会返回一堆6,很明显异步函数执行的太晚了。第一种用闭包来解决,或者用let

  1. for (var i = 1; i <= 5; i++) {
  2. ;(function(j) {
  3. setTimeout(function timer() {
  4. console.log(j)
  5. }, j * 1000)
  6. })(i)
  7. }

-1 参考

  • 参考 《前端开发核心知识进阶》
  • 高级程序设计