在理解闭包之前我们需要先了解以下知识: 作用域与作用域链 JavaScript执行环境 垃圾回收机制 在了解以上知识后,我们再来探讨闭包的知识。
什么是闭包
闭包是指有权访问另一个函数作用域中变量的函数;使用方:一般是在函数中再返回一个函数的,例如:
function closure(){
return function(){}
}
举个实例来看一下,大家可以先算一下输出结果:
let a = 0;
let b = 0;
let A= function(a){
A=function(b){
console.log(a++ +b)
}
console.log(a++)
}
A(1);
A(2);
上面的函数虽然没有显式的返回一个函数,但是他也是一个闭包函数,因为内部的A函数是有权访问外部A函数的值的,其次A函数作为一个全局变量,在内部重新为其绑定函数后就相当于向外部返回了一个新的函数。该函数在执行过程中经过以下步骤: 1、程序开始执行,创建全局执行上下文(EC(G)),创建当前活动对象(VO(G)),VO(G)包括全局变量a = 0;b=0,A=外部A函数的指针位置。当前作用域为EC(G); 2、程序执行到A时,将会创建一个堆内存,该内存位置即为A函数指针位置,当前堆内存将包含:[[scope]] (作用域):EC(G),参数a,以及程序代码。 3、A(1)开始执行,此时将会创建一个函数上下文(EC(A1)),并创建当前活动变量:AO(A1),当前活动变量包括:a=1,A=内部函数A的指针位置,当前函数作用域链为:EC(A1)、EC(G)。函数执行完毕,输出1; 4、步骤3程序执行到A时,将会为A开辟一个堆内存,该内存包括:形参b,[[scope]]:EC(A1),以及函数内的代码。 5、步骤3执行完毕后,函数A中的形参a将变为2;形参a并未被销毁,在后续仍然可以访问。这是为什么呢?我们通过垃圾回收机制来看一下。此时外部A函数变成了内部的A函数,内部的A函数将会被放到全局,而内部A函数引用了外部A函数的形参,根据引用计数的原理,外部A函数的作用域将不会被销毁,将会被持续保存。 6、执行内部A函数,函数创建执行上下文(EC(A2)),创建活动对象(AO(A2)),a=2,b=1;输出结果为3;a将会变更为3。 7、继续执行A(x),,重复步骤6。
整个闭包中值得注意的是,当创建闭包后,函数的作用域在函数执行后没有被销毁。以上面的例子为例,内部的A将会替代外部的A指向一个函数,而内部的A在创建时就引用了外部函数的作用域,所以外部函数的引用将无法归0,外部函数的作用域也将不会被垃圾回收。
闭包的问题
正是由于闭包不会回收外部函数的变量,所以闭包也就容易引起内存泄漏。
闭包的作用
保护函数的私有变量不受外部的干扰。形成不销毁的栈内存。 保存,把一些函数内的值保存下来。闭包可以实现方法和属性的私有化
闭包的应用
1、函数作为参数
function out(){
let a = "out";
return function(){
console.log(a)
}
}
function f(fn){
let a= "test";
fn();
}
f(out())
此时会输出out,这是因为fn就是out函数的返回函数,out的返回函数的作用域链的上级就是out。如果不在out中声明a,此时输出将会报错。这是由于参数具有自己单独的作用域。
|