学习任务来自:百度ife前端技术学院
学习资料参考自:
在经过前面作用域和原型、原型链的学习,感觉遇到闭包也不怕啦... 准备被虐杀!
先补全一些作用域上的知识:
自由变量
在A作用域中使用的变量x,却没有在A作用域中声明(即在其他作用域中声明的),对于A作用域来说,x就是一个自由变量。
例如:
var x = 10;
function fn(){
var b = 20;
// x不在fn的作用域中声明,但却调用了
// 因此x叫做自由变量
console.log(b + x);
}
一个经常容易犯错的例子:
var x = 10;
function fn() {
console.log(x);
}
function show(f) {
var x = 20;
fn();
}
show(); // 10
这个例子中,输出的不是20,而是10,因为在函数定义的时候就已经决定了函数的上下文执行环境,故,fn的作用域链上,只能找到window执行环境下的变量x
另一个例子:
var x = 10;
function fn(){
console.log(x);
}
function show(f){
var x = 20;
(function(){
f();
})()
}
show(fn); // 10
很抱歉,还是10,原理同上~
闭包
闭包就是能够读取其他函数内部变量的函数。可以简单的理解成“定义在一个函数内部的函数”,在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。
闭包应用的两种情况
函数作为返回值
function fn(){
var max = 10;
return function bar(x) {
if (x > max) {
console.log(x);
}
};
}
var f1 = fn();
f1(15);
bar函数作为返回值,赋值给f1变量,执行f1(15)时,用到了fn作用域下的max变量的值(此时fn在执行后作用域应该是销毁了,但是由于闭包的作用,变量max依旧存在)
注意:内部函数定义在引用的变量之前,内部函数依旧可以访问闭包中的变量
function sayAlice() {
var sayAlert = function() { alert(alice); }
// Local variable that ends up within closure
var alice = 'Hello Alice';
return sayAlert;
}
var helloAlice=sayAlice();
helloAlice(); // Hello Alice
函数作为参数被传递
var max = 10,
fn = function(x) {
if (x > max) {
console.log(x);
}
};
// 模拟块级域
// f(15)成功访问到了max = 10
(function(f)) {
var max = 100;
f(15);
})(fn);
fn函数作为一个参数被传递到另一个函数,赋值给f参数,执行f(15),f的作用域中,max = 10,而不是100;
闭包原理
创建函数A本身也就创建了一个新的,独立的作用域A-context,而由于执行A函数时,由于正好要引用到A函数上下文环境中存在的一个变量(或者几个变量)(该变量恰好存在于包裹该A函数的B函数的作用域中),因此在销毁B函数后,因为该独立的A函数被返回的同时需要B函数的变量,所以该变量不会销毁,而接收该返回的A函数就是闭包
闭包的问题
- 因为存在于闭包中的变量不会销毁,一直保存在内存中,因此在运行程序时就 增加了内容开销,在IE中可能导致内存泄露。
解决方法是:在退出函数之前,将不使用的局部变量全部删除(尽管javascript中有垃圾回收机制)
在用闭包模拟javascript中所不含有的私有变量和共有变量时,把闭包当做公有方法要切记不要用闭包修改了私有变量
在循环中引用闭包也容易出现问题
错误的代码:
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];
document.getElementById(item.id).onfocus = function() {
showHelp(item.help);
}
}
}
setupHelp();
我们希望的理想结果是指定哪个显示哪个,可是事实不是这样的,该问题的原因在于赋给 onfocus 是闭包(showHelp)中的匿名函数而不是闭包对象;在闭包(showHelp)中一共创建了三个匿名函数,但是它们都共享同一个环境(item)。在 onfocus 的回调被执行时,循环早已经完成,且此时 item 变量(由所有三个闭包所共享)已经指向了 helpText 列表中的最后一项。
正确的做法是使onfocus指向一个新的闭包对象:
Your age (you must be over 16)
function showHelp(help) {
document.getElementById('help').innerHTML = help;
}
function makeHelpCallback(help) {
return function() {
showHelp(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];
document.getElementById(item.id).onfocus = makeHelpCallback(item.help);
}
}
setupHelp();
设计模式中的单例模式
单例模式singleton就是闭包的典型应用
var singleton = function () {
var privateVariable;
function privateFunction(x) {
...privateVariable...
}
return {
firstMethod: function (a, b) {
...privateVariable...
},
secondMethod: function (c) {
...privateFunction()...
}
};
}();
此时, privateVariable和privateVariable都是私有变量,通过闭包完成了私有的成员和方法的封装。只返回了两个接口~