一个函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包(closure)。也就是说,闭包让你可以在一个内层函数中访问到其外层函数的作用域。在 JavaScript 中,每当创建一个函数,闭包就会在函数创建的同时被创建出来。
// 示例1
function makeFunc() {
// name是个局部变量,在makeFunc的函数作用域中
var name = "Mozilla";
function displayName() {
// displayName函数使用了自己函数作用域以外的变量
alert(name);
}
return displayName;
}
var myFunc = makeFunc();
myFunc();
// 示例2
function makeAdder(x) {
// x是makeAdder函数的入参,在makeAdder的函数作用域中,被一个匿名函数使用了
return function(y) {
return x + y;
};
}
var add5 = makeAdder(5);
var add10 = makeAdder(10);
console.log(add5(2)); // 7
console.log(add10(2)); // 12
类似于面向对象编程。在面向对象编程中,对象允许我们将某些数据(对象的属性)与一个或者多个方法相关联。
// 示例1
// 在页面上添加一些可以调整字号的按钮。
function makeSizer(size) {
return function() {
document.body.style.fontSize = size + 'px';
};
}
var size12 = makeSizer(12);
var size14 = makeSizer(14);
var size16 = makeSizer(16);
document.getElementById('size-12').onclick = size12;
document.getElementById('size-14').onclick = size14;
document.getElementById('size-16').onclick = size16;
var makeCounter = function() {
// 私有变量
var privateCounter = 0;
// 私有函数
function changeBy(val) {
privateCounter += val;
}
return {
increment: function() {
changeBy(1);
},
decrement: function() {
changeBy(-1);
},
value: function() {
return privateCounter;
}
}
};
/* makeCounter执行一次会生成新的局部变量privateCounter,由于有闭包,makeCounter执行完后
* privateCounter不会释放,每个闭包都是引用自己词法作用域内的变量privateCounter
*/
var Counter1 = makeCounter();
var Counter2 = makeCounter();
console.log(Counter1.value()); /* logs 0 */
Counter1.increment();
Counter1.increment();
console.log(Counter1.value()); /* logs 2 */
Counter1.decrement();
console.log(Counter1.value()); /* logs 1 */
console.log(Counter2.value()); /* logs 0 */
// 防抖
function debounce (fn, delay=500) {
let timer = null;
return function() {
if(timer) {
clearTimeout(timer);
}
timer = setTimerout(() => {
fn.apply(this, arguments);
timer = null;
}, delay);
}
}
// 节流
function throttle (fn, delay=500) {
let timer = null;
return function() {
if(timer) {
return;
}
timer = setTimerout(() => {
fn.apply(this, arguments);
timer = null;
}, delay);
}
}
IIFE( 立即调用函数表达式)是一个在定义时就会立即执行的 JavaScript 函数。
写法: (函数声明)(函数参数)
当函数变成立即执行的函数表达式时,表达式中的变量不能从外部访问。
(function () {
var name = "Barry";
})();
// 无法从外部访问变量 name
name // 抛出错误:"Uncaught ReferenceError: name is not defined"
将 IIFE 分配给一个变量,不是存储 IIFE 本身,而是存储 IIFE 执行后返回的结果
var result = (function () {
var name = "Barry";
return name;
})();
// IIFE 执行后返回的结果:
result; // "Barry"
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
var item = helpText[i];
document.getElementById(item.id).onfocus = function() {
showHelp(item.help);
}
}
}
赋值给 onfocus 的是闭包。这些闭包是由他们的函数定义和在 setupHelp 作用域中捕获的环境所组成的。这三个闭包在循环中被创建,但他们共享了同一个词法作用域,在这个作用域中存在一个变量item。三次循环后,变量对象item(被三个闭包所共享)已经指向了helpText的最后一项。
解决办法:
1.使用更多闭包
// 其余地方不变,这里只修改了循环部分
for (var i = 0; i < helpText.length; i++) {
// 三次循环生成了三个匿名函数,每个onfocus的回调绑定了一个各自的词法作用域
(function() {
var item = helpText[i];
document.getElementById(item.id).onfocus = function() {
showHelp(item.help);
}
})(); // 马上把当前循环项的item与事件回调相关联起来
2.使用es5中的let
// 其余地方不变,这里只修改了循环部分
for (var i = 0; i < helpText.length; i++) {
// let具有块级作用域,循环三次生成了三个
let item = helpText[i];
document.getElementById(item.id).onfocus = function() {
showHelp(item.help);
}
}
如果不是某些特定任务需要使用闭包,在其它函数中创建函数是不明智的,因为闭包在处理速度和内存消耗方面对脚本性能具有负面影响。应该尽量少使用闭包。
// 这个构造函数每次调用MyObject都会生成新的getName、getMessage方法
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 = function() {
return this.name;
};
MyObject.prototype.getMessage = function() {
return this.message;
};
[MDN](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Closures)
[MDN](https://developer.mozilla.org/zh-CN/docs/Glossary/IIFE)
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。