js高程上对闭包的定义是,引用了另一个函数作用域中变量的函数,通常发生在函数嵌套中
function outer(){
let name = 'outer'
return function innnter(){
console.log(name)
}
}
let innter = outer()
innter()//name
内部函数中并没有name变量的定义,但是却能访问外部函数中的name变量,这时候内部函数inner就被叫做闭包
闭包有什么特点
- 函数嵌套函数
- 内层函数能够访问外层函数的变量
- 参数的变量不会被垃圾回收机制回收
对于前两条特点,根据闭包的定义就能理解,对于第三条特点,举个例子看看:
function count(){
let count=0
return function(){
count++
return count
}
}
let getCount = count()
getCount()//1
getCount()//2
getCount()//3
函数作用域中的变量应该在调用之后就销毁了,但是在闭包中我们看到,通过外部变量接收内部返回的函数,每次调用,变量的值都保存着,没有被重置,利用这一点有很多的应用场景,后面谈
闭包的原理
- 闭包的形成:内部函数引用外部函数中的变量,并且被返回至外部保存时,一定会产生闭包
- 内层函数访问外层函数中的变量是因为产生了作用域链
- 常驻内存的原因: 我们从JS预编译的角度来讲,可以知道当外部函数执行的前一刻,内部函数被定义,此时他的作用域链和外部函数的作用域链完全相同,都会指向该外部函数的AO(函数上下文)。然后等到外部函数执行结束,原本应被释放的外部函数的AO却因为内部函数被返回至外部且有一个变量进行接收保存,因此该外部函数的AO没有被销毁,还被内部函数的作用域链连接着。所以当我们进行访问内部函数的变量时,若该内部函数查询不到该变量,则它会向外部函数中的AO去寻找,若无则沿着其自身作用域链依次往上级查找。
闭包的缺点
闭包长期占用内存,内存消耗很大,可能导致内存泄露 解决方案: 退出函数之前,将不使用的局部变量进行删除
这段代码会导致内存泄露
window.onload = function(){
var el = document.getElementById("id");
el.onclick = function(){
alert(el.id);
}
}
解决方法为
window.onload = function(){
var el = document.getElementById("id");
var id = el.id; //解除循环引用
el.onclick = function(){
alert(id);
}
el = null; // 将闭包引用的外部函数中活动对象清除
}
闭包的作用
1、保护函数内部的变量安全,实现封装
比如说我们有个应用场景,是对某个属性的值进行增删减查,例如拿一个简单的学生管理系统来说, 我们可以对外部函数的变量students进行私有化,在外部访问内部函数的方法改变私有变量students的值
function myClass() {
var students = []
var operations = {
joinStu: function(name) {
students.push(name)
},
leaveStu: function(name) {
for(var i=0; i<students.length; i++) {
var item = students[i]
if(item === name) {
students.splice(i, 1)
}
}
},
findStu: function() {
console.log(students)
}
}
return operations
}
var class1 = myClass()
class1.findStu() // []
class1.joinStu('Trist')
class1.findStu() // ["Trist"]
class1.joinStu('张三')
class1.findStu() // ["Trist", "张三"]
class1.leaveStu("张三")
class1.findStu() // ["Trist"]
2、可以把这个变量当作缓存来使用
//比如求和操作,如果没有缓存,每次调用都要重复计算,采用缓存已经执行过的去查找,查找到了就直接返回,不需要重新计算
var fn=(function(){
var cache={};//缓存对象
var calc=function(arr){//计算函数
var sum=0;
//求和
for(var i=0;i<arr.length;i++){
sum+=arr[i];
}
return sum;
}
return function(){
var args = Array.prototype.slice.call(arguments,0);//arguments转换成数组
var key=args.join(",");//将args用逗号连接成字符串
var result , tSum = cache[key];
if(tSum){//如果缓存有
console.log('从缓存中取:',cache)//打印方便查看
result = tSum;
}else{
//重新计算,并存入缓存同时赋值给result
result = cache[key]=calc(args);
console.log('存入缓存:',cache)//打印方便查看
}
return result;
}
})();
fn(1,2,3,4,5); // 存入缓存:{1,2,3,4,5:15}
fn(1,2,3,4,5);// 从缓存中取:{1,2,3,4,5:15}
fn(1,2,3,4,5,6);// 存入缓存:{1,2,3,4,5,6:21}
fn(1,2,3,4,5,8);//存入缓存:{1,2,3,4,5,8:23}
fn(1,2,3,4,5,6);//从缓存中取:{1,2,3,4,5,6:21}
3、循环赋值
//每秒执行1次,分别输出1-10
for(var i=1;i<=10;i++){
(function(j){
//j来接收
setTimeout(function(){
console.log(j);
},j*1000);
})(i)//i作为实参传入
}
闭包其他应用
- 函数节流 在某段时间内事件只触发一次, 比如按钮重复点击,滚动事件触发等等。。。
function throttle(fn,wait){
let pre = 0;
return function(){
let now = Date.now()
if(now - pre>=wait){
fn.call(this)
pre = now
}
}
}
- 函数防抖 短时间大量触发事件,只执行最后一次,比如搜索,键盘事件停止才会触发
function debounce(fn,wait){
let timer = null;
return function(){
if(timer){
clearTimeOut(timer)
}
let _this = this
timer = setTimeOUt(function(){
fn.call(_this)
},wait)
}
}