JS 闭包(内存溢出与内存泄漏)(垃圾回收机制)

1.有关闭包定义

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

说说你对闭包的理解

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

使用闭包的注意点

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

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

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

解决:

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

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

1 function f1(){ 2    var n=999; 3    function f2(){ 4      alert(n); // 999 5    } 6  } 7 8 9function f1(){ 10    var n=999; 11    function f2(){ 12      alert(n); 13    } 14    return f2; 15  } 16  var result=f1(); 17  result(); // 999

3.闭包的用处:

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

1function f1(){ 2    var n=999; 3    nAdd=function(){n+=1} 4    function f2(){ 5      alert(n); 6    } 7    return f2; 8  } 9  var result=f1(); 10  result(); // 999 11  nAdd(); 12  result(); // 1000

4.使用必闭包的问题:

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

闭包的例子:

1function outerFun() 2{ 3 var a=0; 4 function innerFun() 5 { 6 a++; 7 alert(a); 8 } 9 return innerFun; //注意这里 10} 11var obj=outerFun(); 12obj(); //结果为1 13obj(); //结果为2 14var obj2=outerFun(); 15obj2(); //结果为1 16obj2(); //结果为2 17 18 19function outerFun() 20{ 21 //没有var 22 a =0; 23 alert(a); 24} 25var a=4; 26outerFun(); 27alert(a); 28结果为 0,0 真是奇怪,为什么呢? 29 30作用域链是描述一种路径的术语,沿着该路径可以确定变量的值 .当执行a=0,31为没有使用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中访问一个变量的时候,搜索顺序是:

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

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

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

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

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

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

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

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

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

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

有关内存溢出与内存泄漏

11. 内存溢出 2 * 一种程序运行出现的错误 3 * 当程序运行需要的内存超过了剩余的内存时, 就出抛出内存溢出的错误 42. 内存泄露 5 * 占用的内存没有及时释放 6 * 内存泄露积累多了就容易导致内存溢出 7 * 常见的内存泄露: 8 * 意外的全局变量 9 * 没有及时清理的计时器或回调函数 10 * 闭包 11// 1. 内存溢出 12 var obj = {} 13 for (var i = 0; i < 10000; i++) { 14 obj[i] = new Array(10000000) 15 console.log('-----') 16 } 17 18 // 2. 内存泄露 19 // 意外的全局变量 20 function fn() { 21 a = new Array(10000000) 22 console.log(a) 23 } 24 fn() 25 26 // 没有及时清理的计时器或回调函数 27 var intervalId = setInterval(function () { //启动循环定时器后不清理 28 console.log('----') 29 }, 1000) 30 31 // clearInterval(intervalId) 32 33 // 闭包 34 function fn1() { 35 var a = 4 36 function fn2() { 37 console.log(++a) 38 } 39 return fn2 40 } 41 var f = fn1() 42 f() 43 44 // f = null

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

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

点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

MySQL部分从库上面因为大量的临时表tmp_table造成慢查询

背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_

皕杰报表之UUID

​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

你不可不知的JS面试题(第三期)

1、什么是闭包?如图所示,闭包就是一个定义在函数内部的函数,其作用是将函数内部和函数外部连接起来。大家知道,作用域的问题,就是在函数内部定义的变量称为局部变量,外部取不到值。下面我们通过代码来更加详细地看一下:function A()       let x  1;       return function B()           c

JS 闭包(内存溢出与内存泄漏)(垃圾回收机制) - HelloWorld