上一篇《对js函数的理解及深入讨论》
谈起闭包很多人都会挠头。我以前也是,因此我查阅了很多资料,综合分析和理解,自认现在已经识得了闭包的真面目。下面我就从闭包的产生条件,闭包的作用,闭包的实质,闭包的本质,闭包产生的时机,闭包的应用 等几个方面具体和系统的阐述一下闭包。
前导
我们谈闭包有一个前提条件:
一般情况下,函数执行完毕,它内部的变量会被销毁。
现在我们在来看
闭包是一种语言特性,可以使嵌套的内部函数访问并保存嵌套的外部函数的局部变量。外部函数执行完后,只要内部函数还存在,那其中保存外部函数的局部变量也会保存下来。可能看这句话有些绕,咱们往下看
闭包产生的条件:
1 存在函数嵌套,函数fun2在函数fun1执行时被定义,我们认为fun2是fun1的内部函数。
2 内部函数引用了外部函数的局部变量。
3 内部函数被暴露出来,可以在外部函数外调用,内部函数中访问的外部函数的局部变量被保存下来,就形成了闭包。
闭包的作用:
1.延长了局部变量的生命周期
2.在函数外可以间接操纵内部局部变量
闭包的本质:
一种语言特性,在浏览器的具体实现中是内部函数对象内部的一个隐式对象【Closure】,用以保存函数引用的作用域链上的变量。
(隐式变量:存在于浏览器内存中,帮助完成语法功能,但不能被程序员操作的对象)
如果有兴趣可以用chrome开发者工具中Sources指令查一下函数执行时的栈结构附上一段调试代码
function fn1 () {
var a = 2
var b = 'abc'
function fn2 () {
console.log(a)
}
}
fn1()
闭包产生的时机:
访问了外部函数局部变量的内部函数被定义时产生了闭包。闭包的生命周期取决于内部函数的生命周期。
闭包到底是内部函数被定义是产生,还是被暴露出去的时候产生?
经常有人会纠结这个问题,其实两者说的都有道理,内部函数定义时就产生了闭包。但此时内部函数也只是被外部函数的变量所引用的·。如果不把内部函数暴露出去,那等外部函数执行完,其变量被回收,闭包也就无从谈起了。
闭报的应用:
说起闭包,我们通常的认识这是一个高级语法,最大的作用就是提高逼格。
非也非也,其实我们的js代码从来没有离开过闭包。
我们曾经长期使用的Jq框架,就是闭包的有力实践者。大家知道的,大部分的框架和模块最终编译出来都是IIFE。所以我们使用的方法和属性都是这个自调用函数的内部变量。
我们使用的$的方法中有保存着大量的IIFE的内部资源。这变量都保存在暴露出来的方法的闭包中。
我们在写较复杂的原生代码时也经常会使用到闭包,只是我们不经意而已。这就是所谓的语法糖。
案例
闭包的魅力和招人恨的的能力,从来不是因为这个概念有多难理解。而是它可以让代码有很多“玩法”,这些玩法通常会颠覆我们的直觉
案例一:
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
return function(){
return this.name;
};
}
};
alert(object.getNameFunc()());
//这个很简单,且没有应用到闭包,稍微有点难点的就是object.getNameFunc()的返回值是作为
一个全局函数被调用的所以,他的this指向window。
关于this稍微啰嗦一句,this是函数代码执行前js引擎预定义的一个变量,这个变量指向调用该函
数或者所方法的对象。说白了,正在执行的函数是谁调用的,他里面的this就指向谁。
案例二:
var name2 = "The Window";
var object2 = {
name2 : "My Object",
getNameFunc : function(){
var that = this;
return function(){
return that.name2;
};
}
};
alert(object2.getNameFunc()()); //? my object
内部函数保存外部函数的局部变量that。
案例三:
function fun(n,o) {
console.log(o)
return {
fun:function(m){
return fun(m,n)
}
}
}
var a = fun(0) //(1)
a.fun(1) //(2)
a.fun(2) //(3)
a.fun(3)//undefined,0,0,0 //(4)
var b = fun(0).fun(1).fun(2).fun(3)//undefined,0,1,2 //(5)
var c = fun(0).fun(1) //(6)
c.fun(2) //(7)
c.fun(3)//undefined,0,1,1 //(8)
这个题很有意思,我建议看到这里的小伙伴试着做一做。
下面我们一行一行的解析一下
(1)先看输出了啥,o传实参,所以o为undefined,打印undefined;
再看返回什么,返回一个对象,这个对象中有一个方法fun。
{
fun:function(m){
return fun(m,n)
}
}
此时a指向这个对象。
(2)a.fun方法是第一次执行是被定义的,且a.fun访问了fun函数的局部变量n。
此后在怎执行a.fun一直指向原来的函数。也就是说a.fun的闭包中始终有一个n = 0;
所以后面三条语句是fun函数执行了三次,分别是fun(1,0);fun(2,0);fun(3,0)
(5)第一个fun(0) 实在执行一个函数fun,与(1)相同, 输出undefined
fun(0).fun(1) 与(2)相同,输出 0;但其返回一个新的对象,这个对象中的方法与a.fun不指向
同一个函数对象了。这个方法闭包中的n是访问第二次fun(m,n)函数的内部变量得来的,所以此时n=1;
故fun(0).fun(1).fun(2) 输出1
同理fun(0).fun(1).fun(2).fun(3) 再输出一个3;
(6)同(1)(2)输出 undefined 和 0;并且返回的对象中fun方法的闭包中保存着n=1;
(7)(8) 调用的是同一个方法 都输出1