闭包知识点笔记,包含十大使用出场景

212 阅读5分钟

闭包知识点笔记,包含十大使用出场景

前言

最近在学习javascript的一个重要知识点,闭包,下文从几个点解释了个人对闭包的理解,使用闭包的注意点,以及闭包的十大使用场景。整理成笔记,方便自己以后复习。同时也欢迎大家指正不足~

  1. 变量作用域:由于变量作用域得关系,函数外部不能访问在函数中定义的变量;但如果在全局定义函数,会被任意修改;此时就考虑到了使用闭包,来访问函数内部的变量,

  2. 在一个函数fn1中定义一个另一个函数fn2,并返回该函数,此时fn2就是闭包;此时就能访问函数fn1中的变量

    function fn1(){
        var b = "我是变量b";
        function fn2(){
            console.log(b);
        }
        return fn2;
    }
    var result = fn1();
    result();
    
  3. 闭包最大的特点是它可以记住诞生的环境,fn2记住了它的诞生环境是fn1,所以在函数fn2中可以访问fn1中声明的变量;本质上,闭包就是函数内部和函数外部链接的桥梁

  4. 闭包的用途

    • 用作计数器

      // 1.制作计数器
      function count() {
          var start = 0;
          function fn2() {
              return start++;
          }
          return fn2;
      }
      var inc = count();
      console.log(inc());//返回0
      console.log(inc());//返回1;而不是返回0
      //释放变量
      inc = null;
      

      在闭包中使用的变量会一直存在在内存中,不会自动释放,所以在重新调用count函数的时候,会直接在内存中找到start变量,在原来的值上直接加1,所以会第二次调用会返回1,而不是0;因为函数不会被自动释放,为了避免内存泄漏,需要手动释放函数的引用变量,置为null

    • 封装对象的私有属性和私有方法

      //2.封装对象的私有属性和私有方法
      function Person(name) {
          var age;//私有属性
          //私有方法
          function setAge(n) {
              age = n;
          }
          //私有方法
          function getAge() {
              return age;
          }
          //返回一个对象
          return{
              name:name,
              setAge:setAge,
              getAge:getAge
          }
      }
      var p = Person("张三");
      p.setAge(21);
      console.log(p.name,p.getAge())//返回张三 21
      
  5. 闭包的注意点

    • 使用闭包使得函数中的变量始终存在在内存中,内存消耗很大,所以不能滥用闭包,否则造成页面的性能问题
    • 闭包形成的三个条件:函数嵌套、访问所在的作用域、在所在 作用域外被调用
  6. 立即执行函数

    两种声明语句

    • (function(){})();
    • (function(){}());
    • 如果在自执行函数声明语句后没有加分号表示结束,可以在声明语句前加+ - !,作用和加分号是一样的,同样表示语句结束
  7. 立即执行函数的作用

    // 立即执行函数也可以写闭包
    	var inc =(function count(){
    		var start = 0;
    		return function fn2() {
    			return start++;
    		}
    	})();
    	console.log(inc());//0
    	console.log(inc());//1
    	console.log(inc());//2
    	console.log(inc());//3
    
  8. 想要循环打印出0,1,2,一个错误的例子

    function foo() {
    		var arr=[];
    		for(var i=0;i<10;i++){
    			arr[i] = function(){
    				return i;
    			}
    		}
    		return arr;
    	}
    	var bar = foo();
    	console.log(bar[1]())//按道理应该是打印出1,但是实际上打印出10
    

    问题在于,在执行数组赋值前,i已经循环完,最后的值是10,然后再赋值给数组,不是一个i存进数组里

  9. 解决方法

    • 使用自执行函数形成闭包

      function foo() {
         var arr=[];
         for(var i=0;i<10;i++){
         	arr[i] = (function(n){
         		return function(){
         			return n;
         		};
         	})(i)
         }
         return arr;
      

    } var bar = foo(); console.log(bar1)//打印出1 console.log(bar2)//打印出2 console.log(bar9)//打印出9

    
    
    
    - ```js
    function foo() {
       var arr=[];
       for(var i=0;i<10;i++){
       	(function(n){
       		arr[n] =  function(){
       			return n;
       		};
       	})(i)
       }
       return arr;
    }
    var bar = foo();
    console.log(bar[1]())//打印出1
    console.log(bar[2]())//打印出2
    console.log(bar[9]())//打印出9
    
    • 还有一种方法是使用let定义i,不过跟闭包没关系,就不讲了...
  10. 闭包的十种使用场景

    • 返回值(最常见的一种形式)

      function fn1(){
          var b = "我是变量b";
          function fn2(){
              return b;
          }
          return fn2;
      }
      var result = fn1();
      console.log(result());
      
    • 函数赋值

      var fun2;
      function fn1(){
          var b = "我是变量b";
          var a= function (){
              return b;
          }
          fn2 = a;
      }
       fn1();
      console.log(fn2());
      
    • 函数参数

      function fn2(f){
          console.log(f());
      }
      function fn(){
          var b = "我是变量b";
          var a = function(){
              return b;
          }
          fn2(a);
      }
      fn();//打印出我是变量b
      
    • IIFE(立即执行函数)

      function fn2(f){
          console.log(f());
      }
      (function(){
          var b = "我是变量b";
          var a = function(){
              return b;
          }
          fn2(a);
      })()
      //相当于执行fn()打印出我是变量b
      
    • 循环赋值

      function foo() {
      	var arr=[];
      	for(var i=0;i<10;i++){
      		arr[i] = (function(n){
      			return function(){
      				return n;
      			};
      		})(i)
      	}
      	return arr;
      }
      	var bar = foo();
      	console.log(bar[1]())//打印出1
      	console.log(bar[2]())//打印出2
      	console.log(bar[9]())//打印出9
      
    • getter和setter,将函数内部的变量保存在内部,防止被任意修改

      var getValue,setValue;
      (function(){
      	var num = 0;
      	getValue = function (){
      		return num;
      	}
      	setValue = function (n){
      		num = n;
      	}
      })();
      console.log(getValue())//打印出0;
      setValue(21);
      console.log(getValue())//打印出21;
      
    • 迭代器

      function printName(arr){
      	var i = 0;
      	return function(){
      		return arr[i++];
      	}
      }
      var name = printName(['张三','李四','王五']);
      console.log(name());//打印出张三
      console.log(name());//打印出李四
      
    • 区分首次

      var isFirst = (function(){
      	var list = [];
      	return function(num){
      		if(list.indexof(num) > -1){
      			return false;
      		}else{
      			list.push(num);
      			return false;
      		}
      	}
      })();
      console.log(isFirst(21));//打印出true
      console.log(isFirst(21));//打印出false
      
    • 缓存机制

      var mult = function(){
          //缓存对象
          var cache = {};
          var calculate = function(){
              var sum = 0;
              for(var i = 0;i < arr.length; i++){
                  sum = sum + i;
              }
              return sum;
          }
          return function(){
              //对cache对象进行操作
              var arrs = Array.prototype.join.call(arr,",");
          }
          if(arrs in cache){
              return cache[arrs]
          }
          console.log(cache)
          return cache[arrs] = calculate.apply(null,arr);
      }
      console.log(mult(1,2,3,4));//打印出10
      
    • 图片上传

      var report = function(src){
          var imgs = [];
          return function (src){
              var img = new Image();
              imgs.push(img);
              img.src = src;
          }
      }();
      report(图片的路径);
      
  11. 总结

    • 注意闭包的形成条件
    • 闭包会使得父级函数中的变量一直保存在内存中
    • 注意闭包造成的内存泄露
    • 十大使用场景
    • 学习学习,复习复习!!!