一、什么是闭包
学习闭包我们要清楚函数作用域、内存回收机制、作用域继承。
1.1 函数作用域 作用域我们可以认为它是一个封闭的盒子,只让它在这个盒子里面进行操作,也可以称这个盒子为独立作用域。在js中,一个函数要执行时就会在内存里面创建一个独立作用域————封闭的盒子。
比如在函数中第一一个变量,只能在函数这个独立作用域中使用(也就是封闭的盒子)。只要跳出这个作用域,就找不到该变量了。
而且函数执行完毕之后,这个独立作用域或(封闭的盒子)就会删除。有一种情况下这个封闭的盒子是不会删除的,那就是“闭包”,后面会讲到。
1.2 内存回收机制 内存回收机制就是不在用到的内存空间,系统会自动进行清理出空间提供给其他程序使用。那回收的前提是什么呢?
内部函数引用外部的函数的变量,外部函数执行完毕,作用域也不会删除。从而形成了一种不删除的独立作用域。
某一个变量或者对象被引用,因此在回收的时候不会释放它,因为被引用代表着被使用,回收机制不会对正在引用的变量或对象进行回收的。
1.3 作用继承 所谓的作用域继承,就像是儿子可以继承父亲的财产一样。比如我这里有一个大的盒子作为一个父级的作用域,然后在这个大的盒子里边放一个小的盒子,作为子作用域。我们规定可以在小盒子中获取到大盒子中的东西,大盒子不能获取小盒子里的东西就称为作用域继承。
在 JS 中,道理是一样的,在一个函数里边我们再声明一个函数,内部函数可以访问外部函数作用域的变量,而外部的函数不能获取到内部函数的作用域变量。
那好,上边的这几个概念理解了之后,什么是闭包对你来说已经不是什么问题。
什么是闭包,那就是在一个函数里边再定义一个函数。这个内部函数一直保持有对外部函数中作用域的访问(小盒子可以一直访问大盒子但大盒子不能访问小盒子)。
函数执行,形成一个独立作用域,保护里边的私有变量不受外界的干扰,除了保护私有变量外,还可以存储一些内容,这样的模式叫做闭包
闭包的作用:
-
1.保护变量私有化
定义在函数内部的变量就叫做私有变量 -
2.在函数外部访问函数内部的私有变量
利用闭包函数访问
闭包的特点
- 1.保护变量了私有化
优点:不去污染全局变量
缺点:外部不能去访问,需要闭包函数 - 2.可以在函数外部访问函数内部的变量
优点:不局限与私有变量
缺点:外部访问需要闭包函数 - 3.变量的生命周期
优点:变量的生命周期被延长了
缺点:一个不会被销毁的函数空间
什么时候使用闭包呢?
- 希望重用一个对象,又保护对象不被污染篡改时
缺点: 由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
二、解决问题:
1.闭包解决循环添加单机事件总是打印最后一个索引的问题
<button>1</button>
<button>2</button>
<button>3</button>
<button>4</button>
var l = document.querySelectorAll('button')
for(var i =0; i<l.length; i++){
(function(i){
l[i].onclick = function(){
alert(i)
}
})(i)
}
此处点击的是数字为2的按钮,打印为1,i从0开始计算
二、解决定时器不能传参的问题
function f(a){
setTimeout(function(a){
alert(a) //就算是传参也是ndefined
})
}
f();
解决这个问题:
function f(x){
return function f1(){
alert(x)
}
}
setTimeout(f(100),3000) //闭包向定时器参数
3.闭包持续增加一个值的大小(可以写在外面定义成全局变量,可是万一别人改了怎么办)
function ff() {
var num = 1;
return function () {
num++;
alert(num)
}
}
var ff1 = ff()
ff1() // 2
ff1() //3
ff1() //4
ff1() //5
console.log(num); //报错,函数内能访问函数外的变量,反之不行
三、两个小案例:
3.2、函数作为返回值:
function create() {
const a = 100;
return function () {
console.log(a);
}
}
const fn = create();
const a = 200;
fn();
执行过程:
函数执行在全局作用域,没有函数包裹, a = 200 也是全局作用域 函数定义是在 create 这个作用域 a = 100 也是在create这个作用域 在函数里面打印a,a是一个自由变量,自由变量寻找的时候 它会在它定义的地方去寻找,往上一级作用域寻找,这个 上一级作用域应该是他函数定义的作用域的上一级
3.2、函数作为参数:
// 函数作为参数
function print(fn) {
let a = 200;
fn();
}
let a = 100;
function fn() {
console.log(a);//100
}
print(fn);
同上层理念,我们执行fn函数调用的时候,执行的时候是在print这个作用域下面, 执行的是console.log(a) , a呢是一个自由变量,它在fn这个函数作用域里面, 它在fn这个作用域里面寻找有没有定义a,没有就向上一级寻找,找到 a = 100
得出结论:
所有的自由变量的查找,是在函数定义的地方,向上一级作用域查找,不是在函数执行的地方
四、总结:
闭包:形成闭包的条件
-
- 一个不会被销毁的函数执行空间
-
- 函数内部直接或者间接的返回一个函数
-
- 内部函数操作(访问, 赋值) 着外部函数的变量
- 当三个条件都满足的时候,我们管内部的函数叫做外部函数的闭包函数