JS闭包

51 阅读4分钟

闭包的定义

这里先来看一下闭包的定义,分成两个:在计算机科学中和在JavaScript中。

在计算机科学中对闭包的定义(维基百科)︰

  • 闭包(英语:Closure),又称词法闭包(Lexical Closure)或函数闭包(function closures)。
  • 是在支持头等函数的编程语言中,实现词法绑定的一种技术;
  • 闭包在实现上是一个结构体,它存储了一个函数和一个关联的环境(相当于一个符号查找表);
  • 闭包跟函数最大的区别在于,当捕捉闭包的时候,它的自由变量会在捕捉时被确定,这样即使脱离了捕捉时的上下文,它也能照常运行。

MDN对JavaScript闭包的解释:

  • 一个函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包(closure)
  • 也就是说,闭包让你可以在一个内层函数中访问到其外层函数的作用域;
  • 在JavaScript中,每当创建一个函数,闭包就会在函数创建的同时被创建出来;

理解和总结:

  • 一个普通的函数function,如果它可以访问外层作用域的自由变量,那么这个函数和周围环境就是一个闭包;
  • 从广义的角度来说: JavaScript中的函数都是闭包;
  • 从狭义的角度来说: JavaScript中一个函数,如果访问了外层作用于的变量,那么它是一个闭包;

没有闭包的局限性

var message = "hello"

function foo(){
console.log(message)  //如果没有闭包则无法访问到全局的message
}

按照JS运行原理message的调取会根据作用域链依次查找。其实作用域链的存在主要是为了实现闭包功能的。如果没有闭包的概念,就像上一个例子中message无法查找到上一个作用域的值,要想在函数中使用message则需要在入参中传入message这样就十分的不方便了。

上面提到了闭包在实现上是一个结构体,它存储了一个函数和一个关联的环境,在这个例子里foo函数和他关联的环境就形成了一个闭包,让他可以访问作用域链上的值。

内存泄漏



function createCircleCount(type){
    function circleCount(r){
        if(type===1) return 3.14*2*r
        if(type===2) return 3.14*r*r
    }
    return circleCount
}

var perimeter=createCircleCount(1)   //返回周长计算方法
var area = createCircleCount(2)      //返回面积计算方法

permieter(2)
area(2)

执行全局代码前创建的GO:

GO
createCircleCountoa001(函数对象内存地址)
perimeterundefined
areaundefined

执行var perimeter=createCircleCount(1)时产生了函数执行上下文,关联了AO对象

AO
arguments传入的参数
circleCount0b001(函数对象的内存地址)
type1

然后将circleCount的内存地址赋值给GO的perimeter。

执行var area=createCircleCount(2)时产生了函数执行上下文,关联了AO对象

AO
arguments传入的参数
circleCount0c001(函数对象的内存地址)
type2

然后将circleCount的内存地址赋值给GO的area。

执行全局代码时,将变量赋值以及其他函数调用,GO变成

GO
createCircleCountoa001(函数对象内存地址)
perimeterob001(函数对象内存地址)
areaoc001(函数对象内存地址)

由此可见目前GO指向的有perimeter和area这几个函数地址,这几个函数执行的时候也有关联对应的AO,如果后续不再需要使用到这两个函数,根据可达性原则,根可以到达这几个地址,所以这些AO并不会被垃圾回收,这样就造成了内存泄露,需要我们进行手动的释放。

perimeter = null
area = null

将GO的perimeter、area指向置为null。这样这两函数关联的AO对象以及AO对象里面存放的函数对象内存地址所指向的对象以及作用域链这些个对象都可以被释放掉。

AO对象不会被销毁时,是否里面的所有属性都不会被释放?

  function createCircleCount(type){
          var message="hello"
            function circleCount(r){
                debugger
            if(type===1) return 3.14*2*r
            if(type===2) return 3.14*r*r
            }
        return circleCount
     }

上面这个例子createCircleCount的AO对象按理说应该有tpye、message两个属性。但是事实上V8将message给释放掉了。

image.png

image.png

闭包的特点:

让外部访问函数内部变量成为可能;

可以避免使用全局变量,防止全局变量污染;

可以让局部变量常驻在内存中;

会造成内存泄漏(有一块内存空间被长期占用,而不被释放)