1 . 闭包

1 . 1 闭包的定义:

我理解的闭包

  • 一个普通的函数function,如果它可以访问外层作用于的自由变量,那么这个函数就是一个闭包;
  • 从广义的角度来说:JavaScript中的函数都是闭包;
  • 从狭义的角度来说:JavaScript中一个函数,如果访问了外层作用于的变量,那么它是一个闭包;
  • 普通的闭包
// JS中的一个函数如果访问了外层的一个变量就会形成闭包
var sge=12;
function add(){
	console.log(sge);//访问函数外部的变量就形成闭包
}
add()

  • 严格闭包
function f(){
	var name="zhangsan";
	function s(){

 console.log("abb",name);
	}
	return s;
}

var ff=f();
ff();

在这里插入图片描述

1 . 2 闭包的组成

在这里插入图片描述

1.3 闭包的访问过程

  • 如果我们编写了如下的代码,它一定是形成了闭包的:
    在这里插入图片描述
    在这里插入图片描述

1 .4闭包的执行过程

  • 上述代码中makeAdder函数执行完毕,正常情况下我们的AO对象会被释放;
  • 但是因为在0xb00的函数中有作用域引用指向了这个AO对象,所以它不会被释放掉;
    在这里插入图片描述
    代码
function foo() {
  var name = "foo"
  var age = 18

  function bar() {
    console.log(name)
    console.log(age)
  }
  return bar
}

var fn = foo()
fn()

在这里插入图片描述

1 . 5闭包的作用

  • 延伸了函数的作用范围,读取函数内部的变量
  • 让变量的值始终保持在内存中。不会在调用后被自动清除。
  • 方便调用上下文的局部变量。利于代码封装。

1 . 6 闭包的主要应用场景

  • 为节点循环绑定事件:
<ul class="nav">
    <li>打印index1</li>
    <li>打印index2</li>
    <li>打印index3</li>
    <li>打印index4</li>
</ul>
<script>
// 1. 我们可以利用动态添加属性的方式
        var lis = document.querySelector('.nav').querySelectorAll('li');
        for (var i = 0; i < lis.length; i++) {
            lis[i].index = i;
            lis[i].onclick = function() {
                // console.log(i);
                console.log(this.index);

            }
        }
        // 2. 利用闭包的方式得到当前小li 的索引号
        for (var i = 0; i < lis.length; i++) {
            // 利用for循环创建了4个立即执行函数
            // 立即执行函数也成为小闭包因为立即执行函数里面的任何一个函数都可以使用它的i这变量
            (function(i) {
                // console.log(i);
                lis[i].onclick = function() {
                    console.log(i);

                }
            })(i);  //注意这里必须传递i
        }
    </script>
  • setTimeout传参
```javascript
<script>
    
    var lis = document.querySelector('.nav').querySelectorAll('li');
        // for (var i = 0; i < lis.length; i++) {
        //     setTimeout(function() {
        //         console.log(lis[i].innerHTML);    //报错无法读取未定义的属性
        //     }, 3000)
        // }

        for (var i = 0; i < lis.length; i++) {
            (function(i) {
                setTimeout(function() {
                    console.log(lis[i].innerHTML);
                }, 3000)
            })(i)      //必须要跟实参
        }
</script>

1 . 7.闭包的优缺点

优点:

  • 避免全局变量的污染
  • 能够读取函数内部的变量
  • 可以在内存中维护一个变量

缺点:

  • 闭包会常驻内存,会增大内存使用量,导致一定程度上的内存泄露。
  • 在游览器中因为回收机制无法回收闭包的函数以及闭包函数中储存的数据,会使游览器占用更多的性能开销

2 . 闭包的内存泄漏

2 . 1内存的泄漏

  • 当一个对象有一个引用指向它时,那么这个对象的引用就+1,当一个对象的引用为0时,这个对象就可以被销毁掉;
  • 这个算法有一个很大的弊端就是会产生循环引用;这样就会造成内存泄漏
    在这里插入图片描述

2 .2闭包的内存泄漏

上面中我们为什么经常会说闭包是有内存泄露的呢?

  • 在上面的案例中,如果后续我们不再使用add10函数了,那么该函数对象应该要被销毁掉,并且其引用着的父作用域AO也应该被销毁掉;
  • 但是目前因为在全局作用域下add10变量对0xb00的函数对象有引用,而0xb00的作用域中AO(0x200)有引用,所以最终会造成这些内存都是无法被释放的;
  • 所以我们经常说的闭包会造成内存泄露,其实就是刚才的引用链中的所有对象都是无法释放的;

那么,怎么解决这个问题呢?

  • 因为当将add10设置为null时,就不再对函数对象0xb00有引用,那么对应的AO对象0x200也就不可达了;

  • 在GC的下一次检测中,它们就会被销毁掉;
    在这里插入图片描述

function f(){
	var name="zhangsan";

	var age=18; 
	function s(){   //指针?
		console.log(name,age);
	}   //这里的s函数对象是指向f() 在函数执行完毕后A0不会被销毁   保存了下来
	return s;
}
var obj=f();
obj();
obj=null   // 指向空销毁内存空间

闭包的内存泄漏主要是指 :

  • 在形成闭包时 : 包含了一个父级作用域 f(); 有内存指向他 还存在AO 因此不会被销毁;
  • 在程序不断执行时 因为需要不断调用这个函数就会造成内存泄漏
  • 内存泄漏主要是看你需不需不断使用
  • 不使用了还存在就是内存泄漏

2 . 3闭包的内存泄漏测试

在这里插入图片描述

在这里插入图片描述
测试代码

function createFnArray() {
  // var arr = [1, 1, 1, 1, 1, 1, 1, 1,1, 1,1, 1,1 ]
  // 占据的空间是4M x 100 + 其他的内存 = 400M+
  // 1 -> number -> 8byte -> 8M
  // js: 10 3.14 -> number -> 8byte ? js引擎
  // 8byte => 2的64次方 => 4byte
  // 小的数字类型, 在v8中成为Sim, 小数字 2的32次方
  var arr = new Array(1024 * 1024).fill(1)
  return function() {
    console.log(arr.length)
  }
}

// var arrayFn = createFnArray()
// arrayFn = null

// 100 * 100 = 10000 = 10s
var arrayFns = []
for (var i = 0; i < 100; i++) {
  setTimeout(() => {
    arrayFns.push(createFnArray())
  }, i * 100);
}

// arrayFns = null
setTimeout(() => {
  for (var i = 0; i < 50; i++) {
    setTimeout(() => {
      arrayFns.pop()
    }, 100 * i);
  }
}, 10000);

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐