作用域与作用域链与闭包

203 阅读3分钟

作用域的分类

1.局部作用域

(1)函数作用域

特点:

  • 函数内部声明的变量只能在函数内部使用,函数外部无法访问,不同函数间内部声明的变量无法互相访问
  • 函数参数也是函数内部的局部变量
  • 函数中声明的所有变量都将被提升至函数顶部(var,let,const),但是var与后两个略有不同
  • 函数执行后由于垃圾回收机制变量被清空

(2)块级作用域(被{}包裹)

if(){},for(){}等 let,const声明的变量具有块级作用域,var没有,也有变量提升

2.全局作用域

html文件中的 script标签和js文件,全局作用域中声明的变量可以被其他作用域所访问,同样有变量提升

作用域链

在定义一个函数A时,js会保存一个作用域链,当函数A被调用时,js将创建一个新的对象来存储A中的局部变量,并把这个新创建的对象添加至保存的作用域链上,同时创建一个更长的作用域链

简单来说作用域链就是作为一种变量查找的机制, 当函数执行时会优先查到当前作用域内的变量,如果该函数作用域中用到了某个变量但是没有在该作用域内申明,就沿着作用域链往父级作用域中查找,直到找到全局作用域(一种从下往上的查找机制)

这里要注意的是函数的作用域链是在函数被定义时就已经确定的,和函数的执行上下文无关

因此子作用域可以访问父作用域中的变量,而父作用域不能访问子作用域中的变量

3.闭包

(1)什么是闭包

一个可以读取使用其他函数内部变量的函数

闭包的构成简单来看可以看成一个内部函数+使用外部变量

 function a(){
        let a = 1
        function b(){
            console.log(a);
        }
        b()
      }
      a()

image.png

(2)闭包的作用

  • 闭包使一个函数内部的变量可以被读取
  • 让变量的值始终保存在内存中直到关闭页面
  function counter(){
        let n = 0
        return {
            count:function(){
                return n++
            },
            reset:function(){
                n = 0
            }
        }
      }
      let a = counter(),b = counter()//创建两个计数器
      a.count()//0
      b.count()//0
      //以上两个互不干扰
      a.reset()
      a.count()//0 c被重置
      b.count()//1

  • 这段代码中这两个方法都可以访问相对于count和reset函数的外部私有变量n,因此是一个闭包
  • 每次调用counter都会创建一个新的作用域链和一个新的私有变量,所以调用两次会得到两个计数器对象,不会相互影响

闭包的缺点:导致内存泄漏,如果闭包中引用的外部变量是一个对象,而这个闭包又长时间存在于内存中,那么标记清除法就始终会"到达"这个对象,因此就不会被垃圾回收机制所回收

  • 闭包会导致我们持有不再需要的函数引用,会导致与函数相关联的词法环境无法被垃圾处理器所销毁,导致内存泄漏
  • 当多个函数共享一个词法环境时,会导致词法环境膨胀,从而导致出现无法触及到的也无法被回收的内存空间, 比如说如果只有一个函数用到了词法环境中的一个内容,而最后这个函数又没被返回给后续的使用,返回了其他的函数,这样相关的内容就不会被清理,导致内存泄漏