什么是闭包?
闭包:闭包就是访问另一个函数作用域变量的函数
闭包简单定义为在一个函数内部的函数
形成闭包的原因:存在上级作用域的引用
变量的作用域
变量的作用域分为两种:全局变量和局部变量
- javascript的特殊之处--函数内部可以直接读取全局变量
var a = 10; //全局变量
function f1(){
console.log(a);
}
f1();//输出 10
f1()是在全局下创建的,所以a的上级作用域就是window,输出的是10
- 函数外部不能读取函数内的局部变量
function f2(){
var b = 1;
}
console.log(b); //error "b is not defined"
f2()的b是函数内部的局部变量,函数外部不能读取,console.log(b)的b作用域是全局(window),然而全局中并未定义b,所以报错. 3.这里有一个地方需要注意,函数内部声明变量的时候,一定要使用var命令。如果不用的话,你实际上声明了一个全局变量!
function f2(){
b = 1;
}
f2();
console.log(b); //输出为1
function f2(){
var b = 1;
console.log(b);
}
f2();
console.log(b);// error "b is not defined"
初识闭包
function foo(){
var a = 2;
function bar(){
console.log(a);
}
return bar;
}
var baz=foo();
baz();//输出 2
bar()词法作用域能访问foo()的内部作用域,将bar()函数对象本身当作返回值。foo()执行后,其返回值(也就是内部的bar()函数)赋值给变量baz并调用baz(),实际上只是通过不同的标识符引用调用了内部的函数bar().在foo()执行后,通常会期待foo()的整个内部作用域都被销毁,因为我们知道引擎有垃圾回收器用来释放不在使用的内存空间.由于看上去foo()内容不会再被使用,所以很自然地会考虑对其进行回收。
闭包的神奇之处正是可以阻止这件事情发生,事实上内部作用域依然存在,因此没有被回收。谁在使用这个内部作用域呢?原来是
bar()本身在使用。bar()拥有涵盖foo()内部作用域的闭包,使得该作用域能够一直存活,以供bar()在之后任何时间进行引用。bar()依然持有对该作用域的引用,而这个引用就叫做闭包。
分析闭包经典使用场景
1. 使用return 返回函数
function foo(){
var a = 2;
function bar(){
console.log(a);
}
return bar;
}
var baz = foo();
baz(); //输出为2
其实可以这样写,如下:
function foo(){
var a = 2;
function bar(){
console.log(a);
}
return bar;
}
foo()();//输出为2
bar函数是一个闭包,它在foo函数内部定义的函数
知识拓展--简化
- f()执行f函数,返回子函数
- f()()执行子函数,返回孙函数
- f()()()执行孙函数,返回重孙函数
注意:但注意,如果想这样执行,函数结构必须是这样,f的函数体里要return 子函数,子函数里要return 孙函数,如果没有return关键字,是不能这样连续执行的,会报错的。
function fun(){
return 5 //子函数
}
var a=fun
var b=fun()
console.log(a); //[Function:fun]
console.log(b);// 输出为5
function fun(){
return k;
function k(){
return '555 '
}
}
var a=fun;
var b=fun();
var kk=fun()();
console.log(a); //[Function:fun]
console.log(b);// [Function:k]
console.log(kk); //555
函数只要是要调用它进行执行的,都必须加括号。此时,函数实际上等于函数的返回值或者执行效果,当然,有些没有返回值,但已经执行了函数体内的行为,就是说,加括号的,就代表将会执行函数体代码。
2. 函数作为参数
var a = '函数外'
function fo(){
var a = ' fo 函数内'
function foo(){
console.log(a)
}
return foo
}
function f(p){
var a = 'f 函数内'
p()
}
f(fo())//输出为fo函数内
使用
retuen foo返回,foo()是一个闭包,f(fo())执行的参数就是函数foo,因为foo()中console.log(a)中的a的上级作用域是函数fo,所以输出的是fo函数内部定义的a的内容fo 函数内。思考:如果将上面的f()函数改一下你是否能理解?如果你能理解那么上面的补充知识你也懂了。
var a = '函数外'
function fo(){
var a = ' fo 函数内'
function foo(){
console.log(a)
}
return foo
}
function f(p){
var a = 'f 函数内'
p()()
}
f(fo)//输出为fo函数内
3.IIFE(自执行函数)
var a = 2;
(function IIFE(){
console.log(a);
})() //输出为 2
这样产生的是闭包
IIFE(),但是严格来讲它并不是闭包,为什么?因为IIFE()并不是在它本身的词法作用域以外执行的。自执行函数本身是没有变量作用域的,因此会使用外层函数的变量作用域。所以输出为2.尽管IIFE本身并不是观察闭包的恰当例子,但它的确创建了闭包,并且也是最常用来创建可以被封闭起来的闭包的工具。因此IIFE的确同作用域息息相关,即使本身并不会真的创建作用域。
4. 定时器setTimeout(回调函数都是闭包)
function wait(m){
setTimeout(function timer(){
console.log(m);
},1000)
}
wait("Hello");//1000毫秒后输出 Hello
闭包
timer传递给setTimeout()。timer()具有涵盖wait()作用域的闭包,因此保存了对变量m的引用。wait()执行1000毫秒后,它的内部作用域并不会消失,timer()函数依然保有wait()作用域的闭包。
知识拓展-回调函数
一个函数被作为参数传递给另一个函数(在这里我们把另一个函数叫做“otherFunction”),回调函数在otherFunction中被调用。
注意回调函数都是闭包
- 将回调函数的参数作为与回调函数同等级的参数进行传递
- 回调函数的参数在调用回调函数内部创建
5.循环和闭包
思考下面的代码:
for(var i = 1;i<=5;i++ ){
(function(){
setTimeout(function timer(){
console.log(i);
},i*1000)
})();
}
输出的答案是什么呢?
1,2,3,4,5?错误正确输出是6,6,6,6,6,6。为什么呢?解析一下6是从哪来的?相信你和我一开始一样都是千百个???,而且循序的终止条件是i不在是<=5。条件首次成立时i的值为6,因此,输出显示的是循环结束i的最终值。延迟函数的回调会在循环结束时才会执行,不管定时器setTimeout的时间是多少,所有的回调函数依然是在循环结束后才会被执行,因此每一次都是输出一个6来。想要输出1,2,3,,4,5的结果,那么要如何修改我们的代码呢?
方法一(传参)
for(var i = 1;i<=5;i++ ){
(function(j){
setTimeout(function timer(){
console.log(j);
},j*1000)
})(i);//传入参数 i
}
//输出1,2,3,4,5
通过传参的方法,将
i传递进去,将变量名取为j来获取i的值
方法二(定义变量接收i的值)
for(var i = 1;i<=5;i++ ){
(function(){
var j=i;
setTimeout(function timer(){
console.log(j);
},j*1000)
})();
}
//输出1,2,3,4,5
通过定义变量的方法,将变量名取为
j用来在每个迭代中存储i的值
方法三(将var改为let)
for(let i = 1;i<=5;i++ ){
(function(){
setTimeout(function timer(){
console.log(i);
},i*1000)
})();
}
//输出1,2,3,4,5
for循环头部使用let声明,let具有块级作用域,let每次迭代都会声明,随后的每个迭代都是使上一个迭代结束时的值来初始化这个变量,迭代变量的作用域仅限于for循环块内部。let可以在任意代码块中隐式的创建或是劫持块作用域;var声明的其中块代码的作用域是全局的,所以当执行完循环之后运行setTimeout中闭包之后,其中引用的i就是全局作用域中的i,然而let就不会。
知识拓展-- let和var的区别
for(let i = 0;i<=5;i++){
console.log(i);
}
console.log(i,'----');
//输出 i is not defined
for(var i = 0;i<=5;i++){
console.log(i);
}
console.log(i,'++++');
//输出0,1,2,3,4,5,6 ++++
var在for循环头部声明变量i,在for循环结束后i会被暴露在全局作用域中,然而let就不会,因为let可以在任意代码块中隐式的创建或是劫持块作用域
思考题
思考题1
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
return function(){
//console.log(this);
return this.name;
};
}
};
console.log( object.getNameFunc()());
输出的是
The Window还是"My Object?首先我们需要了解this.name中this指向的到底是什么?
思考题1解答
this指向的是全局(window)因为上一级getNameFunc()函数没有name属性 ,因此就去找全局的name属性,所以输出“The Window”
思考题2
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
var that = this;
return function(){
return that.name;
};
}
};
console.log(object.getNameFunc()());
输出的是
The Window还是"My Object?首先我们需要了解that = this中this指向的到底是什么?
思考题2解答
将
this赋值给一个变量,内部函数是可以访问外部函数变量的.所以this指向的是object,由于this关键字不是在包含的函数中引用的,而是通过that=this这个调用的,所以这个this不是在闭包内的,因此这个this就不能调用函数体内的全局对象,而是他的局部对象object.name,所以输出的是"My Object"
使用闭包注意点
1)由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
2)闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。
总结
闭包第一次去认真了解,初识闭包发现里面涉及的知识很多,让我对于js的认识更加深刻了!如果有问题欢迎指出,谢谢您的阅读!本人大三,正在学习前端,欢迎大家一起学习探讨!