一、变量的作用域
要理解闭包,首先必须理解Js的作用域。
咱们常说的作用域就是两种:全局作用域和局部作用域(函数作用域)。
js这个弱脚本语言之中的特殊之处,就在于函数内部可以直接读取全局变量。
二、闭包的定义
在一个作用域可以访问另外一个函数内部的局部变量 ,或者说一个函数(子函数)访问另一
个函数(父函数)中的变量。 此时就会有闭包产生 ,那么这个变量所在的函数我们就称之为闭
包函数。
function aaa() {
var a = 0;
return function () {
alert(a++);
};
}
var fun = aaa();
fun(); //1
这就是一个典型的闭包结构
由于作用域链的结构,外围函数是无法访问内部变量的,为了能够访问内部变量,我们就可以使用闭包,闭包的本质还是函数。
三、闭包的两个小注意点
(1)变量的指向不同
先上代码
function fn () {
let a = 10
return function () {
a+=2
console.log(a)
}
}
const a = fn()
a() // 12
const b = fn()
b() // 12
b() // 14
a() // 14
b() // 16
每次执行fn函数时,都会生成一个fn的a变量和局部作用域,函数执行完毕以后,fn的作用域就会直接被销毁,但是a变量被闭包函数引用,所以依旧会进行保留,所以,最终剩下两个a的变量对象,因此我们声明的两个全局a和b在操作fn时,指向的是不同的数据
(2)变量的值需要注意
上代码
function fn () {
const arr = []
for (var i = 0; i <= 10; i++) {
arr[i] = function () {
return i
}
}
return arr
}
const arr = fn()
console.log(arr[0]()) // 11
console.log(arr[5]()) // 11
console.log(arr[9]()) // 11
执行环境和变量对象在运行函数时生成
所以,当执行const arr = fn();时,只是定义函数,而没有执行,真正产生环境变量的时间是在console.log(arr0);这三句的时候,此时fn的变量对象中i值是什么呢?很简单,看它return的时候i的值,显然i的值是11,所以,最后三句输出的都是11
那我们想要让其输出依次0-11又该怎么去进行修改呢?
最简单的办法就是将var这个全局声明的关键字换成let这个声明关键字
function fn () {
const arr = []
for (let i = 0; i <= 10; i++) {
arr[i] = function () {
return i
}
}
return arr
}
const arr = fn()
console.log(arr[0]()) // 5
console.log(arr[2]()) // 2
console.log(arr[6]()) // 6
console.log(arr[5]()) // 5
console.log(arr[9]()) // 9
let关键字声明,会形成一个死区的效果
这里我引用一下MDN里面对于的let作用域死区的解释
let声明的变量只在其声明的块或子块中可用,这一点,与var相似。二者之间最主要的区别在于var声明的变量的作用域是整个封闭函数。
function varTest() {
var x = 1;
{
var x = 2; // 同样的变量!
console.log(x); // 2
}
console.log(x); // 2
}
function letTest() {
let x = 1;
{
let x = 2; // 不同的变量
console.log(x); // 2
}
console.log(x); // 1
}
第二种稍微复杂的办法就是我们可以在里面再加入一个闭包函数
function fn () {
const arr = []
for (var i = 0; i <= 10; i++) {
arr[i] = (function (i) {
return function () {
return i
}
})(i)
}
return arr
}
const arr = fn()
console.log(arr[0]()) // 0
console.log(arr[2]()) // 2
console.log(arr[6]()) // 6
console.log(arr[5]()) // 5
console.log(arr[9]()) // 9
这里我们就是将i当做参数传入到自调用函数之中,然后在其内部定义一个闭包函数进行返回,这样也可以达到目的
四、闭包的优缺点:
闭包的主要作用: 延伸了变量的作用范围, 因为闭包函数中的局部变量不会等着闭包函数执行完就销毁, 因为还有别的函数要调用它 , 只有等所有的函数都调用完了他才会销毁 闭包会造成内存泄漏,如何解决:当我们不再需要使用的时候,将其手动定义为null就可以了
function fn () {
const arr = []
for (var i = 0; i <= 10; i++) {
arr[i] = function () {
return i
}
}
return arr
}
let arr = fn() // 这里我们在全局中引用了闭包函数
...省略其他代码
arr = null // 释放内存
有元素引用的话,将元素引用也置为空就可以了
闭包不仅仅可以实现函数内部的作用域访问这个函数中的局部变量还可以实现全局作用域或者是别的地方的作用域也可以访问到函数内部的局部变量 , 实现方法就是 return 了一个函数,所以 return 函数也是我们实现闭包的一个主要原理, 因为返回的这个函数本身就是我们fn 函数内部的一个子函数 ,所以子函数是可以访问父函数里面的局部变量的, 所以返回完毕之后 ,外面的函数一调用, 就会回头调用返回的这个函数, 所以就可以拿到这个子函数对 应的父函数里面的局部变量.
注意:
1、由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,会造成网页的性能问题,在 IE (仅限于IE9以前)中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
2、闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),也就是你用闭包来隐藏自己的这时一定要小心,不要随便改变父函数内部变量的值。