一、什么是闭包
GC 垃圾回收
通常情况下,函数被调用后,内部的变量 会自动回收,不用的变量 内存自动释放
特殊情况下,例函数里的变量 还会被 用到的就不会被回收 例: 3按钮 点击按钮 弹出 索引+1
1. 闭包的底层:栈
计算机 每调用一次函数时,内存就会 在内存中开辟一块空间,这个空间叫栈
整个函数的所有局部变量都在这个栈中运行
栈的本质:函数执行时会分配空间,函数结束时会回收
闭包可以做到的是:函数执行完了,栈也不回收,这就叫闭包
2.把函数当作对象处理
对于某些语言来说,例java,不能存在独立的方法,一定是寄存在某个对象上,不管是静态的动态的,而js不一样,js中有独立存在的函数,只要函数调用时,它就会创建一个对象出来,这个对象就包含函数的各种细节
function show(){
let a=12;
document.onclick=function(){
alert(a);
}
}
// 总结:尽管 a 是局部变量,但它不会被回收,因为 document.onclick 还会用到它
二、闭包的优缺点
闭包的好处:
延展了函数的局部作用域 的生命周期
正常情况下,一个函数被调用后,它的作用域 就会被销毁;
但当有闭包的情况时,局部作用域暂时不会销毁
闭包的缺点:
不能立即释放作用域,销毁局部作用域
三、产生闭包的几种情况
闭包是一个函数,可以访问独立数据的函数,
在一个作用域中可以访问另一个作用域的变量或者函数(嵌套的函数 访问了它外部的变量!!! (也可以是函数)
访问的外部函数是匿名函数时,Closure: 没有名字的函数)
function fn() { var n = 10; return n; }
fn(); // 未发生闭包:
发生闭包: 函数作为返回值
1.实例:获取1-10的随机数
// 获取1-10的随机数
function getRandom(){
var random = parseInt(Math.random()*10) + 1;
return function(){
return random;
}
}
var fn = getRandom();
console.log(fn());
2.实例:计算两个数的和
// 计算两个数的和
function getSum(n){
return function(m){
return n + m;
}
}
// 求 100 + m
var fn = getSum(100);
// 求 1000 + m
var fn1000 = getSum(1000);
console.log(fn(1)); // 101
console.log(fn1000(1)); // 1001
3.实例:点击ul下的li,打印li对应的索引值
// 方式一:
var heros = document.getElementById('heros');
var list = heros.children; // 获取ul下的所有li
for (var i = 0; i < list.length; i++) {
var li = list[i];
li.index = i;
li.onclick = function () { // 给 li 注册点击事件
console.log(this.index); // 输出当前li对应的索引
}
}
// 方式二:闭包
var heros = document.getElementById('heros');
var list = heros.children;
for (var i = 0; i < list.length; i++) {
var li = list[i];
(function(i){
li.onclick = function () {
console.log(i);
}
})(i)
}
// 不是学会闭包就一定要使用闭包,因为闭包也会带来一些问题,例上述的案例,每次循环都要创建一个自调用function,并且这个函数内部会创建作用域,因为这个函数发生了闭包,所以这个作用域并没有立即被释放,降低了性能(因为使用闭包,它延展了作用域,没有立即释放作用域)
4.实例:当for循环遇到定时器
console.log('start');
for(var i = 0; i < 3; i++) {
setTimeout(function(){
console.log(i);
},0)
}
console.log('over'); // 控制台输出结果:start over 3个3 因为for循环的时机和setTimeout的执行时机不一样,当执行setTimeout时,循环早已结束
// 解决 function 中 输出的值 为 0 1 2
console.log('start');
for(var i = 0; i < 3; i++) {
(function(i){
setTimeout(function(){
console.log(i);
},0)
})(i)
}
console.log('over');
5.实例:点击相应按钮i,字体变化
// 普通方式:
var btn01 = document.getElementById('btn01');
var btn03 = document.getElementById('btn02');
var btn03 = document.getElementById('btn03');
btn01.onclick = function(){ document.body.style.fontSize = '12px';}
btn02.onclick = function(){ document.body.style.fontSize = '14px';}
btn03.onclick = function(){ document.body.style.fontSize = '16px';}
// 闭包方式:
var btn01 = document.getElementById('btn01');
var btn03 = document.getElementById('btn02');
var btn03 = document.getElementById('btn03');
// 创建一个函数,设置body字体的大小
function makeFun(size){
return function(){
document.body.style.fontSize = size +'px';
} }
btn01.onclick = makeFun(12);
btn02.onclick = makeFun(14);
btn03.onclick = makeFun(16);
// 循环+闭包的方式:
<div id="box">
<button size="12">按钮1</button>
<button size="14">按钮2</button>
<button size="16">按钮3</button>
</div>
// js:创建一个函数,设置body的字体大小
function makeFun(size) {
return function () {
document.body.style.fontSize = size + 'px';
}
}
var box = document.getElementById('box');
var buttons = box.children;
for (var i = 0; i < buttons.length; i++) {
var btn = buttons[i];
// 获取标签的自定义属性
var size = btn.getAttribute('size');
btn.onclick = makeFun(size);
}
四、定时器是如何工作的?
console.log('start');
setTimeout(function(){
console.log('timeout');
},0)
console.log('over'); // 输出结果:start over timeout
// 函数的执行 概念:
// 1.执行栈:先执行 执行栈上的函数,再去执行 任务队列中的函数
// 2.任务队列:setTimeout( ) 是一个函数,其中 function 作为参数 会被放在任务队列
// 此时函数并没有执行,而是在队列中排队
// 注册事件的 处理函数 function 也是放在任务队列上