你真的了解闭包吗?,闭包是如何实现的?

       为什么要把作用域与闭包联系起来,因为闭包是作用域息息相关的,作用域就是产生闭包的一部分。 

一、作用域

1、什么是作用域

       作用域:作用域是在程序运行时代码中的某些特定部分中变量、函数和对象的可访问性。简单来说就是一块独立的存放空间。外层作用域无法访问内层作用域变量(除开闭包)。我们再来看看那有些作用域:

  • 全局作用域:不在大括号内或者函数内声明的变量就是全局作用域下的,也就叫做全局变量。全局作用域下声明的变量再程序任何位置都能访问。
  • 函数作用域:在函数内部的范围就是函数作用域,函数作用域内只能函数内部进行访问,无法在外部进行访问(闭包除外)。
  • 块级作用域:在大括号内的范围就是块级作用域,比如 for 循环的大括号内,if 大括号内都是块级作用域。在大括号外无法对内部进行访问(var 声明的变量除外)
  • 静态作用域:静态作用域又叫词法作用域,当前变量所在作用域被创建时就确定好了,而非执行阶段确定的,js遵循的就是静态作用域。
  • 动态作用域:动态作用域就是与代码执行顺序有关,变量所在的作用域是在代码执行的时候确定的。

2、作用域链

       作用域链其实就是作用域相互嵌套,作用域之间形成引用关系,这样就生成了作用域链。作用域链我们也可以叫做静态作用域链,因为作用域链的查找规则就是遵循的静态作用域。

我们来看下面这段代码

const age = 1
function foo() {
    console.log(age)   // 1
}
function bar() {
    const age = 2      
    foo()  
}

       为何打印输出的是1,而不是2。因为静态作用域在变量、函数在定义的时候就确定好了,而不是执行时确定好的,所以上面 foo 所在的作用域直接就是全局作用域,在查找 age 变量的时候,自己函数作用域查找不到,就会向上级作用域查找,就找到了全局作用域下的 age,而不是 bar 函数作用域下的 age。

二、闭包

1、闭包的概念

什么是闭包?

维基百科对闭包的解释:

1、闭包又称词法闭包或函数闭包;

2、是在支持头等函数的编程语言中,是实现词法绑定的一种技术;

3、闭包在实现上是一个结构体,它存储了一个函数和一个关联的环境;

4、闭包跟函数最大的区别在于,当捕捉闭包的时候,它的自由变量会在捕捉时被确定,这样即使脱离了捕捉时的上下文,它也能照常运行。

MDN对闭包的解释:

1、一个函数和对其周围状态的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包。

2、也就是说,闭包让你可以在一个内层函数中访问到期外层函数的作用域。

3、在JavaScript中,每当创建一个函数,闭包就会在函数创建的同时被创建出来。

好像还是不太明白,那我们继续往下看。

2、闭包是如何形成的

       闭包简单来说就是:函数访问了外层作用域变量就会产生闭包。为什么说函数访问了外层作用域变量就会产生闭包呢?我们来看下面例子:

function foo() {
    const age = 1
    const name = 'cj';
    return function bar() {
        const sum = '2'
        console.log(name);
    }
}
const func = foo()
func()   // 'cj'

       这不就是一个闭包吗,对的。我们先忘记它是一个,来思考一个问题:当 foo 函数调用完毕赋值给 func 后,foo 是不是立刻就会被销毁?如果被销毁了,那么 func 为什么还能获取到 return 出来的 bar 函数,和打印 name 变量。那难道 foo 函数没有被销毁?是要被销毁的,假如 foo 函数内部还有其它无关的变量、函数,那不是也会被保存在内存中,这样会带来性能问题。那内部是怎样实现及销毁 foo 函数,又能保存需要访问的其内部变量和函数呢?

       这就是闭包所要解决的问题,有了闭包我们就能够保存要被销毁的作用域中还需要用到的变量、函数不被销毁。那闭包到底是怎么实现的?闭包其实就是将函数引用的父作用域中的变量给函数打包带走。

       那怎样让函数打包带走?这个时候就用到了函数的一个属性:[[Scopes]],它就是用来放函数打包带走用到的环境。这个属性得是一个栈,因为函数有子函数、子函数可能还有子函数,每次打包都要放在这里一个包,所以就要设计成一个栈结构。我们来看看 [[Scopes]] 到底是什么样子的:

我们看见了 [[Scopes]] 属性上有Closure、Script、Global。

  • Closure:这就是打包外部引用变量的 Closure包,相当于像拥有了需要的外部作用域。
  • Script:调用函数本身作用域,也就是 return 的子函数作用域。
  • Global:全局作用域。

       这时我们也大致知道了,js 引擎会将这三个串联起来,形成新的作用域链。而 Global 与 Closure 就形成函数所需要的外部环境,自然就能够正常的运行了。使得不管函数走到哪里,随时随地可以访问带外部环境。

1、js 引擎如何知道那些引用会被打包带走呢?

答:js 引擎在解析代码时,会进行 parse 解析步骤,这个时候就知道需要用到那些外部引用了。

2、那么又是在什么时候添加到函数的 [[Scopes]] 属性上的?

答:在创建函数的时候保存到函数属性上的,创建的函数返回的时候会打包给函数。

现在我们回过头再看维基百科和MDN对闭包的解释是不是就明白说的什么了。

3、闭包的好处

闭包有什么好处呢?我们知道闭包就是解决父作用域销毁,子函数与引用到的外部变量依然会进行保存。在我们的实际场景中就有很多例子:

  • 防抖:需要保存上一次的定时器,这样才能知道在上一次规定的定时器时间内是否再次触发,如果触发了就重新定义一个新的定时器。
function debounce(fn) {
      // 创建一个标记用来存放定时器的返回值      
      let timeout = null;      
      return function() {
           // 每次当用户点击/输入的时候,把前一个定时器清除        
           clearTimeout(timeout);        
           // 然后创建一个新的 setTimeout,        
           // 这样就能保证点击按钮后的 interval 间隔内        
           // 如果用户还点击了的话,就不会执行 fn 函数        
           timeout = setTimeout(() => {
                  fn.call(this, arguments);        
           }, 1000);      
       };    
}
  • 节流:保存上一次的变量开关,不然无法确定在规定的时间内有没有再次触发
function throttle(fn) {
    // 通过闭包保存一个标记      
    let canRun = true;      
    return function() {
        // 在函数开头判断标志是否为 true,不为 true 则中断函数        
        if(!canRun) {
            return;        
        }        
        // 将 canRun 设置为 false,防止执行之前再被执行        
        canRun = false;               
        setTimeout( () => {
             fn.call(this, arguments);          
             // 执行完事件(比如调用完接口)之后,重新将这个标志设置为 true          
             canRun = true;        
        }, 1000);      
    };    
}
  • 柯里化:让函数记住一部分的参数
function foo(x){
    return function(y){
        return function(z){
            return x+y+z
        }
    }
}
const sum = foo(1)(2)
sum(3)

4、闭包的坏处

       闭包的特点就是保存,不被销毁。但是这样可能会造成内存泄漏,GC(垃圾回收机制)无法回收的情况。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值