口试必问问题,但总觉得明白得不深切,干脆写一篇文章逐渐梳理吧。
什么是闭包
红宝书上给出的定义是:闭包是指有权接见另一个函数作用域中的变量的函数,看到别的一个明白是:函数和函数内部能接见到的变量(或许环境)的总合,就是一个闭包。建立一个闭包最常见的体式格局就是在一个函数内部建立另一个函数。下面写一个例子:
function f1() {
var a = 1;
function closure() {
console.log(++a);
}
return closure;
}
上面例子中,f1
内部的匿名函数以及它能够接见到的外部函数的变量 a
合在一起,就形成了一个闭包。运用 return
将闭包返回的目标是让它能够被外部接见。下面看看它怎样运用:
var f2 = f1(); // 实行外部函数,返回闭包
f2(); // 2
f2(); // 3
f2(); // 4
第一句实行函数 f1()
后,闭包被返回并赋值给了一个全局变量 f2
,今后每次挪用 f2()
,变量 a
的值就会加 1
。一般函数实行终了后,其作用域链和运动对象都会被烧毁,为何这里 a
并没有被烧毁而且每次实行 f2()
还会被递增?原因是闭包有权接见外部函数的变量,进一步说,闭包的作用域链会援用外部函数的运动对象,所以 f2()
在实行时,其作用域链实际上是:
- 本身的运动对象;
-
f1()
的运动对象; - 全局变量对象。
所以 f1()
实行完后,实在行环境的作用域链会被烧毁,但运动对象仍然会留在内存中,由于闭包作用域链在援用这个运动对象(说白了就是闭包还须要运用外层函数的变量,不允许它们被烧毁),直到闭包被烧毁后,f1()
的运动对象才会被烧毁。
上面例子中,是将返回的闭包赋值给了一个全局变量 f2
,var f2 = f1();
,f2
是不会被烧毁的,每次实行完 f2()
,闭包的作用域链不会被烧毁,所以就会涌现每次实行 f2()
,a
递增。
然则换一种闭包的挪用体式格局,状况会差别:
f1()(); // 2
f1()(); // 2
由于没有把闭包赋值给一个全局变量,闭包实行完后,实在行域链与运动对象都烧毁了。
闭包的作用
建立用于接见私有变量的公有要领
实在组织函数中定义的实例要领,就是闭包:
function Person(){
var name = 'Leon';
function sayHi() {
alert('Hi!');
}
this.publicMethod = function() {
alert(name);
return sayHi();
}
}
组织函数 Person
中定义实例要领 publicMethod()
就是一个闭包,它能够接见外部函数的变量 name
和 函数 sayHi()
,为何要这么做呢?由于我们想在组织函数中定义一些私有变量,让外部不能直接接见,只能经由历程定义好的公有要领接见,从而到达庇护变量,收敛外部权限的目标。
而在一般函数中,把闭包 return
出去供外部运用,实在目标也就是:让函数内部的变量一向保持在内存中,同时庇护这些变量,让它们不能被直接接见。
function person(){
var name = 'Leon';
function sayHi() {
alert('Hi!');
}
function publicMethod() {
alert(name);
return sayHi();
}
return publicMethod;
}
闭包用于建立单例
所谓单例,就是只要一个实例的对象。单例情势的优点在于:
保证一个类只要一个实例,防止了一个在全局范围内运用的实例频仍建立与烧毁。
- 比方网页中的弹窗,点击 a 按钮弹出,点击 b 按钮隐蔽,假如弹窗每一次弹出都须要新建一个对象,将会形成机能的糟蹋,更好的方法就是只实例化一个对象,一向运用。
划分了定名空间,防止了与全局定名空间的争执。
- 比方在一个单例中能够定义许多要领,经由历程
单例.要领
来运用,防止了在全局环境中定义函数,形成函数名争执。
- 比方在一个单例中能够定义许多要领,经由历程
下面逐渐引见下单例的建立体式格局,后两种体式格局将用到闭包。
1. 对象字面量建立单例
var singleton = {
attr1: 1,
attr2: 2,
method: function () {
return this.attr1 + this.attr2;
}
}
var s1 = singleton;
var s2 = singleton;
console.log(s1 == s2) // true
上面用字面量情势建立了一个单例,能够看到 s1
和 s2
是同等的。这类体式格局的问题在于外部能够直接接见单例的内部变量并加以修正,假如想让单例具有私有变量,就须要运用模块情势,模块情势就是用了闭包。
2. 模块情势
JS 中的模块情势的作用是:为单例增加私有变量和公有要领。它运用马上实行函数和闭包来到达目标。
var singleton = (function(){
// 建立私有变量
var privateNum = 1;
// 建立私有函数
function privateFunc(){
console.log(++privateNum);
}
// 返回一个对象包括公有要领
return {
publicMethod: function(){
console.log(privateNum)
return privateFunc()
}
};
})();
singleton.publicMethod();
// 1
// 2
这里起首定义了一个马上实行函数,它返回一个对象,该对象中有一个闭包 publicMethod()
, 它能够接见外部函数的私有变量。从而这个被返回的对象就成为了单例的大众接口,外部能够经由历程它的公有要领接见私有变量而无权直接修正。总结一下就是两点:
- 马上实行函数能够建立一个块级作用域, 防止在全局环境中增加变量。
- 闭包能够接见外层函数中的变量。
3. 组织函数+闭包
上面提到的对象字面是用来建立单例的要领之一,既然单例只能被实例化一次,不难想到,在运用组织函数新建实例时,先推断实例是不是已被新建,未被新建则新建实例,不然直接返回已被新建的实例。
var Singleton = function(name){
this.name = name;
};
// 猎取实例对象
var getInstance = (function() {
var instance = null;
return function(name) {
if(!instance) {
instance = new Singleton(name);
}
return instance;
}
})();
var a = getInstance('1');
console.log(a); // {name: "1"}
var b = getInstance('2');
console.log(b); // {name: "1"}
这里将组织函数和实例化历程进行了星散, getInstance()
中存在一个闭包,它能够接见到外部变量 instance
,第一次 instance = null
,则经由历程 new Singleton(name)
新建实例,并将这个实例保存在instance
中,以后再想新建实例,由于闭包接见到的instance
已经有值了,就会直接返回之前实例化的对象。