undefined

vuePress-theme-reco Smalin    2021
undefined

Choose mode

  • dark
  • auto
  • light
首页
分类
  • Linux
  • Vue
  • Nginx
  • 树莓派
  • NodeJs
  • JavaScript
  • CSS
  • git
  • 面试
  • HTML
  • Video
  • Tools
  • VSCode
标签
时间轴
Contact
  • GitHub
  • 简历
author-avatar

Smalin

35

Article

23

Tag

首页
分类
  • Linux
  • Vue
  • Nginx
  • 树莓派
  • NodeJs
  • JavaScript
  • CSS
  • git
  • 面试
  • HTML
  • Video
  • Tools
  • VSCode
标签
时间轴
Contact
  • GitHub
  • 简历
  • JavaScript 核心特性之《闭包》

    • 什么是闭包?
      • 闭包的作用
        • 闭包例子
          • 闭包存在的问题

          JavaScript 核心特性之《闭包》

          vuePress-theme-reco Smalin    2021

          JavaScript 核心特性之《闭包》


          Smalin 2020-08-10 JavaScript

          # 什么是闭包?

          其实闭包这个话题一直也是面试高频题,我在面试当中有 80% 的时候面试官会问我闭包的特性以及实际的应用场景。闭包也确实是 JavaScript 中的核心特性,在实际当中可以说你一直在使用闭包,只不过你并不知道这个是闭包。

          举个例子,一个电子科技爱好者会经常做一些电子零件,但是他并没有上过学,也没有系统的学过这个专业。但是当他准备去大学系统的学习电子专业的时候,其实老师讲过的课他不需要听都可以听得懂,只不过是这些名词儿是他在实验中获得的信息,并不知道叫什么而已。随后只不过是一直的顿悟,知道了这些名字。这也是人们的智慧,给相应的事物提取出一个名字来命名,当大家聊起这个名字的时候,就知道,噢~原来他说的是这个...但是如果你不知道的话,当人们聊起的时候,你其实听不懂说的是什么,但其实你是知道这个的。

          闭包也是一样,往下看看,其实你可能就知道了,也许是你在做项目的时候写过,也或许是你看到过类似的实现形式。

          咳咳。。

          用官方的话来说,闭包可以让你从内部函数访问外部函数作用域。

          就是这么一句话就是闭包的精髓,但其实是听不懂的(至少我在学习 JavaScript 的时候,理解他的字面意思,但是并不知道是什么),我再说说我的理解。

          比较官方的话语来说是,一个函数的内部变量被外部访问。

          用白话来举个例子说是,小明买了一辆汽车,然后为了保护爱车(就像大家买了个新的 iPhone 贴膜一样),买了车窗的贴膜,而且为了私密性(没开车),贴了那种酷酷的黑色,而且这个膜是那种只能在里面看到外面,外面看不到里面的。这你可以理解成是个闭包。(核心一句话就是,可以从里面访问到外面,但是外面无法访问到里面)

          好,我们接下来就讨论一下刚才开头说的, 在实际当中可以说你一直在使用闭包,只不过你并不知道这个是闭包。 这句话的含义。

          大家都用过 function 来声明函数吧,其实呢,你的每一个 function 都是一个闭包,为什么呢?

          我们知道,我们写在 JS 环境中直接声明一个变量,这个变量的作用域是在全局作用域下。

          我们也知道,当声明了一个 function 时,在 function 中,当前的作用域就在函数作用域下。

          所以当你声明一个函数时,并在函数当中声明了变量,处理了一些逻辑,其实这个就是闭包,只不过闭包还有一个要求(我们一会在看),你可以理解为这就是闭包。

          所以:你的每一个 function 都可以理解为是闭包。

          所以:在实际当中可以说你一直在使用闭包,只不过你并不知道这个是闭包。

          # 闭包的作用

          闭包可以干嘛呢?

          在 Web 中,你想要这样做的情况特别常见。大部分我们所写的 JavaScript 代码都是基于事件的 — 定义某种行为,然后将其添加到用户触发的事件之上(比如点击或者按键)。我们的代码通常作为回调:为响应事件而执行的函数。

          编程语言中,比如 Java,是支持将方法声明为私有的,即它们只能被同一个类中的其它方法所调用。

          而 JavaScript 没有这种原生支持,但我们可以使用闭包来模拟私有方法。私有方法不仅仅有利于限制对代码的访问:还提供了管理全局命名空间的强大能力,避免非核心的方法弄乱了代码的公共接口部分。

          我们知道在 JS 中是只有全局作用域和函数作用域的(ES6 开始有块级作用域),如果一个变量只为特定的方法或者类来管理是不可以的,只能通过闭包的形式来做私有化变量。

          var Counter = (function() {
            var privateCounter = 0;
            function changeBy(val) {
              privateCounter += val;
            }
            return {
              increment: function() {
                changeBy(1);
              },
              decrement: function() {
                changeBy(-1);
              },
              value: function() {
                return privateCounter;
              }
            }   
          })();
          
          console.log(Counter.value()); /* logs 0 */
          Counter.increment();
          Counter.increment();
          console.log(Counter.value()); /* logs 2 */
          Counter.decrement();
          console.log(Counter.value()); /* logs 1 */
          

          这次我们只创建了一个词法环境,为三个函数所共享:Counter.increment,Counter.decrement 和 Counter.value。

          该共享环境创建于一个立即执行的匿名函数体内。这个环境中包含两个私有项:名为 privateCounter 的变量和名为 changeBy 的函数。这两项都无法在这个匿名函数外部直接访问。必须通过匿名函数返回的三个公共函数访问。

          这三个公共函数是共享同一个环境的闭包。多亏 JavaScript 的词法作用域,它们都可以访问 privateCounter 变量和 changeBy 函数。

          # 闭包例子

          大家应该知道闭包的含义了吧,如果还不知道,那么你在看一遍?

          还记得开头说过闭包还有一个要求吗?现在来一起看一下这个要求,定义是 定义的内部函数引用了父级(或更上级)作用域的变量

          先来个 MDN 的例子

          function makeFunc() {
              var name = "Mozilla";
              function displayName() {
                  alert(name);
              }
              return displayName;
          }
          
          var myFunc = makeFunc();
          myFunc();
          

          myFunc 是个高阶函数(Higher-Order Function) ,先定义了 myFunc 为 makeFunc(),makeFunc() 方法返回了 displayName 函数,这个函数里引用了他的 父级 作用域变量 name 。此时如果调用了 myFunc() 的话,相当于会执行了 displayName(),然后呢,displayName() 里执行了 alert 来访问父级作用域链的变量 name ,最终呢形成了一个闭包。

          # 闭包存在的问题

          如果不是某些特定任务需要使用闭包,在其它函数中创建函数是不明智的,因为闭包在处理速度和内存消耗方面对脚本性能具有负面影响。

          大家可能都听说过,闭包可能造成内存泄漏,导致程序卡死,崩溃。其实不然,如果你了解闭包,会使用闭包,注意一下闭包的特性,是不会出现这种问题的,这种问题一般都是 JavaScript 新手犯的错误,例如在创建新的对象或者类时,方法通常应该关联于对象的原型,而不是定义到对象的构造器中。原因是这将导致每次构造器被调用时,方法都会被重新赋值一次(也就是说,对于每个对象的创建,方法都会被重新赋值)。

          function MyObject(name, message) {
            this.name = name.toString();
            this.message = message.toString();
            this.getName = function() {
              return this.name;
            };
          
            this.getMessage = function() {
              return this.message;
            };
          }
          

          在上面的代码中,我们并没有利用到闭包的好处,因此可以避免使用闭包。修改成如下:

          function MyObject(name, message) {
            this.name = name.toString();
            this.message = message.toString();
          }
          MyObject.prototype = {
            getName: function() {
              return this.name;
            },
            getMessage: function() {
              return this.message;
            }
          };
          

          但我们不建议重新定义原型。可改成如下例子:

          function MyObject(name, message) {
            this.name = name.toString();
            this.message = message.toString();
          }
          MyObject.prototype.getName = function() {
            return this.name;
          };
          MyObject.prototype.getMessage = function() {
            return this.message;
          };