zoukankan      html  css  js  c++  java
  • JS 闭包(内存溢出与内存泄漏)(垃圾回收机制)

    1.有关闭包定义

    闭包是指有权访问另一个函数作用域中变量的函数,创建闭包的最常见的
    方式就是在一个函数内创建另一个函数,通过另一个函数访问这个函数的局部变量
    
    闭包的特性:
        函数内再嵌套函数
        内部函数可以引用外层的参数和变量
        参数和变量不会被垃圾回收机制回收
    

    说说你对闭包的理解

    使用闭包主要是为了设计私有的方法和变量。闭包的优点是可以避免全局变量的污染,
    缺点是闭包会常驻内存,会增大内存使用量,使用不当很容易造成内存泄露。在js中,
    函数即闭包,只有函数才会产生作用域的概念
    
    闭包 的最大用处有两个,一个是可以读取函数内部的变量,另一个就是让这些
    变量始终保持在内存中
    
    闭包的另一个用处,是封装对象的私有属性和私有方法
    
    好处:能够实现封装和缓存等;
    
    坏处:就是消耗内存、不正当使用会造成内存溢出的问题
    

    使用闭包的注意点

    由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用
    闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露
    
    解决方法是,在退出函数之前,将不使用的局部变量全部删除
    
    闭包的定义其实很简单:函数 A 内部有一个函数 B,函数 B 可以访问到函数 A 中的变量,那么函数 B 就是闭包
    
    function A() {
      let a = 1
      window.B = function () {
          console.log(a)
      }
    }
    A()
    B() // 1
    

    闭包会产生一个很经典的问题:

    多个子函数的[[scope]]都是同时指向父级,是完全共享的。因此当父级的
    变量对象被修改时,所有子函数都受到影响。
    

    解决:

    变量可以通过 函数参数的形式 传入,避免使用默认的[[scope]]向上查找
    使用setTimeout包裹,通过第三个参数传入
    使用 块级作用域,让变量成为自己上下文的属性,避免共享
    

    2.闭包简单例子
    指的是有权访问另一个函数作用域中变量的函数,
    创建闭包的常见方式,就是在一个函数内部创建另一个函数。

     function f1(){
        var n=999;
        function f2(){
          alert(n); // 999
        }
      }
    
    function f1(){
        var n=999;
        function f2(){
          alert(n); 
        }
        return f2;
      }
      var result=f1();
      result(); // 999
    

    3.闭包的用处:

    闭包可以用在许多地方。它的最大用处有两个,一个是前面提到的可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中。

    function f1(){
        var n=999;
        nAdd=function(){n+=1}
        function f2(){
          alert(n);
        }
        return f2;
      }
      var result=f1();
      result(); // 999
      nAdd();
      result(); // 1000
    

    4.使用必闭包的问题:

    由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题。

    闭包的例子:

    function outerFun()
    {
     var a=0;
     function innerFun()
     {
      a++;
      alert(a);
     }
     return innerFun;  //注意这里
    }
    var obj=outerFun();
    obj();  //结果为1
    obj();  //结果为2
    var obj2=outerFun();
    obj2();  //结果为1
    obj2();  //结果为2
    
    function outerFun()
    {
     //没有var
     a =0;
     alert(a);  
    }
    var a=4;
    outerFun();
    alert(a);
    结果为 0,0 真是奇怪,为什么呢?
    
    作用域链是描述一种路径的术语,沿着该路径可以确定变量的值 .当执行a=0时,因
    为没有使用var关键字,因此赋值操作会沿着作用域链到var a=4;  并改变其值. 
    

    5.闭包内的微观世界
    参考学习:https://www.cnblogs.com/goloving/p/7062212.html
      如果要更加深入的了解闭包以及函数a和嵌套函数b的关系,我们需要引入另外几个概念:函数的执行环境(excution context)、活动对象(call object)、作用域(scope)、作用域链(scope chain)。以函数a从定义到执行的过程为例阐述这几个概念。

    1.当定义函数a的时候,js解释器会将函数a的作用域链(scope chain)设置为定义a时a所在的“环境”,如果a是一个全局函数,则scope chain中只有window对象。
    当执行函数a的时候,a会进入相应的执行环境(excution context)。
    2.在创建执行环境的过程中,首先会为a添加一个scope属性,即a的作用域,其值就为第1步中的scope chain。即a.scope=a的作用域链。
    3.然后执行环境会创建一个活动对象(call object)。活动对象也是一个拥有属性的对象,但它不具有原型而且不能通过Javascript代码直接访问。创建完活动对象后,把活动对象添加到a的作用域链的最顶端。此时a的作用域链包含了两个对象:a的活动对象和window对象。
    4.下一步是在活动对象上添加一个arguments属性,它保存着调用函数a时所传递的参数。
    5.最后把所有函数a的形参和内部的函数b的引用也添加到a的活动对象上。在这一步中,完成了函数b的的定义,因此如同第3步,函数b的作用域链被设置为b所被定义的环境,即a的作用域。

    当在函数b中访问一个变量的时候,搜索顺序是:

    先搜索自身的活动对象,如果存在则返回,如果不存在将继续搜索函数a的活动对象,
    依次查找,直到找到为止。
    如果函数b存在prototype原型对象,则在查找完自身的活动对象后先查找自身的原型
    对象,再继续查找。这就是Javascript中的变量查找机制。
    如果整个作用域链上都无法找到,则返回undefined。
    

    函数的定义与执行。文中提到函数的作用域是在定义函数时候就已经确定,而不是在执行的时候确定

    6.有关闭包经典案例
    经典面试题,循环中使用闭包解决 var 定义函数的问题

    for ( var i=1; i<=5; i++) {
    	setTimeout( function timer() {
    		console.log( i );
    	}, i*1000 );
    }
    

    首先因为 setTimeout 是个异步函数,所有会先把循环全部执行完毕,这时候 i 就是 6 了,所以会输出一堆 6。
    解决办法两种,第一种使用闭包

    for (var i = 1; i <= 5; i++) {
      (function(j) {
        setTimeout(function timer() {
          console.log(j);
        }, j * 1000);
      })(i);
    }
    

    第二种就是使用 setTimeout 的第三个参数

    for ( var i=1; i<=5; i++) {
    	setTimeout( function timer(j) {
    		console.log( j );
    	}, i*1000, i);
    }
    

    第三种就是使用 let 定义 i 了

    for ( let i=1; i<=5; i++) {
    	setTimeout( function timer() {
    		console.log( i );
    	}, i*1000 );
    }
    

    有关内存溢出与内存泄漏

    
    
    1. 内存溢出
      * 一种程序运行出现的错误
      * 当程序运行需要的内存超过了剩余的内存时, 就出抛出内存溢出的错误
    2. 内存泄露
      * 占用的内存没有及时释放
      * 内存泄露积累多了就容易导致内存溢出
      * 常见的内存泄露:
        * 意外的全局变量
        * 没有及时清理的计时器或回调函数
        * 闭包
    // 1. 内存溢出
      var obj = {}
      for (var i = 0; i < 10000; i++) {
        obj[i] = new Array(10000000)
        console.log('-----')
      }
    
      // 2. 内存泄露
        // 意外的全局变量
      function fn() {
        a = new Array(10000000)
        console.log(a)
      }
      fn()
    
       // 没有及时清理的计时器或回调函数
      var intervalId = setInterval(function () { //启动循环定时器后不清理
        console.log('----')
      }, 1000)
    
      // clearInterval(intervalId)
    
        // 闭包
      function fn1() {
        var a = 4
        function fn2() {
          console.log(++a)
        }
        return fn2
      }
      var f = fn1()
      f()
    
      // f = null
    

    7.js垃圾回收机制
    转载:https://www.cnblogs.com/zhwl/p/4664604.html
    由于字符串、对象和数组没有固定大小,当他们的大小已知时,才能对他们进行动态的存储分配。JavaScript程序每次创建字符串、数组或对象时,解释器都必须分配内存来存储那个实体。只要像这样动态地分配了内存,最终都要释放这些内存以便他们能够被再用,否则,JavaScript的解释器将会消耗完系统中所有可用的内存,造成系统崩溃。

    现在各大浏览器通常用采用的垃圾回收有两种方法:标记清除、引用计数
    标记清除
    这是javascript中最常用的垃圾回收方式。当变量进入执行环境是,就标记这个变量为“进入环境”。从逻辑上讲,永远不能释放进入环境的变量所占用的内存,因为只要执行流进入相应的环境,就可能会用到他们。当变量离开环境时,则将其标记为“离开环境”。
    引用计数
     另一种不太常见的垃圾回收策略是引用计数。引用计数的含义是跟踪记录每个值被引用的次数。当声明了一个变量并将一个引用类型赋值给该变量时,则这个值的引用次数就是1。相反,如果包含对这个值引用的变量又取得了另外一个值,则这个值的引用次数就减1。当这个引用次数变成0时,则说明没有办法再访问这个值了,因而就可以将其所占的内存空间给收回来。这样,垃圾收集器下次再运行时,它就会释放那些引用次数为0的值所占的内存。

  • 相关阅读:
    mysql常用基本命令
    mysql8.0.13下载与安装图文教程
    k8s ingress 增加跨域配置
    Jenkins 备份恢复插件 thinBackup 使用
    k8s HA master 节点宕机修复
    nginx 跨域问题解决
    mongodb 3.4.24 主从复制
    k8s 线上安装 jenkins并结合 jenkinsfile 实现 helm 自动化部署
    k8s helm 运用与自建helm仓库chartmuseum
    centos6 源码安装 unzip
  • 原文地址:https://www.cnblogs.com/princeness/p/11664978.html
Copyright © 2011-2022 走看看