本文深入解析JavaScript闭包的核心机制,从变量作用域出发,阐述如何通过函数嵌套实现内部变量的外部访问。重点讲解闭包在保持变量持久化和数据封装中的应用,并指出滥用闭包可能导致的内存泄漏风险及优化策略,帮助开发者正确理解和使用这一高级特性。
JavaScript闭包详解
以下是针对JavaScript初学者的学习笔记整理。
变量的作用域基础
一、变量的作用域
理解闭包的前提,是掌握JavaScript中独特的变量作用域机制。
变量的作用域主要划分为两类:全局变量与局部变量。
JavaScript的一个显著特性是,函数内部可以直接访问全局变量。
var n=999;
function f1(){
alert(n);
}
f1(); // 999
反之,在函数外部无法直接读取函数内部的局部变量。
function f1(){
var n=999;
}
alert(n); // error
需特别注意,在函数内部声明变量时必须使用var命令。若省略该关键字,实际上会创建一个全局变量。
function f1(){
n=999;
}
f1();
alert(n); // 999
从外部读取局部变量的方法
二、如何从外部读取局部变量?
尽管常规情况下无法在函数外获取局部变量,但出于特定需求,我们有时需要实现这一功能。此时只能通过变通手段达成。
具体做法是在函数内部再定义一个子函数。
function f1(){
var n=999;
function f2(){
alert(n); // 999
}
}
如代码所示,函数f2被包含在函数f1内部,这使得f1的所有局部变量对f2可见。然而反向不可行,f2的局部变量对f1不可见。这种JavaScript特有的“链式作用域”结构(chain scope)意味着子对象能逐级向上查找父对象的变量,即父对象变量对子对象可见,反之则不成立。
既然f2能读取f1的局部变量,只需将f2作为返回值,即可在f1外部访问其内部变量。
function f1(){
var n=999;
function f2(){
alert(n);
}
return f2;
}
var result=f1();
result(); // 999
闭包的概念与本质
三、闭包的概念
前述代码中的f2函数即为闭包的实例。
专业文献中关于“闭包”(closure)的定义往往晦涩难懂。通俗而言,闭包是指能够读取其他函数内部变量的函数。
鉴于JavaScript中仅函数内部的子函数能读取局部变量,因此可将闭包简化理解为“定义在函数内部的函数”。
本质上,闭包充当了连接函数内部与外部的桥梁。
闭包的主要用途
四、闭包的用途
闭包应用场景广泛,主要两大用途:一是读取函数内部的变量;二是让这些变量的值始终保留在内存中。
如何理解这一特性?参考以下代码示例。
function f1(){
var n=999;
nAdd=function(){n+=1}
function f2(){
alert(n);
}
return f2;
}
var result=f1();
result(); // 999
nAdd();
result(); // 1000
在此代码中,result即为闭包f2。它执行了两次,首次输出999,第二次输出1000。这证实了函数f1中的局部变量n一直驻留内存,并未在f1调用结束后自动清除。
原因在于f1是f2的父函数,而f2被赋值给全局变量,导致f2始终存在于内存中。由于f2依赖f1存在,f1也必然常驻内存,从而避免了调用结束后被垃圾回收机制(garbage collection)回收。
此外需注意“nAdd=function(){n+=1}”这一行。首先,nAdd前未使用var关键字,故其为全局变量而非局部变量。其次,nAdd的值是一个匿名函数(anonymous function),该匿名函数本身也是闭包。因此,nAdd相当于一个setter,允许在函数外部操作内部的局部变量。
使用闭包的注意事项
五、使用闭包的注意点
1)由于闭包会导致函数内的变量保存在内存中,造成较大的内存消耗,因此不可滥用。否则可能引发网页性能问题,在IE浏览器中甚至可能导致内存泄露。解决方法是在退出函数前,删除不再使用的局部变量。
2)闭包会在父函数外部改变其内部变量的值。若将父函数视为对象(object),将闭包视为公共方法(Public Method),将内部变量视为私有属性(private value),则需谨慎操作,避免随意修改父函数内部的变量值。
闭包运行机制思考
六、思考题
若能理解以下两段代码的运行结果,即表示已掌握闭包的运行机制。
代码片段一
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
return function(){
return this.name;
};
}
};
alert(object.getNameFunc()());
代码片段二
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
var that = this;
return function(){
return that.name;
};
}
};
alert(object.getNameFunc()());
以上内容为本文的全部讲解,希望读者能从中受益。