目录
闭包是JavaScript的一大难点,也是它的特色。很多高级应用都要依靠闭包来实现。
变量作用域
要理解闭包,首先要理解JavaScript的特殊的变量作用域。
变量的作用域无非就两种:全局变量和局部变量。
JavaScript语言的特别之处就在于:函数内部可以直接读取全局变量,但是在函数外部无法读取函数内部的局部变量。
全局变量的作用域是全局性的,即在整个JavaScript程序中,全局变量处处都在。
而在函数内部声明的变量,只在函数内部起作用。这些变量是局部变量,作用域是局部性的;函数的参数也是局部性的,只在函数内部起作用。
闭包的本质就是在函数外部 调用操作 函数内部的数据。
为了保护数据的安全性,全局变量容易造成全局变量污染,为了确保数据的安全性会将重要的数据定义成局部变量。
再通过闭包的语法形式调用使用函数内部的数据。
闭包的概念
由于实际项目的需求,我们有时候需要获取到函数内部的局部变量。正常情况下,这是办不到的。这时候我们就要想办法把函数内部的局部变量取出来,如下所示:
function fun(){
let int = 2;
return function(){
console.log(int);//2
int *= 2 ;
console.log(int);//4
}
}
上面代码中return的匿名函数,就是闭包。
由于在 JavasSript 中,只有函数内部的子函数才能读取局部变量,所以说,闭包可以简单理解成"定义在一个函数内部的函数"。
闭包可以用在许多地方,它的最大用处有两个,一个是前面提到的可以读取函数内部的变量,另一个就是让这些变量的值始终保持再内存中,不会在调用后被自动清除。
要理解闭包的第二个作用,我们就需要深入了解函数程序的执行。
深入了解函数程序的执行
首先我们先要了解一下栈和堆,在 内存中 有 两个 存储区域(栈和堆) 存储数据。
栈(操作系统):由操作系统自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
栈使用的是一级缓存, 他们通常都是被调用时处于存储空间中,调用完毕立即释放。存储的是:
基本数据类型:布尔、数值、字符串、undefined、null。
有序存储数据:先定义的数据存储在栈的下方,后定义的数据存储在栈的上方。
堆(操作系统): 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS(操作系统)回收,分配方式倒是类似于链表。
堆则是存放在二级缓存中,生命周期由虚拟机的垃圾回收算法来决定(并不是一旦成为孤儿对象就能被回收)。所以调用这些对象的速度要相对来得低一些。存储的是:
引用数据类型:函数、数组、对象。
无序存储数据:在 堆 中 开辟一个 独立的 存储空间 准备存储 引用数据类型数据,操作系统 会给这个存储空间 分配一个 内存地址,引用数据类型 变量/函数名称 存储在 栈 中,其中 存储的是 存储空间的 内存地址。
以上内容,可以用下图来解释说明一下:
函数程序的执行
封装
1、在内存的堆中开辟一个存储空间,操作系统给这个存储空间赋值内存地址,存储空间准备存储函数程序。
2、函数程序以字符串形式存储在存储空间中。
3、函数名称/变量名称 存储在内存的栈中,函数名称/变量名称 中存储的是函数的内存地址
调用执行。
1、通过栈中的 函数名称/变量名称 中存储的内存地址,找到堆中的存储空间,调用其中存储的函数的代码程序。
2、给函数的形参赋值实参。
3、预解析函数的程序
4、执行函数程序代码
函数执行开始
在堆中函数的存储空间中,再开辟一个独立的内存空间称为执行空间,专门存储函数执行时,需要的形参 变量等
函数执行结束
执行空间自动自动 销毁/释放,执行空间中存储的形参 变量等也会一起 销毁/释放。
这个过程就被称为JavaScript内存回收机制。
以上内容,可以用下图来解释说明一下:
闭包的基本原理
1、不能调用函数内部数据的原因是,函数执行结束执行空间就会被释放,其中存储的形参和变量也会被释放;如果要操作调用函数内部的形参和变量就需要执行空间不会被销毁/释放,就需生成一个不会被销毁/释放的执行空间,那就是 给函数一个返回值,函数的返回值是一个引用数据类型。如下所示:
function fun(){
const arr = [1, 2, 3, 4];
return arr;
}
const res = fun();
res中存储的是函数fun的执行结果返回值,返回的是arr中存储的数组的内存地址,数组arr的内存地址在函数外被变量存储,也就是数组arr在函数外被使用,此时函数的执行空间不会被销毁/释放,但是此时还不能直接操作变量arr。
2、看下面的代码
function fun(){
let int = 2;
return function(){
int *= 2;
if( int > 300){
console.log('王昭君');
}else{
console.log('王昭没有君啊');
}
}
}
const res = fun();
res();
int定义在函数内部,不会被全局变量污染,数据就安全了。
函数的返回值是一个匿名函数,return返回的是一个引用数据类型,执行空间就不会被 销毁.释放,变量int和return的匿名函数就会一直存在。
res中存储的是函数fun的返回值,也就是return的匿名函数。
调用res()就是执行调用匿名函数,也就是对变量int数据的操作调用。
闭包的注意点
1、由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在浏览器中导致内存泄漏。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
2、闭包会在函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象使用,把闭包当作它的公用方法,把内部变量当作它的私有属性,这时一定要小心,不要随便改变父函数内部变量的值。