什么是闭包?
我的理解是,闭包就是函数嵌套函数,内部函数能够读取其他函数内部变量的函数。
由于在Javascript语言中,只有函数内部的函数才能读取局部变量,因此可以把闭包简单理解成"定义在一个函数内部的函数"。
所以,在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。
闭包的用途
闭包可以用在许多地方。它的最大用处有两个,一个是可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中。
闭包的特性
1、函数嵌套函数
2、内部函数可以直接访问外部函数的内部变量或参数
3、变量或参数不会被垃圾回收机制回收
闭包的优点:
1、变量长期驻扎在内存中
2、避免全局变量的污染
3、私有成员的存在
闭包的缺点:
- 不能及时释放内存
- 对捕获的变量是引用,不是复制
- 父函数每调用一次,会产生不同的闭包
什么是内存泄漏
首先,需要了解浏览器自身的内存回收机制。
每个浏览器会有自己的一套回收机制,当分配出去的内存不使用的时候便会回收;内存泄露的根本原因就是你的代码中分配了一些‘顽固的’内存,浏览器无法进行回收,如果这些’顽固的’内存还在一直不停地分配就会导致后面所用内存不足,造成泄露。
造成内存泄露的原因:
1、意外的全局变量(在函数内部没有使用var进行声明的变量)
2、console.log
3、闭包
4、对象的循环引用
5、未清除的计时器
6、DOM泄露(获取到DOM节点之后,将DOM节点删除,但是没有手动释放变量,拿对应的DOM节点在变量中还可以访问到,就会造成泄露)
如何避免闭包引起的内存泄漏:
1、在退出函数之前,将不使用的局部变量全部删除,可以使变量赋值为null
//这段代码会导致内存泄露
window.onload = function(){
var el = document.getElementById("id");
el.onclick = function(){
console.log(el.id);
}
}
//解决方法为
window.onload = function(){
var el = document.getElementById("id");
var id = el.id; //解除循环引用
el.onclick = function(){
console.log(id);
}
el = null; // 将闭包引用的外部函数中活动对象清除
}
2、避免变量的循环赋值和引用
3、jq考虑到了内存泄漏的潜在危害,所以它会手动释放自己指定的所有事件处理程序。只要坚持使用jq的事件绑定方法,就可以一定程度上避免这种特定的原因导致的内存泄漏。
//这段代码会导致内存泄露
$(document).ready(function() {
var app = document.getElementById('#app');
app.onclick = function() {
console.log('hello world');
return false;
};
});
//当指定单击事件处理程序时,就创建了一个在其封闭的环境中包含app变量的闭包。而且,现在的app也包含一个指向闭包(onclick属性自身)的引用。这样,就导致了在IE中即使离开当前页面也不会释放这个循环。
//用jQuery化解引用循环
$(document).ready(function() {
var $app= $('#app');
$app.click(function(event) {
event.preventDefault();
console.log('hello !!!小明');
});
});
//注:$(document).ready()所要执行的代码是在DOM元素被加载完成的情况下执行
//window.onload = function(){ console.log("welcome to beijing"); }语句的作用是希望在页面加载完,自动执行定义js代码(function)
在页面中$(document).ready(function(){.... })这个函数是用来取代页面中的window.onload;
闭包的使用场景
1、setTimeout的使用
<script>
//原生的setTimeout传递的第一个函数不能带参数,通过闭包可以实现传参数
function fun(a){
function fun2(){
console.log(a);
}
return fun2;
}
var fn=fun(1);
setTimeout(fn,1000);//一秒之后打印出1;
</script>
2、回调
3、封装私有变量
<script>
//再返回的对象中,实现了一个闭包,该闭包携带了几步变量sum,并且,从外部代码根本无法访问到变量
function fun(){
var sum=0;
var obj={
say:function(){
sum++;
return sum;
}
}
return obj;
}
let result=fun();
console.log(result.say());//打印结果为1;
console.log(result.say());//打印结果为2;
function fn(){
var sum=0;
function fn2(){
sum++;
return fn2;
}
fn2.valueOf=function(){
return sum;
}
fn2.toString=function(){
return sum+'';
}
return fn2;
}
//执行fn函数,返回的是fn2函数
console.log(+fn());//打印结果0;
console.log(+fn()());//打印结果1;
</script>
4、函数防抖节流
函数防抖:
<script>
//函数防抖是指在函数被高频触发时当停止触发后延时n秒在执行的函数(即每次触发都清理延时函数再开始计时),一般用于resize,scroll,mousemove等
function fun(a){
let timeout=null;//通过闭包创建一个标记用来存放定时器的返回值
return function(){
clearTimeout(timeout);//每当用户输入的时候把前一个setTimeout清空掉
timeout=setTimeout(()=>{//然后又创建一个新的setTimeout,这样就能保证输入字符后interval间隔内如果还有字符输入的话,就不会执行a函数
a.apply(this.arguments);
},500);
}
}
function say(){
console.log('防抖成功')
}
var app=document.getElementById('app');
app.addEventListener('input',debounce(say));//防抖
</script>
函数节流:
<script>
//函数节流:原理 函数被高频触发时延迟n秒后才会再次执行。
//防抖主要是用户触发最后一次事件后,延迟一段时间触发,而节流会规定的
function fun(fn,delay){
let timer=null;
let startTime=Date.now();
return function(){
let curTime=Date.now();
let remaining=delay-(curTime-startTime);
const args=arguments;
clearTimeout(timer);
if(remaining<=0){
fn.apply(this.args);
startTime=Date.now();
}else{
timer=setTimeout(fn,remaining);
}
}
}
function show(){
console.log('111');
}
document.addEventListener('click',throttle(show,2000));
//注:Date.now() 方法返回自 1970 年 1 月 1 日 00:00:00 (UTC) 到当前时间的毫秒数。
</script>