浅谈对闭包的理解和应用

140 阅读3分钟

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)
   
 }
}