0
点赞
收藏
分享

微信扫一扫

关于闭包理解

奋斗De奶爸 2022-01-13 阅读 100

对闭包的理解:

闭包是由函数以及创建该函数的词法环境组成,形式上看就是函数内的子函数。

因为js有自动的垃圾回收机制,当变量不被引用时就会通过特殊的算法回收(一般是标记清除引用计数两种方法),当一个函数执行完后作用域会被销毁,而闭包可以访问父级作用域,父级函数中的变量在闭包中被引用,那么就不会被垃圾回收;所以闭包可以实现在函数外部访问内部的局部变量,并且可以使让这些变量的值始终保持在内存中。

从内存来看闭包:

当JS引擎判断形成闭包,就会在堆内存中开辟一块空间,里面有一个闭包对象closure,这个对象里包含闭包用到的外部变量,之后要用这些变量的时候,就会去到堆里面的closure对象里面找。

理解练习:

例一

var x = 10;

function fn(){
console.log(x);
}

function show(f){
var x = 20;       //因为闭包是由函数及创建该函数的词法环境组成,而fn创建的词法环境中x=10
(function(){
f();
})()                       
}

show(fn); //10

例二:

function a() {
    var i = 0;
    function b() {
        alert(++i);
    }
    return b;
}
var c = a();
c(); //1   i=0,第一次调用c 执行++i,输出1
c(); //2   因为是闭包,i变量在b()中引用不会在c()执行完后释放内存,所以再次c(),++i就是1+1=2

例三:

var add = function(x) {
    var sum = 1;
    var tmp = function(x) {
        sum = sum + x;
        return tmp;
    }
    tmp.toString = function() {
        return sum;
    }
    return tmp;
    }
alert(add(1)(2)(3))    // 6
第一次调用传入了参数1,没有使用,此时sum的值是1;然后返回tmp函数
第二次调用tmp函数传入 2,sum的值是1+2=3;
第三次重复调用tmp函数传入3,sum的值是3+3=6;隐式自动调用tmp的toString()弹出6

例四:

var i = 0;
function outerFn(){
 function innerFn(){
  i++;
  console.log(i);
  }
 return innerFn
}
var inner1 = outerFn();
var inner2 = outerFn();
inner1();//1
inner2();//1
inner1();//2
inner1();//3
inner2();//2
inner2();//3

因为闭包找到的是同一地址中父级函数中对应变量最终的值。

例五:

function fun(){
   //1. 找受保护的变量: 外层函数的局部变量
   var n=999;
   //2. 外层函数共抛出哪些内层函数
   nAdd=function(){n++};
   return function(){ console.log(n) };
  }
  var get=fun();//fun的AO(n=999)
  //var nAdd: function(){n++}
  //get: function(){ console.log(n) };
 
  get();//999
  nAdd();//AO(n=1000)
  get();//1000
因为nAdd没有被定义过,强行赋值会使JS将nAdd定义为全局变量,所以他也会被返回到外部,故也将执行

例六

for(var i = 0;i < 5;i++){
    setTimeout(function (){
        console.log(i++);
    },4000)
}
console.log(i);

for循环并不是一个函数,所以没有函数作用域,var声明的变量也不存在块级作用域,所以i就是个全局变量。

顺序执行,先执行for,setTimeout异步宏任务放任务队列等待执行

当for循环完i=5,执行console.log(i)输出5

然后执行任务队列的五个setTimeout宏任务,因为是闭包i的值保存不被释放,所以接下来输出5,6,7,8,9

正常实现从0增加输出

1.立即执行函数,每一次都会创建函数执行环境得到对应的结果

for(var i = 0;i < 5;i++){
            (function (x) {
                setTimeout(function (){
                    console.log(x++);
                },4000)
            })(i);
        }   

2.setTimeout 的第三个参数
for ( var i=1; i<=5; i++) {
    setTimeout( function timer(j) {
        console.log( j );
    }, i*1000, i);
}
3.使用 let 定义 i 
for ( let i=1; i<=5; i++) {
    setTimeout( function timer() {
        console.log( i );
    }, i*1000 );
}

内存溢出和内存泄漏

内存溢出:当程序运行需要的内存超过了剩余的内存时, 就出抛出内存溢出的错误


内存泄露:占用的内存没有及时释放

常见的内存泄漏:

1.没有及时清理的计时器或回调函数

原因:定时器中有dom的引用,即使dom删除了,但是定时器还在,所以内存中还是有这个dom。

解决:手动删除定时器和dom。

2.意外的全局变量。

原因:全局变量,不会被回收。

解决:使用严格模式避免。

function fn () {
  b = new Array[1000000]
  a = [] //不小心没有var定义,这时候a变量是全局的
}
fn()
function foo() {
    this.variable = "potential accidental global";
}
// foo 调用自己,this 指向了全局对象(window)
foo();

3.闭包,函数执行完后, 函数内的局部变量没有释放, 占用内存时间会变长,容易造成内存泄露。

原因:闭包可以维持函数内局部变量,使其得不到释放。

解决:将事件处理函数定义在外部,解除闭包,或者在定义事件处理函数的外部函数中,删除对dom的引用。

4.dom清空或删除时,事件未清除导致的内存泄漏。

原因:虽然别的地方删除了,但是对象中还存在对dom的引用

解决:手动删除。

5.子元素存在引用引起的内存泄漏

原因:div中的ul li  得到这个div,会间接引用某个得到的li,那么此时因为div间接引用li,即使li被清空,也还是在内存中,并且只要li不被删除,他的父元素都不会被删除。

解决:手动删除清空。

 

怎样避免内存泄露:

1)减少不必要的全局变量,或者生命周期较长的对象,及时对无用的数据进行垃圾回收;

2)注意程序逻辑,避免“死循环”之类的 ;

3)避免创建过多的对象 原则:不用了的东西要及时归还。

关于垃圾回收机制:

垃圾收集器会定期(周期性)找出那些不在继续使用的变量,然后释放其内存。

通常情况下有两种实现方式:标记清除,引用计数。

标记清除:

工作原理:是当变量进入环境时,将这个变量标记为“进入环境”。当变量离开环境时,则将其标记为“离开环境”。标记“离开环境”的就回收内存。

工作流程:

1.    垃圾回收器,在运行的时候会给存储在内存中的所有变量都加上标记。

2.    去掉环境中的变量以及被环境中的变量引用的变量的标记。

3.    再被加上标记的会被视为准备删除的变量。

4.    垃圾回收器完成内存清除工作,销毁那些带标记的值并回收他们所占用的内存空间。

引用计数:

工作原理:跟踪记录每个值被引用的次数。

工作流程:

1.    声明了一个变量并将一个引用类型的值赋值给这个变量,这个引用类型值的引用次数就是1。

2.    同一个值又被赋值给另一个变量,这个引用类型值的引用次数加1.

3.    当包含这个引用类型值的变量又被赋值成另一个值了,那么这个引用类型值的引用次数减1.

4.    当引用次数变成0时,说明没办法访问这个值了。

5.    当垃圾收集器下一次运行时,它就会释放引用次数是0的值所占的内存。

举报

相关推荐

0 条评论