JS闭包理解及使用
JS中的闭包,其本质就是在函数内部再创建一个函数。
当外部函数执行环境被销毁后,内部函数的作用域链依然保持着对外部函数活动对象的引用,简而言之,闭包就是能够读取其他函数内部变量的函数。
我们可以将闭包理解为:
- 对于外部函数f1,内部函数f2,
- 当f1终止后,f2任然对f1的AO保持访问。
因此,可以总结出闭包的三个特性:
- 函数嵌套函数
- 函数内部可以引用函数外部的参数和变量
- 参数和变量不会被垃圾回收机制回收
js的闭包与java略有不同,它有两种主要表现形式:
- 函数作为返回值
function fun1(){
var name = 'Tom';
return function(){
return name;
}
}
var b = fun1();
console.log(b());//Tom
当执行var b = fun1()时,返回的是function(){return name;}这个函数,再执行b()时,返回内部函数返回值name。
它的作用域链如下:
- 函数作为参数传递
var num = 100;
var fn1 = function(x){
if(x > num)console.log(x);
}
function(fn2){
var num = 200;
fn2(250)
}(fn1)
function(fn2)是一个自执行函数,将fn1函数传入它当作参数,在自执行函数中执行了传入的函数,所以fn1函数中的num = 200,而函数fn1的参数x = 250,所以执行if后面的输出语句得到结果250。
闭包的作用:
- 读取自身函数外部的变量——沿着作用域链去查找
- 让外部变量始终保存在内存中
我们来看一个例子:
function outer() {
var result = new Array();
for (var i = 0; i < 2; i++) {//注:i是outer()的局部变量
result[i] = function () {
return i;
}
}
return result;//返回一个函数对象数组
//这个时候会初始化result.length个关于内部函数的作用域链的值
}
var fn = outer();
console.log(fn[0]());//2
console.log(fn[1]());//2
- fn[0]执行过程:
可以看到result[0]函数的活动对象里并没有定义i这个变量,于是沿着作用域链去找i变量,结果在父函数outer的活动对象里找到变量i(值为2),而这个变量i是父函数执行结束后将最终值保存在内存里的结果;从这个步骤可以知道:js函数内的变量值不是在编译的时候就确定的,而是等在运行时期再去寻找的
那么,我们如何让result数组返回期望值呢?
我们知道,result的AO里有一个arguments,arguments对象是一个参数的集合,是用来保存对象的类数组。那么我们就可以把i当成参数传进去,这样一调用函数生成的活动对象内的arguments就有当前i的副本。
- 方法一
function outer() {
var result = new Array();
for (var i = 0; i < 2; i++) {
//定义一个带参函数
function argu(num) {
return num;
}
//把i当成参数传进去
result[i] = argu(i);
}
return result;
}
var fn = outer();
console.log(fn[0]);//0
console.log(fn[1]);//1
我们可以画出作用域链来理解方法一fn[0]的执行过程:
- 方法二:
function outer() {
var result = new Array();
for (var i = 0; i < 2; i++) {
//定义一个带参函数
function arg(num) {
function innerarg() {
return num;
}
return innerarg;
}
//把i当成参数传进去
result[i] = arg(i);
}
return result;
}
var fn = outer();
console.log(fn[0]());
console.log(fn[1]());
方法一虽然可以得到期望值但是会破坏闭包,方法二可以得到期望值并且不会破坏闭包,但是会让函数变得复杂,但是更推荐方法二。
向上面方法二这样,保证了闭包完整性,但函数较为复杂,写起来会比较困难,因此徐娅将其进行一些简化,简化后的方法二如下:
function outer() {
var result = new Array();
for (var i = 0; i < 2; i++) {
//定义一个带参函数
result[i] = function (num) {
function innerarg() {
return num;
}
return innerarg;
}(i);//预先执行函数写法
//把i当成参数传进去
}
return result;
}
闭包的好处:
- 保护函数内部变量安全,实现封装,防止变量流入其他作用域
- 维持变量,可用于做缓存
- 使用闭包时用匿名函数可以减少内存消耗
闭包的弊端:
- 变量不销毁,增加了内存消耗,严重可能造成内存泄漏——解决方案:使用完该变量后删除或者赋值null
- 闭包涉及跨作用域访问,如果使用过多,会造成性能下降——解决方案:将需要跨作用域访问的变量保存在局部变量中,每次访问时直接使用局部变量
使用闭包时请注意:
- 由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除或者赋值为null;
- 闭包会在外部函数的外面,改变外部函数内部变量的值。所以,如果你把外部函数当作对象使用,把闭包当作它的公用方法,把内部变量当作它的私有属性,一定不要随便改变父函数内部变量的值!