# 前言

闭包是什么时候被创建的,什么时候被销毁的?具体的实现又是怎么样的?

		var name = "The Window";
        var object = {
            name: "My Object",
            getNameFunc: function () {
                return function () {
                    return this.name;
                };
            }
        };
        alert(object.getNameFunc()());

👆当然了js基础扎实的,一定明白里面的端详,看完一下内容,对于这道题一定就能游刃有余的解决了✊

点这里看
 
     obj.getNameFunc()()实际上是在全局作用域中调用了匿名函数,this指向了window。这里要理解*函数名与函数功能(或者称函数值)是分割开的,不要认为函数在哪里,其内部的this就指向哪里。匿名函数的执行环境具有全局性,因此其 this 对象通常指向 window
  

# 什么是闭包

来看看红宝石怎么说的👇

闭包是指有权访问另外一个函数作用域中的变量的函数

先来看看MDN定义👇

函数和对其周围状态(**lexical environment,词法环境**)的引用捆绑在一起构成**闭包**(**closure**)。也就是说,闭包可以让你从内部函数访问外部函数作用域。在 JavaScript 中,每当函数被创建,就会在函数生成时生成闭包。

👆上面定义大概就是:闭包是指那些能够访问自由变量的函数。其中自由变量,指在函数中使用的,但既不是函数参数arguments也不是函数的局部变量的变量,其实就是另外一个函数作用域中的变量。

网上对于闭包的定义各种各样,每个人对闭包的理解不同,自然定义就不同,那么我也不去定义闭包,自己意会吧👊

非要理解的话,有两种理解:

1️⃣闭包是嵌套的内部函数(绝大数人)

2️⃣包含被引用变量(或函数)的对象(极少数人)

我觉得闭包存在与嵌套的内部函数中😊

我们针对第二个理解,写一个demo👇

function count () {
            var x = 0
            return {
                add() {
                    x++;
                },
                print() {
                    console.log(x)
                }
            }
        }
        let demo = count();
        demo.add()
        demo.print()
        demo.add()
        demo.print()

嗯🙃 只可意会不可言传,往下再看看吧!

# 闭包的生命周期

# 闭包产生原因

首先要明白作用域链的概念,其实也很简单,在ES5中只存在两种作用域

1️⃣全局作用域2️⃣局部作用域

当访问一个变量时,解释器会首先在当前作用域查找标示符,如果没有找到,就去父作用域找,直到找到该变量的标示符或者不在父作用域中,这就是作用域链。 值得注意的是,每一个子函数都会拷贝上级的作用域,形成一个作用域的链条。 比如:

var x = 1;
function demo() {
  var x = 2
  function demo2() {
    var x = 3;
    console.log(x);//3
  }
}

☝️这段代码中,我个人的理解demo函数作用域指向全局作用域(window)和它本身,而demo2函数的作用域指向全局作用域(window)丶demo 和它本身。并且的话,作用域的查找是从最底层开始向上找的,直到找到全局作用域window为止,如果全局作用域还没有找到的话,就会报错❌

闭包产生的本质💯 当前环境中存在指向父级作用域的引用。还是举上面的例子:👇

		var x = 1;
        function demo() {
            var x = 2

            function demo2() {
                console.log(x); 
            }
            return demo2;
        }
        var h = demo();
        h()   // 2

这里h变量会拿到父级作用域的变量,输出2。在当前的环境中,含有对demo2的引用,demo2恰恰引用了window demo 和 本身的作用域。 因此demo2可以访问到demo作用域中的变量。

问题来了? **是不是只有返回函数才会产生闭包呢?**❓

回到闭包实质:只需要让父级作用域的引用存在即可 那么我们可以这么做👇

		var demo2;
        function demo() {
            var x = 2

            demo2 = function () {
                console.log(x); 
            }
            
        }
        demo();
        demo2()    // 2

首先让外部函数执行,给demo2赋值,等于demo2现在拥有了window demo 和 自身的作用域的访问权限,那么查找变量x的时候,会逐级的向上去找,在最近的demo作用域找到标示符就返回结果,输出2。

🤭 在这里外部的变量demo2存在父级作用域的引用,因此产生了闭包,形式变了,本质还是没有改变。

# 闭包的表现形式🤤

明白了本质,我们从真实的场景中出发,究竟在哪些地方能体现闭包的存在❓

1️⃣ 返回一个函数,上面已经举例了

2️⃣作为函数参数传递

var a = 1;
function foo(){
  var a = 2;
  function baz(){
    console.log(a);
  }
  bar(baz);
}
function bar(fn){
  // 这就是闭包
  fn();
}
// 输出2,而不是1
foo();

3️⃣在定时器、事件监听、Ajax请求、跨窗口通信、Web Workers或者任何异步中,只要使用了回调函数,实际上就是在使用闭包

以下的闭包保存的仅仅是window和当前作用域。

// 定时器
setTimeout(function timeHandler(){
  console.log('2222');
}100)

// 事件监听
$('#div').click(function(){
  console.log('DIV Click');
})

4️⃣IIFE(立即执行函数表达式)创建闭包, 保存了全局作用域window当前函数的作用域,因此可以全局的变量。

var x = 22;
(function IIFE(){
  // 输出22
  console.log(x);
})();

# 产生的条件

1️⃣函数嵌套

2️⃣内部函数引用了外部函数的数据(变量/函数)

# 销毁闭包

说到闭包的销毁,得先聊一聊的就是V8的垃圾回收机制

V8垃圾回收机制,我想到时候我会开一个新的章节去聊一聊它🤭

大概说一说垃圾回收机制吧:

在javascript中,如果一个对象不再被引用,那么这个对象就会被垃圾回收机制回收 如果两个对象互相引用,而不再被第3者所引用,那么这两个互相引用的对象也会被回收

☝️ 简单来说,如果一个引用对象不被引用,V8的垃圾回收机制会进行销毁,闭包会携带包含它的函数的作用域,因此会比其他函数占用更多的内存。因此可以手动解除对匿名函数的引用,以便释放内存。

缺点就是生成多个不被销毁的私有作用域(堆内存),除了对性能上有影响,如果当前保存的闭包,或者说它的父级作用域存在html元素,是很危险的🚫

注意

闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。

闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。

当然了,合理的利用闭包也是可以解决很多问题的,我想函数式编程思想一定跟闭包有很多的联系。

比如柯里化的知识

# 总结

  • 个人理解:闭包产生的本质💯 当前环境中存在指向父级作用域的引用
  • 被引用的变量直到闭包被销毁时才会被销毁
  • 闭包会生成私有作用域,消耗内存大,所以不能滥用闭包,对于引用类型,存储位置在堆内存中,会造成内存泄漏问题,尤其是当闭包中父级作用域存在着html元素时,是很危险的🚫
  • 闭包会携带包含它的函数的作用域,因此会比其他函数占用更多的内存。因此可以手动解除对匿名函数的引用,以便释放内存。
  • 闭包应用场景:模块化与柯里化
阅读全文