本文深入解析JavaScript闭包的核心概念,通过对比不同代码写法阐明闭包的形成机制。重点剖析循环中创建函数导致的变量共享陷阱,解释引用计数与垃圾回收机制的关系,帮助开发者理解闭包在封装私有变量时的优缺点及潜在内存消耗问题。
闭包的基本概念与示例
何为闭包? 观察以下代码示例:
function a(){
var n = 0;
function inc() {
n++;
console.log(n);
}
inc();
inc();
}
a(); //控制台输出1,再输出2
逻辑清晰。接着分析另一段代码:
function a(){
var n = 0;
this.inc = function () {
n++;
console.log(n);
};
}
var c = new a();
c.inc(); //控制台输出1
c.inc(); //控制台输出2
逻辑清晰。
何为闭包?此即闭包之定义!
能够访问其他函数作用域内变量的函数,即为闭包。此处 inc 函数访问了构造函数 a 内部的变量 n,从而构成了闭包。
再观察一段代码:
function a(){
var n = 0;
function inc(){
n++;
console.log(n);
}
return inc;
}
var c = a();
c(); //控制台输出1
c(); //控制台输出2
执行流程如下:
语句 var c = couter() 中,couter() 返回函数 inc,故该句等价于 var c = inc;
语句 c() 等价于 inc(); 需注意,函数名仅为标识(指向函数的指针),而括号 () 才是执行函数的操作符。
后续三句可翻译为:var c = inc; inc(); inc();,这与第一段代码有区别吗?并无区别。
何为闭包?此即闭包之定义!
多数教科书教程倾向于使用最后一段代码来阐释闭包,但笔者认为此举使问题复杂化。此处返回的是函数名,未研读谭浩强C/C++程序设计的读者可能一时难以察觉带括号与不带括号的区别,这种写法隐含陷阱。尽管该写法看似高级,笔者仍主张将问题简化,对比代码 1 和代码 2,你是否还会纠结函数调用或 n 的值?

闭包的作用与封装性
为何如此编写? 众所周知,JS 中每个函数如同独立小黑屋,可获取外界信息,但外界无法直接窥探其内容。将变量 n 置于小黑屋内,除 inc 函数外,无其他途径接触变量 n,且在函数 a 外部定义同名变量 n 亦互不影响,此即所谓增强“封装性”。
之所以使用 return 返回函数标识 inc,是因为在 a 函数外部无法直接调用 inc 函数,故 return inc 将 inc 与外部建立联系,代码 2 中的 this 同样是将 inc 与外部建立联系。
闭包常见陷阱与内存管理
常见陷阱 观察此例:
function createFunctions(){
var result = new Array();
for (var i=0; i < 10; i++){
result[i] = function(){
return i;
};
}
return result;
}
var funcs = createFunctions();
for (var i=0; i < funcs.length; i++){
console.log(funcs[i]());
}
乍看之下,预期输出 0~9,结果却出乎意料地输出 10 个 10?
此处的陷阱在于:函数带 () 才是执行函数!单纯语句 var f = function() { alert('Hi'); }; 不会弹窗,后续接 f(); 才会执行函数内部代码。上述代码可翻译为:
var result = new Array(), i;
result[0] = function(){ return i; }; //没执行函数,函数内部不变,不能将函数内的i替换!
result[1] = function(){ return i; }; //没执行函数,函数内部不变,不能将函数内的i替换!
...
result[9] = function(){ return i; }; //没执行函数,函数内部不变,不能将函数内的i替换!
i = 10;
funcs = result;
result = null;
console.log(i); // funcs[0]()就是执行 return i 语句,就是返回10
console.log(i); // funcs[1]()就是执行 return i 语句,就是返回10
...
console.log(i); // funcs[9]()就是执行 return i 语句,就是返回10
为何仅垃圾回收了 result,却未回收 i 呢?因为 i 仍被 function 引用。犹如餐厅盘子有限,服务员会巡台回收空盘,但装有菜的盘子岂敢收回?当然,若手动倒掉盘中菜(=null),盘子即被收走,此即内存回收机制。
至于 i 的值为何能保留,从文章开头一路读至此处,这应无纠结之处。盘中菜肴,吃一块少一块,不是吗?
总结而言 闭包是一个函数引用另一个函数的变量,因变量被引用而不会被回收,故可用于封装私有变量。此乃优点亦是缺点,不必要的闭包仅徒增内存消耗!此外使用闭包需注意变量值是否符合预期,因其类似静态私有变量。闭包常与其他概念混搭,接触越多理解越深,此处仅简述基础内容。
本文链接:http://www.cnblogs.com/qieguo/p/5457040.html
以上为本文全部内容,希望助您学习。