基本概念
- 闭包是指有权访问另一个函数作用域变量的函数,创建闭包的通常方式,是在一个函数内部创建另一个函数
- 如果一个函数访问了它的外部变量,那么它就是一个闭包。
- 闭包的本质是函数
- 闭包能访问其他函数的变量
- 闭包通常作为其他函数的返回值,也可能是函数的参数
- 外部函数不能访问内部函数的变量,但是内部函数可以访问外部函数的变量。所以闭包通常是被返回的(参数中的)内部函数
- "链式作用域"结构(
chain scope
):子对象会一级一级地向上寻找所有父对象的变量。所以,父对象的所有变量,对子对象都是可见的,反之则不成立。 - 自由变量跨作用域取值时:要去创建这个函数的作用域取值,而不是“父作用域”。
- 函数的特别之处在于可以创建一个独立的作用域。
- 闭包引用的变量,保存在创建闭包的函数的作用域中,可以一直延伸到全局作用域,形成作用域链(
chain scope
)。 -
this
的关键是确定调用函数的对象;闭包变量的关键确定创建闭包的作用域链。这两者有本质的区别。
var a = 10;
var b = 200;
function fn() {
var b = 20;
function bar() {
console.log("a + b = " + (a + b));
console.log("this.b = " + this.b);
}
return bar;
}
var foo = fn();
foo();
// a + b = 30
// this.b = 200
- 闭包
foo()
的创建环境是作用域链bar() => fn() => 全局
a
在bar()
和fn()
中都没有,所以取了全局的a = 10
b
在bar()
中没有,取了fn()
中b = 20;
;全局中的b = 200;
被屏蔽了。
- 闭包
foo()
的调用者(执行环境)是全局,所以this.b
指的是全局中的b = 200;
使用场景
闭包可以用在许多地方。它的最大用处有两个,一个是前面提到的可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中。
实现get
和set
function Person(){
var name = "default";
return {
getName : function(){
return name;
},
setName : function(newName){
name = newName;
}
}
};
var john = Person();
john.getName(); // "default"
john.setName("john");
john.getName(); // "john"
console.log(john.name); // undefined
var jack = Person();
jack.getName(); // "default"
jack.setName("jack");
jack.getName(); // "jack"
console.log(jack.name); // undefined
这个看上去就很面向对象了。封装了数据name
,同时提供了getName()
和setName(newName)
两个闭包(返回的内部函数)来作为这个变量的访问接口。
缓存
var CachedSearchBox = (function(){
var cache = {},
count = [];
return {
attachSearchBox : function(dsid){
if(dsid in cache){//如果结果在缓存中
return cache[dsid];//直接返回缓存中的对象
}
var fsb = new uikit.webctrl.SearchBox(dsid);//新建
cache[dsid] = fsb;//更新缓存
if(count.length > 100){//保正缓存的大小<=100
delete cache[count.shift()];
}
return fsb;
},
clearSearchBox : function(dsid){
if(dsid in cache){
cache[dsid].clearSelection();
}
}
};
})();
CachedSearchBox.attachSearchBox("input1");
- 闭包
attachSearchBox(dsid)
和clearSearchBox(dsid)
访问了对象cache
(也可以看做是字典),所以cache
会一直保存在内存中。 - 这是一个缓存,如果命中了,直接就从缓存中取,不需要再查找,可以提升查找速度。
- 这是一个利用闭包占内存的例子,大多数情况下,要注意闭包导致内存占用过多的问题
解决this
问题
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
return function(){
return this.name;
};
}
};
object.getNameFunc()(); // "The Window"
-
object.getNameFunc()
返回的是一个匿名函数,可以给个名字,比如var foo = object.getNameFunc();
。 - 然后执行
foo();
这里的执行环境是全局,所以此时的this.name
就返回全局的var name = "The Window";
that
大法:
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
var that = this;
return function(){
return that.name;
};
}
};
object.getNameFunc()(); // "My Object"
- 同样给匿名函数一个名字,方便分析
var foo = object.getNameFunc();
这时的执行环境是object
,所以函数getNameFunc()
中的this
指的是object
; -
var that = this;
那么that = object;
这样object中的信息(比如name : "My Object"
)都保存在that
中了。that
处于函数getNameFunc()
的作用域中 - 函数
foo()
的执行环境是全局,但是创建环境是函数getNameFunc()
- 函数
foo()
访问了不在自己作用域中的变量that
,所以他是一个闭包。 - 闭包
foo()
沿着作用域链往上找,在函数getNameFunc()
的作用域中找到了变量that
,就拿出来用了that.name = "My Object"
匿名包装器
其实就是我们通常说的自执行匿名函数
匿名函数的作用是减少临时的中间变量,解决取名困难问题。
自执行就是定义后马上执行,并且只执行一次。如果没有闭包,函数马上就垃圾回收了,如果有闭包引用变量,那么作用域会继续替闭包保存变量,相当于一个缓存。
for(var i = 0; i < 10; i++) {
setTimeout(function() {
console.log(i);
}, 1000);
} // 输出10个10,与预期有差距
- 函数
setTimeout(fn, time);
的参数fn
是一个函数;这里的fn
是一个匿名函数,并且使用了外部的变量i
,所以这个匿名函数是一个闭包。
-
fn
的执行环境是全局的(异步执行函数),创建的作用域是for
循环所在的作用域。 -
fn
执行的时候,for
循环所在的作用域保存了变量i
,闭包可以访问,由于1
秒钟之后,for
循环早就执行完了(对电脑来说1
秒钟是很长的时间),这时变量i
的值已经变成了10
-
1
秒钟之后,fn
执行了10
次,变量i
只有1
个,值是10
,所以输出了10
个10
for(var i = 0; i < 10; i++) {
(function(e) {
setTimeout(function() {
console.log(e);
}, 1000);
})(i);
}
(funtion(e))(i)
是一个自执行匿名函数,定义完后马上执行。变量i
属于for
循环所在的作用域,通过参数e
传递给函数setTimeout(fn, time);
- 每次
for
循环都会创建一个自执行匿名函数(funtion(e))(i)
,定义完后马上执行。所以有10
个不同的e
- 自执行匿名函数
(funtion(e))(i)
虽然执行完了,但是函数作用域还在内存中,没有被回收,因为变量e
被setTimeout(fn, time);
中的闭包使用 -
fn
的执行环境是全局的(异步执行函数),执行时,取10
个不同的自执行匿名函数(funtion(e))(i)
中的e
来执行 -
10
个不同的e
中保存了0~9
,输出符合预期 -
10
个fn
执行完后,自执行匿名函数(funtion(e))(i)
中的e
没有闭包使用,这些执行匿名函数被系统回收,内存占用下降。
参考文章
深入理解javascript原型和闭包(14)——从【自由变量】到【作用域链】
深入理解javascript原型和闭包(15)——闭包
作用域的图画得不错
学习Javascript闭包(Closure)
两个作用总结得不错
最后的例子给的挺好,this
的that
(或者self
)大法
详解js中的闭包
这里的图画得挺不错的
js闭包的用途
使用的例子不错
闭包和引用
循环的例子给得不错
Javascript闭包——懂不懂由你,反正我是懂了
既然是翻译stackflow的文章,可以看看
网友评论