聊一聊闭包

前言

闭包是何时被建立的,何时被销毁的?具体的实现又是怎么样的?javascript

一开始学闭包的时候,囫囵吞枣😔,但愿此次能够静下来好好琢磨琢磨,对闭包有更深的理解。html

🤭为了更好的理解闭包,有必要先了解做用域跟执行上下文相关的内容:java

JavaScript执行上下文-执行栈bash

你不知道的Javascript动态做用域闭包

话很少说,上来就是一个思考题🤔异步

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

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

什么是闭包

来看看红宝石怎么说的👇post

闭包是指有权访问另一个函数做用域中的变量的函数
复制代码

先来看看MDN定义👇ui

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

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

网上对于闭包的定义各类各样,每一个人对闭包的理解不一样,天然定义就不一样,那么我也不去定义闭包,本身意会吧👊
复制代码

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

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做用域中的变量。

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

回到闭包实质:只须要让父级做用域的引用存在便可 那么咱们能够这么作:point_down:

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

            demo2 = function () {
                console.log(x); 
            }
            
        }
        demo();
        demo2()    // 2
复制代码

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

🤭 在这里外部的变量demo2存在父级做用域的引用,所以产生了闭包,形式变了,本质仍是没有改变。

闭包的表现形式🤤

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

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

:two:做为函数参数传递

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

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

如下的闭包保存的仅仅是window和当前做用域。

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

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

:four:IIFE(当即执行函数表达式)建立闭包, 保存了全局做用域window当前函数的做用域,所以能够全局的变量。

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

产生的条件

1️⃣函数嵌套

:two:内部函数引用了外部函数的数据(变量/函数)

销毁闭包

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

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

闭包优缺点

总结

  • 我的理解:闭包产生的本质💯 当前环境中存在指向父级做用域的引用
  • 被引用的变量直到闭包被销毁时才会被销毁

参考

发现JavaScript中闭包的强大威力

阮一峰闭包

JavaScript闭包的底层运行机制

发现 JavaScript 中闭包的强大威力

相关文章
相关标签/搜索