MDN对闭包的定义是:函数与对其状态即词法环境(lexical environment)的引用共同构成闭包(closure)。也就是说,闭包可以让你从内部函数访问外部函数作用域。在JavaScript,函数在每次创建时生成闭包。
闭包:内部函数总是可以访问其所在的外部函数中声明的参数和变量,即使在其外部函数被返回(寿命终结)了之后。
通俗来讲 一个内部函数访问外部函数局部作用域内的变量 就产生了闭包
作用:延申变量的作用范围 ,使局部变量常驻在内存中不被销毁
缺点:变量不被销毁,发生内存泄漏
我们先通过一个错误的例子了解局部作用域和垃圾回收机制
function func() {
var num = 10;
console.log(num);
}
func();//输出10
console.log(num);//报错 num is not defined
结论:在一个函数的外部无法访问函数内部作用域定义的变量 即函数的局部作用域。而在函数内部,func函数后 1.定义变量 2.被console.log 输出 3.被调用之后 退出函数 变量i失去引用 转而被当垃圾回收。
在javascript中,如果一个对象不再被引用,那么这个对象就会被垃圾回收机制回收;
再来看一个简单的闭包案例
// 内部函数总是可以访问其所在的外部函数中声明的参数和变量,即使在其外部函数被返回(寿命终结)了之后。
function fn() {
var str = "这是一段字符!";
return function () {
return str;
}
}
let f =fn();
console.log(f());//输出字符串 被返回的方法外部访问了fn函数局部作用域内的变量str 产生了闭包
// f = "这是一段字符!";
// f()函数称为 闭包函数
闭包函数:声明在一个函数中的函数,叫做闭包函数。
结论:被当作返回值的方法f 在fn()方法的局部作用域中访问了str变量产生了闭包,同时我们在fn()函数外部通过变量 f 函数接收了返回值,f = str,成功访问了函数局部作用域内的srt变量; 变相的 srt变量也延申了其值到全局;
两个案例了解闭包的作用
定义一个数组 三秒后打印其内容
var arr = [11,22,33];
//1.常规方法
for (var i = 0;i<arr.length;i++){
setTimeout(()=>{
console.log(arr[i]);//打印输出
},3000)
}
//
结果:报错
undefined*3
原因:for循环是同步函数,而timeout的回调函数是一个异步函数,for循环执行完成之后, 其中的变量 i 已经被后续for循环的i++操作重新赋值 现在的值为3,导致报错。
解决方案:通过闭包 对每次for循环产生变量i值进行引用 产生闭包作用域 保留变量存活状态 但是内存中增加了 i=0,i=1,i=2;四个值 产生内存溢出。
//2.采用闭包方式
for (var i = 0;i<arr.length;i++){
//通过for循环 产生4个立即函数 并将i值传入 产生引用关系
(function (i) {
setTimeout(()=>{
console.log(arr[i]);//打印输出
},3000)
})(i)
//Timeout中的回调方法访问了立即函数中的变量 i 产生闭包
}
结果:
[0,1,2,3]
正常输出,延申变量i的使用范围 产生闭包
下面再来看一个经典案例
点击三个按钮 获取其索引值
<ul>
<li>
<button>按钮1</button>
</li>
<li>
<button>按钮2</button>
</li>
<li>
<button>按钮3</button>
</li>
</ul>
<script>
//1.获取元素
var lis = document.querySelectorAll("button");
//2.通过动态添加属性方式 解决for循环是同步方法的问题
// for(var i = 0;i<lis.length;i++){
// lis[i].index = i;//动态添加属性
// lis[i].onclick = function () {
// console.log(i);
// console.log(this.index);//通过调用onclik方法的对象 输出动态添加的属性值
// }
// }
//3.通过闭包实现
for(var i = 0;i<lis.length;i++){
//利用for循环创立4个立即执行函数
(function (i) {
//向立即执行函数中传递当前i变量的值 产生引用外部遍历关系 产生了闭包,立即执行函数就是闭包函数
lis[i].onclick = function () {
console.log(i);
}
})(i)
}
</script>
立即执行函数
js立即执行函数可以让你的函数在创建后立即执行,可以让你的函数在定义后立即被执行,这种模式本质上就是函数表达式(命名的或者匿名的),在创建后立即执行。
常见方式
//1.加括号
(function (传值) {
})(传值)
//2.加特殊符号 +-x~! void new都可以
!function (str) {
console.log(str)
}("这是字符!")
作用:
1.创建一个独立的作用域。内部变量外部函数无法访问。
2.避免其它全局变量污染,函数为匿名函数。
3.使用作用域外的变量可通过参数传值进行引用。