闭包
闭包定义:指拥有多个变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分。
函数内部可以直接读取全局变量。
函数内部变量无法在函数外部访问。
函数内部声明要用var或者let声明,不然会变成全局变量
链式作用域:子对象会一级级向上寻找父对象的变量,父对象的变量子对象都是可见的,反之则不行。
在一个闭包环境内修改变量值,不会影响另一个闭包中的变量。
普通的函数内嵌,内部函数是先执行;而闭包则是:先把内部函数赋给外部函数,然后在执行。
下面这段代码就是一根典型的闭包
function f1(){
var a = 10;
function f2(){
alert(a);
}
f2(); //①
}
f1(); //10 ②
f1
和f1()
的区别不加括号是代码,加()
是执行这段代码,加return
是返回一个值,可以把返回的值赋值给变量,不加return
默认返回undefined
;
所以①
处有三种写法:
第一种:①
处写f2();
,②
处调用需要这样写f1();
。具体执行过程:f1
体内调用f2
函数,并执行。
第二种:①
处写return f2();
,②
处调用需这样写f1();
。具体执行过程:同上;区别是多了个return
,因为现在f2
函数中没有返回值,所以f1
在调用f2
只是执行一下alert(a)
,f1
的返回值是undefined
。
第三种:①
处写return f2;
,②
处调用需这样写f1()();
。这里返回的是f2
函数的代码,所以在调用f1
时要加上2个括号,第一个括号是执行f1
函数,第2个括号是执行f2
函数,如果①
处省略return
会报错。
return
和函数调用时是否加括号的意思都明白,但是把它俩结合起来,就搞不清了。
正好今天学闭包时碰上了,顺便就把它搞清楚了。
到底什么是闭包
对于新人(当然了是说我了),看很多闭包的定义,代码,还是不知啥是闭包,云里雾里的,这里感谢方方老师的文章JS 中的闭包是什么?,看完后,虽然还是说不出啥是闭包,但现在已经知道啥是闭包了,果然用图说话最牛逼。(图在文章中,我就不放出来了)
闭包的应用
MDN 上这个例子也写的很好
调用Counter.value()
时,返回的是Counter
内部的变量privateCounter
;increment
内部没有返回值,这个方法只是执行了privateCounter + 1
操作,没有返回值;
同理decrement
是将privateCounter - 1
,也没有返回值;
所以执行Counter.increment
,会返回undefined
,但是接着操作Counter.value()
时就可以得到1
,因为执行上一步Counter.increment
时privateCounter
被+1
了。
今天在下面三段代码上花费了大量的时间,一直似懂非懂,心里不踏实。
代码一:
var name = 'window';
var obj = {
name: 'object',
getName: function() {
return this.name;
}
};
obj.getName(); //object
(obj.getName = obj.getName)(); //window 非严格模式下
代码二:
var name = 'window';
var obj = {
name: 'object',
getName: function() {
return function(){
return this.name;
}
}
};
obj.getName()(); //window
代码三:
var name = 'window';
var obj = {
name : 'object',
getName : function(){
var that = this;
return function(){
return that.name;
};
}
};
obj.getName()(); //object
今天在看阮一峰的博客学习Javascript闭包(Closure),对代码二、代码三部分很是不解,看到一网友搬出犀牛书(还没看过,我买了红宝石书才看了一点点)里的话,实在不解什么是作为函数调用,什么是作为方法调用;
《Javascript权威指南》上说:如果嵌套函数作为函数调用,其this值不是全局对象(非严格模式下)就是undefined(严格模式下); 如果嵌套函数作为方法调用,其this值指向调用它的对象。
又有一位网友说
每个函数在被调用时,其活动对象都会自动取得两个特殊变量:this和arguments。内部函数在搜索这个变量时,只会搜索到其活动对象为止,因此永远不可能直接访问外部函数中的这两个变量(这一点通过前面的图可以看得更清楚)。意思就是说找到匿名函数中的this和arguments就不会再往下找了(这里的往下指的是外层的包含函数,和最外层的window全局环境),而匿名函数的this对象通常指向window,所以输出的是全局的那个字符串。不过,把外部作用域中的this对象保存在一个闭包能够访问到的变量里,就可以让闭包访问该对象了。
看到这里大概明白匿名函数的作用域是全局,继续翻看下面评论,大概意思是说“把this
保存在obj
作用域下的一个变量中,this
就在当前函数的作用域下了”。直到看完也是,似懂非懂,反正就是感觉哪里不对劲,但也说不上了。
直到看到【JavaScript】【函数】闭包闭包!这篇文章的代码一部分,终于明白其中的逻辑了。
下面就来分析其中的逻辑,我分析的方法就是把不懂的地方一个个用console
打印出来
代码二和代码一的区别是多了一层嵌套函数,this
值就不一样了。
ps:我一开始以为在代码二中再嵌套一层函数,就会打印出object
=_=|||
先来看代码一,明白之后,另外两段代码自然就懂了。
为什么obj.getName()
打印出来的是object
,因为这时getName
方法是在obj
的作用域下,所以this
指向obj
,返回值当然就是object
了。
接着看(obj.getName = obj.getName)()
删掉右边后,打印出的结果变成了object
,这就纳闷了。
ps:第一眼看上去,这啥玩意,把自己赋值给自己?这不是多此一举,直接用不就行了!
用console.log
打印出obj.getName
后,终于拨云见天,obj.getName = obj.getName
这句话的意思就是把getName
函数赋值给自己,这个时候就不是obj.getName
,而是getName
匿名函数了,匿名函数通常用的方法是()()
立即执行,此时再看匿名函数已经脱离obj
了,当然this
也就指向了全局,打印出window
。
再来看代码二,用console
打印出obj.getName()
会发现是一个匿名函数,而匿名函数的this通常会指向全局,所以也就不难理解了
理解上面两段代码,代码三也就很好理解了。
闭包中引用循环变量
廖雪峰的闭包在文中就很形象的讲解了函数中的引用会变化的变量会有什么后果,我节选了他的结论和代码。
返回闭包时牢记的一点就是:返回函数不要引用任何循环变量,或者后续会发生变化的变量。
如果一定要引用循环变量怎么办?方法是再创建一个函数,用该函数的参数绑定循环变量当前的值,无论该循环变量后续如何更改,已绑定到函数参数的值不变:
function count() {
var arr = [];
for (var i=1; i<=3; i++) {
arr.push((function (n) {
return function () {
return n * n;
}
})(i));
}
return arr;
}
var results = count();
var f1 = results[0];
var f2 = results[1];
var f3 = results[2];
f1(); // 1
f2(); // 4
f3(); // 9
这里的核心就是立即执行,如果不是立即执行的话,变量i
就是for
循环结束后的值了。