一、经典面试题
function outer(){
var num = 0;
return function add(){
num++;
console.log(num);
}
}
var func1 = outer();
func1(); //1
func1(); //2
var func2 = outer();
func2(); //1
func2(); //2复制代码
图解:
二、什么是闭包
函数中返回了另一个函数,闭包就是能够读取其他函数内部变量的函数。
1、没有形成闭包的情况下,函数外部是不能访问函数内部的局部变量的。
function f1(){
var n = 1;
}
console.log(n);复制代码
结果:
2、形成闭包时,函数外部是可以访问函数内部的局部变量的。
function f1(){
var n = 1;
return function f2(){
console.log(n);
}
}
var result = f1();
result();复制代码
f2在f1的内部,它是可以返问f1中的变量的,那我们只要把f2作为返回值,就可以在f1外部读取它的内部变量了。
闭包形成的条件
1、函数嵌套
2、内部函数引用外部函数的局部变量
三、闭包的特性
能够记忆自己定义时所处的作用域环境。
例1
var inner;
function outer(){
var a = 1;
inner=function(){
console.log(a);//这个函数虽然在外面执行,但能够记忆住定义时的那个作用域,a是1
}
}
outer();
var a = 2;
inner(); //一个函数在执行的时候,找闭包里面的变量,不会理会当前作用域。复制代码
结果:
例2
function outer(x){
function inner(y){
console.log(x+y);
}
return inner;
}
var inn=outer(3);//数字3传入outer函数后,inner函数中x便会记住这个值
inn(5);//当inner函数再传入5的时候,只会对y赋值,所以最后弹出8复制代码
四、闭包的作用
1、使用闭包可以返回函数中的变量。
2、可以使变量长期保存在内存中,生命周期比较长。(原因:内部函数会把包含函数的活动对象填到它的作用域链中。)
3、可以用来实现js模块化。
JS模块:具有特定功能的js文件,将所有的数据和功能都封装在一个函数内部(私有的),只向外暴露一个包信n个方法的对象或函数,模块的使用者,只需要通过模块暴露的对象调用方法来实现对应的功能。
例子:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
</body>
<script src="myModule.js"></script>
<script>
myModule.eat();
myModule.sleep()
</script>
</html>复制代码
myModule函数
(function(){
//私有数据
var eating = "eat";
var sleeping = "sleep";
//内部方法
function eat(){
console.log('eat()' + eating);
}
function sleep(){
console.log('sleep()' + sleeping);
}
//搭建与外部联系的桥梁
window.myModule = {
eat:eat,
sleep:sleep
}
})();复制代码
加分项:
闭包不能滥用,否则会导致内存泄漏。影响页面性能。闭包使用完毕后,要立即释放资源,将引用变量指向null。
五、闭包的内存泄漏
栈内存提供一个执行环境,即作用域,包括全局作用域和私有作用域,那他们什么时候释放内存的?
全局作用域----只有当页面关闭的时候全局作用域才会销毁
私有的作用域----只有函数执行才会产生
一般情况下,函数执行会形成一个新的私有的作用域,当私有作用域中的代码执行完成后,我们当前作用域都会主动的进行释放和销毁。但当遇到函数执行返回了一个引用数据类型的值,并且在函数的外面被一个其他的东西给接收了,这种情况下一般形成的私有作用域都不会销毁。
例:
function f1(){
var n = 1;
return function f2(){
console.log(n);
}
}
var result = f1();
result();复制代码
f1函数内部的私有作用域会被一直占用的,发生了内存泄漏。所谓内存泄漏指任何对象在您不再拥有或需要它之后仍然存在。所以闭包不能滥用,否则会导致内存泄漏。影响页面性能。闭包使用完毕后,要立即释放资源,将引用变量指向null。
六、闭包中的this
var name = "The Window";
var object = {
name: "My object",
getNameFunc: function() {
return function() {
return this.name;
};
}
}
console.log(object.getNameFunc()()); // "The Window"复制代码
为什么最后的结果是"The Window"而不是object里面的name"My object"呢?
首先,要理解函数作为函数调用和函数作为方法调用。
我们把最后的一句拆成两个步骤执行:
var first = object.getNameFunc();
var second = first();复制代码
其中第一步,获得的first为返回的匿名函数,此时的getNameFunc()作为object的方法调用,如果在getNameFunc()中使用this,此时的this指向的是object对象。
第二部,调用first函数,可以很清楚的发现,此时调用first函数,first函数没有在对象中调用,因此是作为函数调用的,是在全局作用域下,因此first函数中的this指向的是window。
为什么匿名函数没有取得其包含作用域(外部作用域)的this对象呢?
每个函数被调用时,其活动对象都会自动取得两个特殊变量:this和arguments。内部函数在搜索这两个变量时,只会搜索到其活动对象为止,因此永远不可能直接访问外部函数中的这两个变量。 《Javascript高级程序设计》
那么,如何获得外部作用域中的this呢?
可以把外部作用域中的this保存在闭包可以访问到的变量里。如下:
var name = "The Window";
var object = {
name: "My object",
getNameFunc: function() {
var that = this; // 将getNameFunc()的this保存在that变量中
var age = 15;
return function() {
return that.name;
};
}
};
console.log(object.getNameFunc()()); // "My object"复制代码
其中,getNameFunc()执行时的活动对象有:that/age/匿名函数,在执行匿名函数时,同时引用了getNameFunc()中的活动对象,因此可以获取that和age的值。但是由于是在全局环境中调用的匿名函数,因此匿名函数内部的this还是指向window。
var name = "The Window";
var object = {
name: "My object",
getNameFunc: function() {
var that = this; // 将getNameFunc()的this保存在that变量中
var age = 15;
return function() {
console.log(this.name); //匿名函数的this The Window
return that.name; //外部函数的this My object
};
}
};
console.log(object.getNameFunc()()); // "My object"复制代码
七、闭包的应用
我们要实现这样的一个需求: 点击某个按钮, 提示"点击的是第n个按钮"。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<button>测试1</button>
<button>测试2</button>
<button>测试3</button>
<script type="text/javascript">
var btns = document.getElementsByTagName('button');
//遍历加监听
var btns = document.getElementsByTagName('button');
for (var i = 0; i < btns.length; i++) {
btns[i].onclick = function () {
console.log('第' + (i + 1) + '个')
}
}
</script>
</body>
</html>复制代码
但是,当点击任意一个按钮,后台都是弹出“第四个”,这是因为i是全局变量,点击事件是异步操作,需要等同步事件执行完才能执行,所以执行到点击事件时i的值为3。
修改方法1:使用let声明i,因为let不会变量提升
for (let i = 0; i < btns.length; i++) {
btns[i].onclick = function () {
console.log('第' + (i + 1) + '个')
}
}复制代码
方法2;使用闭包
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<button>测试1</button>
<button>测试2</button>
<button>测试3</button>
<script type="text/javascript">
var btns = document.getElementsByTagName('button');
//遍历加监听
var btns = document.getElementsByTagName('button');
for (var i = 0; i < btns.length; i++) {
(function(j){
btns[j].onclick = function () {
console.log('第' + (j + 1) + '个')
}
})(i)
}
</script>
</body>
</html>复制代码
解析:在执行for循环同步代码的时候,把i的值赋值给j,这样内部的匿名函数就记录下了每一次for循环的i(也就是j)的值,然后在执行点击事件时就用自己记录下来的j的值,达到想要的效果。