闭包(1)
闭包是 js 的一个难点也是它的一个特色,是我们必须掌握的 js 高级特性,那什么是闭包呢?它又有什么用呢?
闭包有 3 个特性:
① 函数嵌套函数
② 函数内部可以引用函数外部的参数和变量
③ 参数和变量不会被垃圾回收机制回收
js 的作用域分两种,全局和局部,基于我们所熟悉的作用域链相关知识,我们知道在js作用域环境中访问变量的权利是由内向外的,内部作用域可以获得当前作用域下的变量并且可以获得当前包含当前作用域的外层作用域下的变量,反之则不能,也就是说在外层作用域下无法获取内层作用域下的变量,同样在不同的函数作用域中也是不能相互访问彼此变量的,这被称作 链式作用域"结构。
那么我们想在一个函数内部也有限权访问另一个函数内部的变量该怎么办呢?出于种种原因,我们有时候需要获取到函数内部的局部变量。正常情况下,这是办不到的!只有通过变通的方法才能实现。闭包就是用来解决这一需求的,闭包的本质就是在一个函数内部创建另一个函数。
闭包就是能够读取其他函数内部变量的函数。
由于在javascript中,只有函数内部的子函数才能读取局部变量,所以说,闭包可以简单理解成“定义在一个函数内部的函数“。
所以,在本质上,闭包是将函数内部和函数外部连接起来的桥梁。
function a() { var name = 'dov'; return function(){ return name; } } var b =a(); console.log(b());
在这段代码中,a() 中的返回值是一个匿名函数,这个函数在 a() 的作用域内部,所以它可以获取 a() 作用域下变量 name 的值,将这个值作为返回赋值给全局作用域下的变量b,实现了全局变量下获取到局部变量中的变量的值
function f(){ var num = 3; return function(){ var n = 0; console.log(++n); console.log(++num); } }
var a = f()
a()
a()
一般情况下,在函数 fn 执行完后,就应该连同它里面的变量一同被销毁,匿名函数作为fn的返回值被赋值给了 a ,这个时候就相当于 a = function(){var n = 0 ...}, 并且匿名函数内部引用这 a 里的变量 num , 所以 num 变量无法被销毁,而变量 n 是每次被调用是重新创建,所以每次a执行完后它就把属于自己的变量连同自己一起销毁,于是乎最后就剩下孤零零的num,于是就产生了内存消耗的问题。
这些变量的值始终保持在内存中,不会在匿名函数调用后被自动清除。
为什么会这样呢?原因就在于匿名函数的父函数,而匿名函数被赋给了一个全局变量,这导致匿名函数始终在内存中,而匿名函数的存在依赖于f,因此f也始终在内存中,不会在调用结束后,被垃圾回收机制(garbage collection)回收。
使用闭包的注意点
(1)由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
(2)闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。
1什么是内存泄露?
1. 定义:一块被分配的内存既不能使用,也不能回收。从而影响性能,甚至导致程序崩溃。
2. 起因:JavaScript的垃圾自动回收机制会按一定的策略找出那些不再继续使用的变量,释放其占有的内存。然而由于一些原因导致在这种机制下内存管理器不能正确解读JavaScript变量的生命周期,从而没有释放其内存,而也没有再被使用。循环引用是导致以上情况的主要原因之一。
2 解决办法
常用的解决方法就是在JavaScript代码段运行完之时将形成循环引用的JavaScript对象手动设置为空,切断引用。
再来看一个经典例子-定时器与闭包
写一个for循环,让它按顺序打印出当前循环次数
for (var i = 0;i<5; ++i){ setTimeout( function() { console.log(i + ' '); },100); }
按照预期他应该一次输出1 2 3 4 5, 而结果它输出了五次 5,这是为什么呢?原来由于单线程的,所以在执行 for 循环的时候定时器 setTimeout 被安排到任务队列中排队等待执行,而在等待过程中for循环就已经在执行了,等到 setTimeout 可以执行的时候,for 循环已经结束,i 的值也已经编程到 5,所以打印出来五个 5,那么为了实现预期结果,怎么更改这段代码呢?
引入闭包来保存变量 i,将 setTimeout 放入函数中,将 for 循环中的循环值i作为参数传递,
for(var i = 0; i<5; ++i){ ( function(i){ setTimeout(function(){ console.log(i+' '); },100) })(i) }
闭包作为参数传递
var num =15; var a = function(x){ if(x>num){ console.log(x) } } void function(bb){ var num=100 bb(30) }(a)
在这段代码中,函数a作为参数传入立即执行函数中,在执行到bb(30)的时候,30作为参数传入a中,这时候if(x>num)中的num取的并不是立即执行函数中的num,而是取创建函数的作用域中的num。这里函数创建的作用域是全局作用域,所以num取的是全局作用域中的值15,即30>15,打印30
下面这个JS程序的输出是什么:
function Foo() { var i = 0; return function() { console.log(i++); } } var f1 = Foo(), f2 = Foo(); f1(); f1(); f2();
A 0 1 0
B 0 1 2
C 0 0 0
D 0 0 2
正确答案: 0 1 0
这道题考察闭包和引用类型对象的知识点:
1.一般来说函数执行完后它的局部变量就会随着函数调用结束被销毁,但是此题foo函数返回了一个匿名函数的引用(即一个闭包)它可以访问到foo()被调用产生的环境,而局部变量i一直处在这个环境中,只要一个环境有可能被访问到,它就不会被销毁,所以说闭包有延续变量作用域的功能。这就好理解为什么:
f1();
//0
f1();
//1
其实foo()返回的是一个匿名函数,所以f1,f2相当于指向了两个不同的函数对象
f2();//0