前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >javaScript核心技术--“闭包”,不看绝对后悔!

javaScript核心技术--“闭包”,不看绝对后悔!

作者头像
Java深度编程
发布2020-06-10 14:51:03
3830
发布2020-06-10 14:51:03
举报
文章被收录于专栏:Java深度编程

“闭包”,又称“定义在函数内部的函数”,闭包技术是javaScript中很关键的核心技术,很多框架的研发或者企业高端技术都需要使用到它。要理解闭包技术,必须先弄明白“变量的作用域”。 1.变量的作用域 javaScript沿袭的java的变量规则,但稍有改进。和java一样可分为“全局变量”和“局部变量”,在javaScript中的“局部变量”又称之为函数变量。

代码语言:javascript
复制


    var x = 999;
 
    function f1() {
      var y = 888;
      console.log(x); 
      console.log(y); 
    }
    f1() // 999  888
    console.log(y); // Uncaught ReferenceError: n is not defined

由上可知javaScript和java一样,父对象的所有变量,对子对象都是可见的,反之则不成立。而有些时候需要在外部父对象中获取子对象区域内部的变量,正常情况下是无法做到的,这时候就需要用到“闭包”技术了。

2.什么是闭包 请先看以下的函数:

代码语言:javascript
复制


    function f1() {
      var n = 999;
      function f2() {
      console.log(n); // 999
      }
    }

上面代码中,函数f2就在函数f1内部,这时f1内部的所有局部变量,对f2都是可见的。但是反过来就不行,f2内部的局部变量,对f1就是不可见的。这就是 JavaScript 语言特有的"链式作用域"结构(chain scope),子对象会一级一级地向上寻找所有父对象的变量。所以,父对象的所有变量,对子对象都是可见的,反之则不成立。上面的代码中内部函数分f2()就是“闭包”,一个定义在函数内部的函数。 3.闭包的用途 3.1. 突破局域限制,读取函数内部的变量值。 逻辑思维分析: 上面我们已经知道了函数f2()就是闭包,那么我们如果去使用它获取函数内部的变量呢? 分析:既然f2可以读取f1的局部变量,那么只要把f2作为返回值,我们不就可以在外部得到返回值,进而间接读取它的内部变量了吗!

代码语言:javascript
复制


    function f1() {
      var n = 999;
      function f2() {
        console.log(n);
      }
      return f2;
    }
 
    var result = f1();
    result(); // 999

上面代码中,函数f1的返回值就是函数f2,由于f2可以读取f1的内部变量,所以就可以在外部获得f1的内部变量了。

3.2.“记住”诞生的环境 闭包最大的特点,就是它可以“记住”诞生的环境,比如f2记住了它诞生的环境f1,所以从f2可以得到f1的内部变量。在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。它可以让这些变量始终保持在内存中,使得它诞生环境一直存在。 现在,假设我们有一个需求:每调用一次函数,都记录这个函数的被调用的次数。如何实现?用我们常规的思维,肯定是定义一个外部变量,然后每调用一次就++,如下所示:

代码语言:javascript
复制


    var start = 0;
    function test_01() {
      start++;
    }
 
    test_01();
    start // 1
    test_01();
    start // 2
    test_01();
    start // 3

你们会发现,上面的方式完美的实现了。但假如需求在改动一下,函数test_01()内部还有一个函数test_02(),要录test_02()函数被调用的次数,这个时候如何实现呢?我们继续按上面的套路搬砖:

代码语言:javascript
复制


    var start = 0;
    function test_01() {
 
        function test_02() {
            start++;
        }
    }
 
    test_01();
    start // 0
    test_01();
    start // 0
    test_01();
    start // 0

这时候你们会发现,无论你调用多少次函数,start都不会增长,一直是0。why? 但如果你把上面的代码改一改,将函数test_02作为返回值,并且外部定义一个变量接受它,就不一样了。

代码语言:javascript
复制


    var start = 0;
    function test_01() {
 
       return function test_02() {
            start++;
        }
    }
 
    var temp = test_01();
    temp ();
    start // 1
    temp ();
    start // 2
    temp ();
    start // 3

可以看到start的值又神奇般的增长了。这究竟是为什么呢?你是否感觉到了想破脑袋也想不明白是为什么?哈哈…… 其实这就是闭包技术的一种体现。用比较科学的术语来技术就是:“temp始终在内存中,而temp的存在依赖于函数test_01(),函数test_01()也因此始终在内存中,不会在调用结束后,被垃圾回收机制回收。所以它才能一直记录下这个‘诞生环境’ ”。 上面的这种解释可能过于“科学语言”,让人难以理解。因此我用比较通俗的语言来解释:因为我在外部声明了一个变量temp,它调用了函数test_01(),而test_01()又返回了函数test_02()。所以上面的代码可以等价与下面的代码:

代码语言:javascript
复制


    var start = 0;
    function test_01() {
 
       return function test_02() {
            start++;
        }
    }
 
    var temp = function test_02() {
            start++;
        };
    temp ();
    start // 1
    temp ();
    start // 2
    temp ();
    start // 3

上面我举的第一个例子就已经很好的说明了,在同一个作用域操作一个变量是可以成功的。这种变换操作手法更this的作用域极其相似。javaScript中this始终指向当前对象,然而this的指向却是动态的。说到这里了我就随便提一提this的作用域吧。 this的作用域

代码语言:javascript
复制


 
    var A = {
      name: '张三',
      describe: function () {
        return '姓名:'+ this.name;
      }
    };
 
    var B = {
      name: '李四'
    };
 
    B.describe = A.describe;
    B.describe()
    // "姓名:李四"
 
    var name = '龚文学';
    var tenp = function () {
        return '姓名:'+ this.name;
      };
 
    temp(); // 姓名:龚文学

从上面可以看出在A对象和B对象调用同一个函数this的指向不同,所以输出了不能的结果。如果把这个函数提取出来,赋值给一个变量,this的指向就是最顶层的window对象,这个时候就输出了我的顶顶大名--“龚文学”。这与“闭包”的方式十分类似,我以此举例说明,希望能帮助大家理解。如果大家还是有不懂的地方,请在微信公众平台《Java深度编程》留言。

3.3.封装对象的私有属性和私有方法

代码语言:javascript
复制


    function Person(name) {
      var _age;
      function setAge(n) {
        _age = n;
      }
      function getAge() {
        return _age;
      }
 
      return {
        name: name,
        getAge: getAge,
        setAge: setAge
      };
    }
 
    var p1 = Person('张三');
    p1.setAge(25);
    p1.getAge() // 25

上面代码中,函数Person的内部变量_age,通过闭包getAge和setAge,变成了返回对象p1的私有变量。因为闭包能一直记住之前的环境,所以Person的内部变量会随之永久改变,这与java的get,set方式十分类似。 4.闭包的弊端 注意,因为外层函数每次运行,都会生成一个新的闭包,而这个闭包又会保留外层函数的内部变量,外层函数多次运行后会导致内存消耗很大。因此不能滥用闭包,否则会造成网页的性能问题。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2018-10-18,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Java深度编程 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档