- 代码例:
// 什么是闭包 function fn1() { // 定义fn1函数 let a = 2 // fn1函数体内声明变量a,值为2 let b = 'abc' // fn1函数体内声明变量b,值为'abc' let c = true // fn1函数体内声明变量c,值为true function fn2() { // fn1函数体内声明函数fn2 console.log(a) // fn2函数体内打印输出变量a console.log(c) // fn2函数体内打印输出变量c } return fn2 // 返回fn2 } fn1() // 执行fn1函数这段代码写了个fn1函数,内部声明3个变量和1个函数fn2 最后执行了fn1,却没有执行fn1内部的fn2,但fn2内部却有着2条打印输出变量a和c的语句未执行
闭包是什么? 1、闭包是“包含被引用变量/函数的对象”,上方代码中,fn1函数里的内部函数fn2内的两条语句分别引用了变量a和c,而这两个变量由一个对象所接收,这个对象就是闭包(示例:{a: 2, c: true} ) · 2、闭包存在于嵌套的内部函数中
闭包是存在于嵌套的内部函数中的某个对象 在上方代码例当中,那个对象内容是 { a: 2, c: true },该对象存在于return 返回的fn2函数之中
下方代码例展示了闭包的另一种写法
- 代码例:
// 什么是闭包-2 function fn1() { // 定义fn1函数 let a = 2 // fn1函数体内声明变量a,值为2 let b = 'abc' // fn1函数体内声明变量b,值为'abc' let c = true // fn1函数体内声明变量c,值为true function fn2() { // fn1函数体内声明函数fn2 console.log(a) // fn2函数体内打印输出变量a } function fn3() { console.log(c) // fn3函数体内打印输出变量c } return fn3 // 返回fn3 } fn1() // 执行fn1函数这边的代码例中,定义了2个内部函数,在各自的函数体内分别操作到了变量a和c,最后return 返回了fn3,那么fn3中的闭包对象的内容是什么呢?
fn3中的闭包对象 { a: 2, c: true }
虽然fn3自身的函数体中没有使用到a,但是其他函数体使用到的变量也在这个闭包对象之中。
可以这么理解
闭包对象类似于一个公共池,存放所有内部函数所使用到的数据(变量/函数)return的那个函数相当于一位代表,把这个公共池带在自己身上并且展示出来当然也可以return多个函数(多位代表),只是每位代表各自的函数体不同- 代码例:
// 如何产生闭包 function fn1() { // 函数fn1 let a = 2 function fn2() { // 函数fn1里的函数fn2 // ① 函数fn1里如果没有任何函数的话,比如把fn2整个删了,那就不存在闭包了 console.log(a) // fn2函数体内打印输出fn1作用域的变量a // ↑ 如果把上面这行代码删了,也不存在闭包了 // ② 内部函数要引用外部函数的变量/函数 } return fn2 // 返回fn2 // ↑ ③ 在外部函数的作用域内,return 返回内部函数名称 } fn1() // 执行fn1函数 // ↑ 如果把上面这行代码删了,也不会存在闭包 // ④ 必须要执行外部函数产生闭包需要同时满足条件1、2、4 1、函数内存在其他函数(函数有嵌套现象) 2、内部的函数要引用外部函数的数据(变量/函数) 3、return 内部函数名称 4、必须要执行外部函数
关于第3点的说明: 即便不return 内部函数名称,但满足1、2、4就可以“产生”闭包了,但这个闭包无法在较新版本的Chorme debug工具内查看到(2017年以前的老版本似乎可以)
如何查看闭包对象: 闭包对象可以通过Chrome浏览器的debug工具内的Sources一栏,进行断点调试时于右侧信息栏查看,比如存在于外部函数fn1里的内部函数fn2里的闭包对象,查看时信息栏内大致是以下结构:
Scope └ Local └ a: 2 fn2: { ..., ..., ..., [[Scopes]]: Scopes[2] 0: Closure (fn1){ ← 这个Closure就是闭包对象,此处的fn1就是执行的外部函数名称 a: 2 ← 内部函数fn2使用外部函数的变量a } 1: Global {...} }- 代码例:
// 常见的闭包及闭包作用 function fn1() { // 函数fn1 let a = 2 function fn2() { // 函数fn1里的函数fn2 a++ // fn2函数体内对fn1作用域的变量a自增 console.log(a) // 打印输出a } function fn3() { // 函数fn1里的函数fn3 a-- // fn3函数体内对fn1作用域的变量a自减 console.log(a) // 打印输出a } return fn3 // 返回fn3函数对象 } let f = fn1() // 声明变量f,并将fn1执行结果返回给f // 由于fn1的执行结果是返回fn3函数对象,因此执行f()相当于执行fn3() // fn1作用域内的变量a为2,而fn3里将a进行自减,最后还打印输出a f() // 打印输出a为 1 f() // 打印输出a为 0以上代码是闭包的常用方式
在函数嵌套的同时,外部函数将"内部函数名称"作为返回值返回 声明一个变量并接收该返回值由于此变量接收的是一个"函数对象",因此可以直接执行此变量但是在这边,你是否想过这2个问题: 1、fn1函数执行完毕后,函数体内部都会自动释放,包括这个变量a,但是上方代码最后却能成功让a自减并打印输出,这是为什么呢? 2、f() 是在全局执行的,为何能访问到fn1函数内部的a?
一般情况下,函数执行完都会自动释放,并且是无法在全局访问到函数内部的数据,但是利用闭包,可以解决这些问题
闭包作用
1、可以使函数内部的变量,在函数执行完后,不会自动释放,仍然存活在内存中(延长了局部变量的生命周期)2、在函数外部可以操作到函数内部的数据(变量/函数)我们可以再看一下刚才那段代码 - 代码例:
// 常见的闭包及闭包作用-2 function fn1() { // 函数fn1 let a = 2 function fn2() { // 函数fn1里的函数fn2 a++ console.log(a) } function fn3() { // 函数fn1里的函数fn3 a-- console.log(a) } return fn3 // 返回fn3函数对象 } let f = fn1() // ↑ 这行代码是关键,首先是执行了fn1函数 // fn1函数执行完后,返回fn3函数对象,确切地说,是fn3所对应的某个地址值,包含着fn3函数的一些内容 // 而这个内容之中,包含了变量a的数据 // 这个内容被变量f所引用 // 而其他没有被引用的内容,“函数名fn3,函数名fn2及其对应的地址值”,均被自动释放,成为垃圾对象了闭包是何时产生,又是何时死亡的呢
产生:在嵌套内部函数定义执行完时就产生了(不是在调用)死亡:在嵌套的内部函数成为垃圾对象时- 代码例:
// 闭包的生命周期 function fn1() { // 函数fn1 // 产生:由于函数提升,此时已经产生了闭包 let a = 2 function fn2() { // 函数fn1里的函数fn2 a++ console.log(a) } return fn2 } let f = fn1() // 变量f引用返回值fn2的内容 // ...此处对f进行一系列操作... f = null // 死亡:变量f赋值null,包含闭包的函数对象成为垃圾对象- 代码例:
// 产生多个闭包 function fn1() { // 函数fn1 let a = 2 function fn2() { // 函数fn1里的函数fn2 a++ console.log(a) } return fn2 } let f = fn1() // 变量f引用返回值fn2的内容(此处产生第一个闭包) f() // 3 f() // 4 let f2 = fn1() // 变量f1引用返回值fn2的内容(此处产生第二个闭包) f2() // 3闭包的优点在于: 1、可以从外部操作函数内部的数据 2、延长相关数据在内存中的占用时间
第2点同时也可能变成缺点,比如这个相关数据本身,非常非常大,而且就一直占用在内存里边,这时候需要去释放它
- 代码例:
// 闭包的缺点及解决方案 function fn1() { // 函数fn1 let arr = new Array[1000000] // arr是一个拥有一百万个空对象的数组 function fn2() { // 函数fn1里的函数fn2 console.log(arr.length) // 计算arr的长度 } return fn2 } let f = fn1() // 变量f引用返回值fn2的内容 f() // 第一次计算并输出arr的长度 f() // 第二次计算并输出arr的长度 f() // 第三次计算并输出arr的长度 // ...... 第n次 // 如此一来,这些数据就会变得很多而且很大 // 但是之后已经用不到这些数据了,此时需要释放掉这些数据 f = null // 让内部函数成为垃圾对象-->回收闭包- Recorded by Scorpio_sky@2020-10-24