闭包(closure)是一个函数以及其捆绑的周边环境状态(lexical environment,词法环境)的引用的组合。换而言之,闭包让开发者可以从内部函数访问外部函数的作用域。在 JavaScript 中,闭包会随着函数的创建而被同时创建。
- JS对变量的declare, define, scope很自由,var可以不声明就使用,考虑到有需要将变量在fn中private,就可以通过闭包的方式实现
- 闭包是一个函数的局部变量 - 函数返回后它还活着
- 闭包是一个函数返回时但是内存堆栈帧不会被释放,不会被垃圾回收。
性能考量#
<span style="font-family: '.PingFangSC-Regular'">如果不是某些特定任务需要使用闭包,在其他函数中创建函数是不明智的,因为闭包在处理速度和内存消耗方面对脚本性能具有负面影响。</span>
- method in prototype 👍 / constructor 👎(将导致每次构造器被调用时,方法都会被重新赋值一次)
实用闭包#
// (fn)() 这种写法,(fn)是声明一个匿名的fn对象,()是自执行,返回一个实例,id就成了fn对象实例的属性了,且闭包
// 注意区分声明和实例化,就能很好理解闭包了
_uniqueID = (function(){
var id = 0;
/*
* 返回的匿名fn中,持有id的引用,所以在自执行之后,变量id不会被GC,这是闭包的本质;
*/
return function () {return id ++}
})();
alert(_uniqueID()); //0
alert(_uniqueID()); //1
// 变量的scope
var a = 1;
var b = 2;
(function() {
var b = 3;
a += b;
})();
console.log(a); //4
console.log(b); //2
内存泄漏#
闭包会在不经意间导致内存的泄漏(主要是ie,ie自有GC);
在 IE 中,每当在一个 JavaScript 对象和一个DOM对象之间形成循环引用时,就会发生内存泄露。
// Common Example
function leakMemory() {
var el = document.getElementById('el');
var o = { 'el': el };
el.o = o;
}
// Closure Example
function addHandler() {
var el = document.getElementById('el');
el.onclick = function() {
el.style.backgroundColor = 'red';
}
}
// Resolve 1
function addHandler(){
document.getElementById('el').onclick = function(){
this.style.backgroundColor = 'red';
};
}
// Resolve 2
function addHandler() {
var clickHandler = function() {
this.style.backgroundColor = 'red';
};
(function() {
var el = document.getElementById('el');
el.onclick = clickHandler;
})();
}
在循环中创建闭包:一个常见错误#
参考[1]
<p id="help">Helpful notes will appear here</p>
<p>E-mail: <input type="text" id="email" name="email" /></p>
<p>Name: <input type="text" id="name" name="name" /></p>
<p>Age: <input type="text" id="age" name="age" /></p>
function showHelp(help) {
document.getElementById("help").innerHTML = help;
}
function setupHelp() {
var helpText = [
{ id: "email", help: "Your e-mail address" },
{ id: "name", help: "Your full name" },
{ id: "age", help: "Your age (you must be over 16)" },
];
for (var i = 0; i < helpText.length; i++) {
var item = helpText[i]; // 这里发生了变量提升,导致执行完之后,item 指向最后一个元素
document.getElementById(item.id).onfocus = function () {
showHelp(item.help);
};
}
}
setupHelp();