JavaScript 闭包
介绍
- 闭包(英语:Closure),又称词法闭包(Lexical Closure)或函数闭包(function closures),是在支持头等函数的编程语言中实现词法绑定的一种技术。
- 闭包在实现上是一个结构体,它存储了一个函数(通常是其入口地址)和一个关联的环境(相当于一个符号查找表)。环境里是若干对符号和值的对应关系,它既要包括约束变量(该函数内部绑定的符号),也要包括自由变量(在函数外部定义但在函数内被引用),有些函数也可能没有自由变量。换而言之,闭包让开发者可以从内部函数访问外部函数的作用域。
- 闭包跟函数最大的不同在于,当捕捉闭包的时候,它的自由变量会在捕捉时被确定,这样即便脱离了捕捉时的上下文,它也能照常运行。捕捉时对于值的处理可以是值拷贝,也可以是名称引用,这通常由语言设计者决定,也可能由用户自行指定(如C++)。
使用
下面在全局上下文中,定义了一个 showName
函数。
function showName() {
const company = "Bytedance";
const dep = "边缘云";
const name = "zhangqi";
console.log('company', company);
return function() {
console.log(dep);
return name;
}
}
const getName = showName();
console.log(getName());
上面代码会依次打印:
company Bytedance
边缘云
zhangqi
第 11 行,showName
函数已被执行完成,按照通常逻辑,里面的变量应该被回收,但是我们还是通过 getName
函数访问到了它里面的变量 dep
和 name
。
因为 showName
函数返回了一个函数,这个函数产生了一个闭包,这个闭包是一个地址,它指向了堆上的一块内存,这块内存存储了闭包要访问的外部函数的两个变量 dep
和 name
:
{
dep: "边缘云",
name: "zhangqi"
}
这个闭包会一直跟随通过 showName
函数返回的函数赋值的 getName
函数的生命周期结束。所以即使 showName
函数执行结束,它里面的这个闭包也是没有被回收的。
性能问题
- 在 Javascript 中,如果一个对象不再被引用,那么这个对象就会被 GC 回收。如果两个对象互相引用,而不再被第3者所引用,那么这两个互相引用的对象也会被回收。因为函数 a 被 b 引用,b 又被 a 外的 c 引用,这就是为什么函数 a 执行后不会被回收的原因。
- 通常,如果引用闭包的函数是一个全局变量,那么闭包会一直存在直到页面关闭;但如果这个闭包以后不再使用的话,就会造成内存泄漏。如果引用闭包的函数是个局部变量,等函数销毁后,在下次 JavaScript 引擎执行垃圾回收时,判断闭包这块内容如果已经不再被使用了,那么 JavaScript 引擎的垃圾回收器就会回收这块内存。
- 如果不是某些特定任务需要使用闭包,在其他函数中创建函数是不明智的,因为闭包在处理速度和内存消耗方面对脚本性能具有负面影响。例如,在创建新的对象或者类时,方法通常应该关联于对象的原型,而不是定义到对象的构造器中。原因是这将导致每次构造器被调用时,方法都会被重新赋值一次(也就是说,对于每个对象的创建,方法都会被重新赋值)。例如:
function MyObject(name, message) {
this.name = name.toString();
this.message = message.toString();
this.getName = function() {
return this.name;
};
this.getMessage = function() {
return this.message;
};
}
在上面的代码中,我们并没有利用到闭包的好处,因此可以避免使用闭包。修改成如下:
function MyObject(name, message) {
this.name = name.toString();
this.message = message.toString();
}
MyObject.prototype = {
getName() {
return this.name;
},
getMessage() {
return this.message;
}
};
但我们不建议重新定义原型,可以对原型进行更新操作:
function MyObject(name, message) {
this.name = name.toString();
this.message = message.toString();
}
MyObject.prototype.getName = function() {
return this.name;
};
MyObject.prototype.getMessage = function() {
return this.message;
};
总结
理解 JavaScript 的闭包是迈向高级 JS 程序员的必经之路,理解了其解释和运行机制才能写出更为安全和优雅的代码。