本文深入解析JavaScript闭包机制,区分匿名函数与闭包概念。通过作用域链原理说明闭包如何访问外部变量,分析闭包导致的内存占用问题及解决方案。同时探讨闭包中this指向window的陷阱及通过变量捕获this的正确做法,帮助开发者避免常见错误。
匿名函数与闭包的概念区分
首先需要明确区分两个核心概念:匿名函数与闭包。
匿名函数是指创建时未指定名称的函数。常见场景包括函数表达式,即定义一个匿名函数并将其赋值给变量,此时该变量即作为函数的引用名。例如:
var sayHi = function(){
alert("Hi");
}; //注意这个分号
sayHi(); //调用函数
另一种常见用法是作为回调函数,例如在 jQuery 中常用的点击事件处理:
$("p").click(function(){
alert("click");
});
此外,匿名函数还常被用作其他函数的返回值:
function sayNameWithAge(age){
return function(person){
if(person.age == age){
return person.name;
}
}
}
那么,什么是闭包?闭包本质上是一个函数,其特殊性在于它有权访问其他函数作用域中的变量。
回顾上述匿名函数的例子,实际上已经包含了闭包的概念。在最后一个示例中,返回的匿名函数访问了外部函数 `sayNameWithAge` 的参数 `age`,因此这个作为返回值的匿名函数就是一个典型的闭包。
闭包的作用域链机制
要彻底理解闭包,必须掌握函数调用时的完整机制,这里从作用域链的角度进行解析。
首先看下面的示例:
function sayName(name){
alert(name);
}
sayName("Jack");
当调用 `sayName` 函数时,会创建一个对应的执行环境和作用域链,结构如下图所示:

函数调用时创建的作用域链包含两个引用,分别指向全局变量对象和函数的活动对象。全局对象在函数创建时已存在,仅在调用时被复制到作用域链中;而活动对象则是在函数调用时才创建的。
在函数内部访问变量时,引擎会从作用域链中按顺序搜索同名变量。
函数执行结束后,其活动对象通常会被销毁,而全局变量对象则永久保留在内存中。
然而,上述情况仅适用于普通函数。对于闭包而言,情况有所不同:
以 `sayNameWithAge` 函数为例:
function sayNameWithAge(age){
return function(person){
if(person.age == age){
return person.name;
}
}
}
//创建函数
var sayName = sayNameWithAge(18);
//调用函数
var name = sayName({name:"Jack",age:18});
//解除对匿名函数的引用
sayName = null;
当调用 `sayName` 函数时,生成的作用域链如下所示:

匿名函数被 return 后,其作用域链被创建,其中包含了外部函数的活动对象和全局变量对象。这使得匿名函数可以访问 `sayNameWithAge` 中定义的所有变量,从而形成闭包。
闭包导致的内存管理问题
这种闭包机制存在一个潜在问题:当 `sayNameWithAge` 执行完毕时(JavaScript 的垃圾回收机制多为标记清除法),其活动对象因被闭包引用而不会被销毁。只有当匿名函数本身被销毁后,`sayNameWithAge` 的活动对象才会被释放。因此,最后一行解除对匿名函数的引用不仅是为了销毁闭包对象,也是为了确保外部函数活动对象能被正确回收。务必谨慎使用闭包!
闭包中的变量共享陷阱
关于闭包,还需注意一点:闭包访问其他函数变量,是因为其作用域链中包含指向该函数活动对象的引用,而非闭包自身的活动对象中保存了这些变量。示例如下:
function outer(){
var result = new Array();
for(var i = 0; i < 5; i ++){
result[i] = function(){
return i;
};
}
return result;
}
按照设想,最后 outer 返回的数组各个项中的值应该是与其下标一致的。但是,最后的结果却是每个项的值都是 5 不难想象,在上面的所有闭包的作用域链中,都有一个引用指向了 outer 的活动对象中的参数 i,而且是指向同一个对象。
这是因为所有闭包的作用域链中,都包含一个指向 `outer` 活动对象中参数 `i` 的引用,且指向同一个对象。当 `outer` 执行完毕时,`i` 的值为 5,因此所有闭包访问 `i` 时获取的值均为 5。
我们可以通过另一种方法实现预期效果:
function outer(){
var result = new Array();
for(var i = 0; i < 5; i ++){
result[i] = (fuction(index){
return index;
})(i);
}
return result;
}
这里为匿名函数定义了一个参数 `index`,并在每次循环中立即调用该函数,将 `i` 的当前值复制给参数 `index`(注意 JavaScript 中是按值传递),并将返回的 `index` 赋值给 `result`。
闭包中 this 指向的处理
此外,闭包中另一个需要注意的问题是 `this` 对象的指向。
`this` 对象在 JavaScript 中是在函数运行时基于执行环境绑定的。由于匿名函数的执行环境通常具有全局性,因此在匿名函数中,`this` 对象通常指向 `window`。
var name = "Tom";
var person = {
name : "Jack",
sayName : function(){
return (function(){
return this.name;
})();
}
}
person.sayName(); //Tom
上述代码中访问 `this.name` 时,`this` 并非指向自身或 `person` 的 `this`,而是指向 `window`。
若需在闭包中访问外部函数的 `this` 对象,可在外部函数中定义一个变量,将 `this` 赋值给该变量。
var name = "Tom";
var person = {
name : "Jack",
sayName : function(){
var self = this;
return (function(){
return self.name;
})();
}
}
person.sayName(); //Jack
感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!