1.什么是闭包?
讲白了就是 利用一种方式实现访问局部变量的功能;我觉得它就是一个函数,一个怎样的函数呢?有权访问另一个函数作用域中的变量的函数,在JS中,只有函数内部的子函数才能读取函数的局部变量,所以,闭包嘛,可以理解成" 一个函数内部的函数",一句话概括:一个内部函数被其外部函数之外的变量引用时就形成了闭包。那说到闭包,我们必须得扯一扯作用域的问题...
此处补充一下自由变量:在一个作用域中使用未在该作用域中声明的变量。在下面提到....
function mytest( ){
var test=10;
return function( ){
test++;
alert(test);
}
}
var atest = new mytest( ); //引用返回的函数
atest( ); // 11
atest( ); // 12
从以上给出的定义和栗子上来看,所有的函数都可以是闭包。
当一个函数在调用时,引用了不是自己作用域内定义的变量(通常称其为自由变量),
则形成了闭包;闭包是代码块和创建该代码块的上下文中数据的结合。
通过测试结果我们可以发现, 闭包会使该函数引用的变量一直在内存中,原因是什么呢?
简单的解释就是因为这个返回的函数引用了变量test;当浏览器解析到var atest=new mytest();
这一行且mytes()函数执行完毕,准备内存释放时,发现所返回的函数引用了test变量。
从而该出栈的并没有出去;
想完全弄清楚这个问题,我们还需要进一步理解AO(活动对象)和VO(变量对象)以及作用域链、执行上下文的问题。
2.作用域是什么?
它就是变量和函数的可访问范围,顾名思义,作用区域;也就是说作用域控制着函数的可见性和生命周期;
在JS中,作用域分为全局作用域和函数作用域(当然ES6又加了一个块级作用域,更符合正常人思维,比如栗子1)
当我们执行一段js代码时,js引擎会为我们创建一个作用域又称为执行上下文,在页面加载后会首先创建一个全局作用域,然后每执行一个函数,会建立一个对应的作用域。所以js拥有全局作用域和函数作用域。
栗子1:没有块级作用域,会带来以下问题:
--------变量提升导致内层变量可能会覆盖外层变量
var i = 5;
function func() {
console.log(i); //因为在func这个作用域内,后面已经var了i这个变量(预解析声明为undefined)
if (true) {
var i = 6;
}
}
func(); // undefined
--------用来计数的循环变量泄露为全局变量
下面代码中,变量i只用来控制循环,但是循环结束后,它并没有消失,泄露成了全局变量。
for (var i = 0; i < 10; i++) {
console.log(i);
}
console.log(i); // 10
全局作用域:在js中其实就是windows,
a.最外层函数和最外层函数外面定义的变量拥有全局作用域;
var authorName="Rita溪";
function doSomething(){
var blogName="xrr22223";
function innerSay(){
alert(blogName);
}
innerSay();
}
alert(authorName); //Rita溪
alert(blogName); //脚本错误
doSomething(); //xrr22223
innerSay() //脚本错误
b.所有未定义直接赋值的变量自动声明为拥有全局作用域;
function doSomething(){
var authorName="Rita溪";
blogName="xrr22223";
alert(authorName);
}
doSomething(); //Rita溪
alert(blogName); //xrr22223
alert(authorName); //脚本错误
c.所有Windows对象的属性拥有全局作用域;如window.name、window.location、window.top等
函数作用域:声明在函数内部的变量,和全局作用域相反,局部作用域一般只在固定的代码片段内可访问到,
最常见的例如函数内部 ;
当函数被创建(不是执行)时,会将this.arguments(形参类数组),命名参数和该函数中的所有局部变量添加到该作用域中;
当运行时,上下文被销毁,活动也会被销毁;
闭包形成的原因其实就是因为活动对象被引用着无法销毁而导致的;
所以全局作用域的优点就是可以重复使用,随处可用;但是会造成全局污染,因为他一直在内存中被引用,无法释放;
函数作用域优点的话,就是不会污染全局,但同时它又不能被重复使用,只可以在函数内部使用。
那为什么函数可以构成闭包?
闭包就是一个具有封闭和包裹功能的结构,是为了实现具有私有访问空间的函数的,因为函数内部定义的数据函数外部无法访问,也就是说函数具有封闭性,函数可以封装代码)(包裹性),所以函数可以构成闭包;
因为函数是JS中唯一拥有自身作用域的结构,因此闭包的创建依赖于函数
3.为什么要使用闭包?什么时候要用闭包?( 当我们需要重用一个对象又保护对象不被污染篡改时)
全局变量:可以重用,但是过多会造使用成全局污染,而且容易被修改。
局部变量:仅在函数内使用,当函数执行完毕时就会被销毁,不会造成全局污染,也不会被篡改,不可以重用。
哈哈哈,那闭包的出现就正好 结合了全局变量的优点和局部变量的优点,它可以使已经执行结束的函数中的局部变量保存在内存中并且可以重复访问使用。
4.那接下来就该说说作用域链的事了
JS引擎在查找变量的时候,是在整个作用域中查找的,在当前作用域中找不到它,会继续往上一层作用域中查找,直至到顶层作用域即全局作用域,而这一个过程就产生了作用域链(当然如果这个过程没有找到,就会抛出引用错误的异常)
注意:作用域链是在执行上下文的时候产生的。
执行上下文整体流程如下:
1、全局执行上下文
创建global.VO
2、全局变量的赋值 | 调用函数()(激活)
激活函数后,会得到当前的AO,其中有内部函数的声明、内部变量的声明、形参
3、进入所激活的函数的上下文
进行所在作用域链上的变量的赋值 各种运算 (作用域链包含全局的VO,和当前执行上下文的AO)
4.a、若在函数中有内部函数调用(或自执行),重复3;
4.b 若返回一个函数(或其引用),且该函数有对自由变量的引用-->形成闭包-->作用域链机制依然有效-->当前已压入执行上下文堆栈的FunctionContext不会出栈;-->回到2;
4.c 正常return或正常结束,FunctionContext出栈;-->回到2;
5.所有代码执行完毕,程序关闭,释放内存。
执行上下文的生命周期在这啦→→
首先函数创建时:
会进行以下动作------生成变量对象--->建立作用域链---->确定this指向。
接下来函数执行时:
会进行以下动作-----变量赋值----->函数引用(执行完毕后出栈,等待被回收)------>执行其他代码
4.好了,该回归到闭包了
那我们用闭包到底有什么用处呢?
首先最直接的就是它可以读取函数内部变量,让一个变量(被引用的变量)长期驻扎在内存中,而又不会污染全局变量;
但是呢,又有一定的缺陷,因为它让函数中的这些变量都被保存在内存中,内存消耗很大,会造成内存泄露,所以不能滥用闭包,会造成网页性能问题;
解决::在退出函数之前将不使用的局部变量删除; 变量=null;