【闭包】
什么是闭包?
1.闭包就是能够读取其他函数内部变量的函数.在本质上,闭包是将函数内部和函数外部连接起来的桥梁
闭包的作用?
1.读取函数内部变量
2.让这些变量的值始终保持在内存中
3.实现块级作用域
闭包的缺点?
1.闭包引用了祖先函数的作用域,所以滥用闭包会有内存问题
2.让函数的变量都保存在内存中,内存消耗变大。使用不当会造成内存泄漏
1. 闭包的定义
外层函数嵌套内层函数, 内层函数使用外层函数的局部变量,把内层函数作为外层函数的返回值。
function A() {
let a = 1
function B() {
console.log(a)
}
return B
}
2.闭包的应用
用闭包解决递归问题
function factorial(num) {
if(num<= 1) {
return 1;
} else {
return num * factorial(num-1)
}
}
var anotherFactorial = factorial
factorial = null
anotherFactorial(4) // 报错 。
//最好是return num* arguments.callee(num-1),arguments.callee指向当前执行函数,但是在严格模式下不能使用该属性也会报错,所以借助闭包来实现
// 使用闭包实现递归
function newFactorial = (function f(num){
if(num<1) {return 1}
else {
return num* f(num-1)
}
})
//这样就没有问题了,实际上起作用的是闭包函数f,而不是外面的函数newFactorial
用闭包模仿块级作用域
- 例1:
for(var i=0; i<10; i++){ console.log(i) } alert(i) // 变量提升,弹出10 //为了避免i的提升可以这样做 (function () { for(var i=0; i<10; i++){ console.log(i) } )() alert(i) // undefined 因为i随着闭包函数的退出,执行环境销毁,变量回收 - 例2:
for (var i = 0; i < 5; i++) { (function(i) { setTimeout(function() { console.log(i) }, 1000); })(i); }
封装私有变量
function create_counter(initial) {
var x = initial || 0;
return {
inc: function () {
x += 1;
return x;
}
}
}
var c1 = create_counter();
c1.inc(); // 1
c1.inc(); // 2
c1.inc(); // 3
var c2 = create_counter(10);
c2.inc(); // 11
c2.inc(); // 12
c2.inc(); // 13
在返回的对象中,实现了一个闭包,该闭包携带了局部变量x,并且,从外部代码根本无法访问到变量x。换句话说,闭包就是携带状态的函数,并且它的状态可以完全对外隐藏起来。
3.闭包的作用
- 读取函数内部变量
- 让变量的值始终保持在内存中
4.闭包的注意事项
通常,函数的作用域及其所有变量都会在函数执行结束后被销毁,被垃圾回收机制回收。但是,在创建了一个闭包以后,这个函数的作用域就会一直保存到闭包不存在为止。
function makeAdd(x) {
return function(y) {
return x + y;
};
}
var add1 = makeAdder(5);
var add2 = makeAdder(10);
console.log(add1(4)); // 9
console.log(add2(3)); // 13
// 释放对闭包的引用
add5 = null;
add10 = null;
闭包只能取得包含函数中任何变量的最后一个值,这是因为闭包所保存的是整个变量对象,而不是某个特殊的变量。
function test(){
var arr = [];
for(var i = 0;i < 10;i++){
arr[i] = function(){
return i;
};
}
for(var a = 0;a < 10;a++){
console.log(arr[a]());
}
}
test(); // 连续打印 10 个 10
闭包中的this
var name = "The Window";
var obj = {
name: "My Object",
getName: function(){
return function(){
return this.name;
};
}
};
console.log(obj.getName()()); // The Window
//将这一部分解:console.log( function(){return this.name;};() );
改变作用域
var name = "The Window";
var obj = {
name: "My Object",
getName: function(){
var that = this;
return function(){
return that.name;
};
}
};
console.log(obj.getName()()); // The Window
//将这一部分解:console.log( function(){return this.name;};() );
5.闭包的缺点
- 闭包的缺点就是常驻内存会增大内存使用量,并且使用不当很容易造成内存泄露。
- 如果不是因为某些特殊任务而需要闭包,在没有必要的情况下,在其它函数中创建函数是不明智的,因为闭包对脚本性能具有负面影响,包括处理速度和内存消耗。
6.关于闭包的面试题
第一个JS闭包问题
function fun(n,o) {
console.log(o)
return {
fun:function(m){
return fun(m,n);
}
};
}
var a = fun(0); a.fun(1); a.fun(2); a.fun(3);//undefined,?,?,?
var b = fun(0).fun(1).fun(2).fun(3);//undefined,?,?,?
var c = fun(0).fun(1); c.fun(2); c.fun(3);//undefined,?,?,?
//问:三行a,b,c的输出分别是什么?
//答案:
//a: undefined,0,0,0
//b: undefined,0,1,2
//c: undefined,0,1,1
(1)先确定这三个函数的关系
这段代码中出现了三个fun函数,所以第一步先搞清楚,这三个fun函数的关系,哪个函数与哪个函数是相同的。
function fun(n,o) {
console.log(o)
return {
fun:function(m){
//...
}
};
}
先看第一个fun函数,属于标准具名函数声明,是新创建的函数,他的返回值是一个对象字面量表达式,属于一个新的object。这个新的对象内部包含一个也叫fun的属性,通过上述介绍可得知,属于匿名函数表达式,即fun这个属性中存放的是一个新创建匿名函数表达式。
注意:所有声明的匿名函数都是一个新函数。
所以第一个fun函数与第二个fun函数不相同,均为新创建的函数。最内层的return出去的fun函数不是第二层fun函数,是最外层的fun函数。所以,三个fun函数的关系也理清楚了,第一个等于第三个,他们都不等于第二个。
(2)函数是怎样调用的
为了方便看把代码重新写一下
function fun(n,o) {
console.log(o)
return {
fun:function(m){
return fun(m,n);
}
};
}
var a = fun(0); a.fun(1); a.fun(2); a.fun(3);//undefined,?,?,?
var b = fun(0).fun(1).fun(2).fun(3);//undefined,?,?,?
var c = fun(0).fun(1); c.fun(2); c.fun(3);//undefined,?,?,?
//问:三行a,b,c的输出分别是什么?
第一行 a
var a = fun(0); a.fun(1); a.fun(2); a.fun(3);
第一个fun(0)是在调用第一层fun函数。第二个fun(1)是在调用前一个fun的返回值的fun函数,所以:第后面几个fun(1),fun(2),fun(3),函数都是在调用第二层fun函数。
遂:
- 在第一次调用fun(0)时,o为undefined;
- 第二次调用fun(1)时m为1,此时fun闭包了外层函数的n,也就是第一次调用的n=0,即m=1,n=0,并在内部调用第一层fun函数fun(1,0);所以o为0;
- 第三次调用fun(2)时m为2,但依然是调用a.fun,所以还是闭包了第一次调用时的n,所以内部调用第一层的fun(2,0);所以o为0。
- 第四次同理;
即:最终答案为 undefined,0,0,0
第二行 b
var b = fun(0).fun(1).fun(2).fun(3);//undefined,?,?,?
先从fun(0)开始看,肯定是调用的第一层fun函数;而他的返回值是一个对象,所以第二个fun(1)调用的是第二层fun函数,后面几个也是调用的第二层fun函数。
遂:
- 在第一次调用第一层fun(0)时,o为undefined;
- 第二次调用 .fun(1)时m为1,此时fun闭包了外层函数的n,也就是第一次调用的n=0,即m=1,n=0,并在内部调用第一层fun函数fun(1,0);所以o为0;
- 第三次调用 .fun(2)时m为2,此时当前的fun函数不是第一次执行的返回对象,而是第二次执行的返回对象。而在第二次执行第一层fun函数时时(1,0)所以n=1,o=0,返回时闭包了第二次的n,遂在第三次调用第三层fun函数时m=2,n=1,即调用第一层fun函数fun(2,1),所以o为1;
- 第四次调用 .fun(3)时m为3,闭包了第三次调用的n,同理,最终调用第一层fun函数为fun(3,2);所以o为2;
即最终答案:undefined,0,1,2
第三行 c
var c = fun(0).fun(1); c.fun(2); c.fun(3);//undefined,?,?,?
根据前面两个例子,可以得知:
fun(0)为执行第一层fun函数,.fun(1)执行的是fun(0)返回的第二层fun函数,这里语句结束,遂c存放的是fun(1)的返回值,而不是fun(0)的返回值,所以c中闭包的也是fun(1)第二次执行的n的值。c.fun(2)执行的是fun(1)返回的第二层fun函数,c.fun(3)执行的也是fun(1)返回的第二层fun函数。
遂:
- 在第一次调用第一层fun(0)时,o为undefined;
- 第二次调用 .fun(1)时m为1,此时fun闭包了外层函数的n,也就是第一次调用的n=0,即m=1,n=0,并在内部调用第一层fun函数fun(1,0);所以o为0;
- 第三次调用 .fun(2)时m为2,此时fun闭包的是第二次调用的n=1,即m=2,n=1,并在内部调用第一层fun函数fun(2,1);所以o为1;
- 第四次.fun(3)时同理,但依然是调用的第二次的返回值,遂最终调用第一层fun函数fun(3,1),所以o还为1
即最终答案:undefined,0,1,1
第二个JS闭包问题
循环中使用闭包解决 var 定义函数的问题
for ( var i=1; i<=5; i++) {
setTimeout( function timer() {
console.log( i );
}, i*1000 );
}
首先因为 setTimeout 是个异步函数,所有会先把循环全部执行完毕,这时候 i就是 6 了,所以会输出一堆 6。
解决办法两种,第一种使用闭包
for (var i = 1; i <= 5; i++) {
(function(j) {
setTimeout(function timer() {
console.log(j);
}, j * 1000);
})(i);
}
第二种就是使用 setTimeout 的第三个参数
for ( var i=1; i<=5; i++) {
setTimeout( function timer(j) {
console.log( j );
}, i*1000, i);
}
第三种就是使用 let 定义 i 了
for ( let i=1; i<=5; i++) {
setTimeout( function timer() {
console.log( i );
}, i*1000 );
}
因为对于 let 来说,他会创建一个块级作用域,相当于
{ // 形成块级作用域
let i = 0
{
let ii = i
setTimeout( function timer() {
console.log( ii );
}, i*1000 );
}
i++
{
let ii = i
}
i++
{
let ii = i
}
...
}