闭包的定义
当一个函数即便在离开了它的词法作用域(Lexical Scope)的情况下,仍然存储并可以存取它的词法作用域(Lexical Scope),这个函数就构成了闭包。由定义我们不难发现闭包和作用域有很多联系
闭包的作用
- 实现变量的“缓存”(使用不好容易出现内存泄漏)。
- 单列模式
- 代码模块化
- 。。。。。
闭包的表现
- 闭包是一个函数。
- 嵌套在另一个函数里面,并且调用其变量。
- 被调用参数和变量不会被垃圾回收机制回收
function outer() {
var local_var = "I'm local"; //不会被立刻回收
return function inner() { //闭包函数
console.log("local: " + local_var);
};
}
闭包的实例讲解(最全面)
- 变量跨域访问的问题
var global_var = "I'm global";
function A() {
var local_var = "I'm local";
console.log("global: " + global_var)
}
console.log("local: " + local_var);
这就是上面所提到的js作用域问题了,函数内形成自己的作用块级作用域,可以访问全局变量。反之却不行,全局作用域内,却不能访问块级作用域的变量。但如果我们想访问到呢,第一种方式当然就是设置为全局变量,第二种就是用闭包了,下面看第二种解决方案。
function outer() {
var local_var = "I'm local"; //不会被立刻回收
return function inner() { //闭包函数
return local_var;
};
}
var getLocal_var = new outer(); // 当然你也可以直接new outer()();
var local_varValue = getLocal_var();
- 最常见setTimeout函数问题
for (var i = 1; i <= 5; i++) {
setTimeout( function timer(){
console.log( i );
}, i*1000 );
}
这个例子相信大家再熟悉不过了,首先timer()形成闭包函数。直觉上都会认为输出为1,2,3,4,5。很明显这个结论是错误,实际结果为6,6,6,6,6。
首先是js执行为单线程,所以当循环执行完之后才会开始执行setTimeout函数。所以最后执行的时候i已经变成6了,可能有人会问,每一个闭包不应该都会有自己的一份拷贝吗?恭喜你,你的理解是对的,但这个方法却没有这样做,它都是调用了i这个变量,也就是说没有保存自己的那一个i。那么问题也就好解决了,我们给它传一个属于自己i。
for (var i=1; i<=5; i++) {
(function(j){
setTimeout( function timer(){
console.log( j );
}, j*1000 );
})(i);
}
- 来看一下阮一峰老师的思考题
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
var this_ = this; // 把第二个集合在一起了
return function(){
return this.name+'加'+this_.name;
};
}
};
alert(object.getNameFunc()());
相信大家已经知道答案 this. name = The Window ,this_.name = My Object;,首先我们需要解析的是object.getNameFunc()()到底是怎么运行的呢?
首先 object.getNameFunc = function(){
var this_ = this;
return function(){
return this.name+'加'+this_.name;
};
}
其次 object.getNameFunc() = function(){
return this.name+'加'+this_.name;
};
而最后的object.getNameFunc()()相信大家已经知道它执行的是哪一个函数了。这边还会涉及到一个this指向的问题。永远指向调用它的函数,如果没有就指向window。所以this_ 指向了object 对象 。而 this 没有指向任何对象,所以指向了window,这也就是答案的由来。
- 比较复杂的面试题
function fun(n,o) { // 第一个fun
console.log(o)
return {
fun:function(m){ // 第二个fun
return fun(m,n); // 还是第一个 fun
}
};
}
var a = fun(0); a.fun(1); a.fun(2); a.fun(3);//undefined,?,?,?
var b = fun(0).fun(1).fun(2).fun(3);//undefined,?,?,?
var c = fun(0).fun(1); c.fun(2); c.fun(3);//undefined,?,?,?
可能很多人知道答案,却不是很理解,仔细看下面的分析,主要我们需要认清fun指向的是哪一个,最终执行的函数是什么
1: fun(0) 最后返回的是什么,作用是什么?
a = {
fun:function(m){ // 这是返回
return fun(m,n);
}
}
而作用就是将n置为了0,可能有人会问,执行完不应会被回收吗?当然不是这样,这个n = 0这个参数被下面的函数所引用,所以不会被垃圾回收机制销毁,这就是闭包的作用之一。
a.fun(1) = fun(0).fun(1);
a.fun(2) = fun(0).fun(2);
实际上就是在执行 fun(1,n) fun(2,n) fun(3,n) 当然是有返回值的,我们这边不讨论,又因为n被赋值为0,所以其实就是在执行 fun(1,0) fun(2,0) fun(3,0) 这样相信大家都知道打印为0 0 0了吧
- 2:fun(0).fun(1)相信大家都知道为0,但是我们还需要关注一下这个结果的返回值是什么?
fun(0).fun(1) 最后执行的为fun(1,0) {n = 0,o = 0} 所以返回值依然是 {
fun:function(m){ // 这是返回
return fun(m,n);
}
所以fun(0).fun(1).fun(2) 就是在执行fun(2,1) 打印1 n = 2 ,o = 1,以此类推 fun(0).fun(1).fun(2).fun(3) 打印2
- 3:和第二题一样,fun(0).fun(1) 依次打印 underfined 和0 c.fun(2) = fun(0).fun(1).fun(2)
根据第二题的分析为1,c.fun(3) = fun(0).fun(1).fun(3),这边不在是2而是1,虽然第二次的n已经变成了2,但是因为你又重新执行了fun(0).fun(1) n重新被赋值为1了,所以最后执行的就是fun(3,1),打印1
闭包常见用法
- 单利模式
var Singleton = (function () {
var instance;
function createInstance() {
return new Object("I am the instance");
}
return {
getInstance: function () {
if (!instance) {
instance = createInstance();
}
return instance;
}
};
})();
function run() {
var instance1 = Singleton.getInstance();
var instance2 = Singleton.getInstance();
console.log("Same instance? " + (instance1 === instance2));
}
- 代码模块化(闭包模拟私有方法)
function CoolModule() {
var something = "cool";
var another = [1, 2, 3];
function doSomething() {
console.log( something );
}
function doAnother() {
console.log( another.join( " ! " ) );
}
return {
doSomething: doSomething,
doAnother: doAnother
};
}
var foo = CoolModule();
foo.doSomething(); // cool
foo.doAnother(); // 1 ! 2 ! 3
- 对象与方法关联
body {
font-family: Helvetica, Arial, sans-serif;
font-size: 12px;
}
#test-h1 {
font-size: 1.5em;
}
#test-h2 {
font-size: 1.2em;
}
#test-h3 {
font-size: 1.2em;
}
可能以前我们需要点击来改变body字体的大小的方法为
document.getElementById('size-12').onclick = function(){
document.body.style.fontSize ='12px';
};
我们常见的做法就是,响应事件然后执行的函数,但如果需要改变大小的样式有很多,比如12px一个按钮,14px一个按钮,16px一个按钮,如果以前的方式来写就很麻烦,而闭包可以为我们提供便利
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;
是不是很简单呢?
ES6 解决闭包
- 块级作用域
- let 替代 var
了解详情请阅读《ECMAScript 6 入门-阮一峰老师》(http://es6.ruanyifeng.com/)
本文参考
- George
- MDN
- 阮一峰的网络日志-学习闭包