js闭包详解|青训营笔记

90 阅读4分钟

这是我参与《第四届青训营》笔记创作活动的第四天

在聊闭包之前先来了解一下函数创建的两种方式以及匿名函数的定义:

一、函数创建的两种方式

1.1函数声明

我们知道,定义函数有两种方式:函数声明和函数表达式。 函数声明的关键特点是函数声明提升,函数可以在代码执行前调用,这表明函数调用可以在函数定义之前,如下面的例子:

函数声明是这样的:
sayHi()
function sayHi(){
    console.log('Hi hi')
}
1.2函数表达式

第二种创建函数的方式就是函数表达式,函数表达式看起来就像一个普通的变量定义和赋值,即声明一个变量,将创建的函数赋值给这个变量。最常见的函数表达式是这样的

let sayHello=function (){
    console.log('Hello')
}
//这里需要注意,如果想要调用sayHello,必须要在函数定义之后
sayHello()
1.2.1匿名函数

创建一个函数并将它赋值给一个变量,这样创建的函数称为匿名函数。因为function关键字后面没有标识符。没有赋值给其他变量的匿名函数的name属性是空字符串,可以自己验证一下。所以函数表达式和JavaScript中其他表达式一样,需要先赋值再使用。而创建函数并赋值给变量的能力也可以用于在一个函数中把另一个函数当作值返回

function CreateDiffName(name){
return function(value){
    if(value){
    return value
  }
 }
}

二、什么是闭包

2.1闭包的产生

嵌套的内部函数引用了外部函数中的变量形成了闭包。在嵌套内部函数定义执行完成时就形成了闭包。

    function fn1 () {
      var a = 2
      function fn2 () {
        a++
        console.log(2);
      }
      return fn2
    }
    var f = fn1()
    f()//3
    f()//4

在这个例子中,fn1函数内部嵌套了fn2函数,fn2函数内部引用了外部fn1函数定义的变量a,形成了闭包。可以在调试工具中进行调试,当执行到f()时,发现出现了闭包。在JavaScript内部的引擎机制中,闭包在嵌套内部函数定义执行完成时就已经产生,但只有在调用外部函数的时候,才会显示出来。

image.png

在这个例子中,变量a定义在函数作用域中,当通过调用一个外部函数来返回一个内部函数时,该外部函数就已经执行结束了。根据上下文执行规则,当函数执行完成,函数上下文也执行完成,内部变量在函数调用结束后自动释放,那为什么这里还可以继续调用变量a呢?

这里就体现了闭包的作用:使用函数内部的变量在函数执行完成后,仍然存活在内存中,延长了局部变量的生命周期。函数执行完成后,函数声明的局部变量只有存在于闭包中才有可能继续存在,让函数外部可以操作到函数内部的变量或函数。

2.2闭包的回收

在程序运行时,有时会产生内存溢出或内存泄露的问题。内存溢出是一种程序运行出现的错误,当程序需要的内存超过了剩余的内存时,就会抛出内存溢出的错误。而内存泄露是指占有的内存没有及时释放,内存泄露积累多了就容易产生内存溢出的错误。常见的内存泄露:1.是产生的意外变量,2.是没有及时清理的计时器或回调函数,3,闭包

JavaScript引擎在执行垃圾回收时,会进行判断,如果引用闭包的函数是个局部变量,等函数销毁后,JavaScript引擎就会判断闭包这块内容不再被使用,但如果因为闭包的函数是全局变量,闭包就会一直存在,直到浏览器页面关闭。这样就会造成内存泄露。将创建的全局变量设置为null可以解除对函数的引用,从而让垃圾回收程序将内存释放掉。

而在《JavaScript高级程序设计》中也提到闭包会保留它们包含的函数作用域,过度使用闭包会导致内存过度占用,所以谨慎使用闭包。