匿名函数闭包模仿块级作用域,轻松解决开发中的两大难题

简介: 大家都知道在ES6之前,JavaScript是没有块级作用域的,但其实我们是可以通过匿名函数的闭包来模仿实现一个块级作用域,并且可以依靠这样的操作来解决平时开发中的两大难题。

01

什么是块级作用域


在例如Java 、C++等语言中,一个 for循环语句中定义了一个变量 i ,那么该变量就只属于这个 for循环语句块中,即循环结束后,自动销毁该语句块中定义的变量。这个 for循环语句形成的一个作用域就叫做块级作用域


了解过作用域链就能知道,在JavaScript中能形成作用域的就只有函数以及全局。例如下列这个例子


    function input() {  for(var i=0; i < 10; i++) {    console.log(i)  }    alert(i)                //返回10}


    我们可以看到,如果 for语句能形成块级作用域,那么 alert(i) 就应该会报错,但最后却成功返回了一个值,说明 for循环是无法形成块级作用域的。


    02

    如何模仿块级作用域


    上文也说了,JavaScript中函数可以形成一个单独的作用域,所以我们就可以通过定义一个匿名函数并进行自调用的方式来模拟出一个块级作用域来。


    看一下上述例子改造后是什么样子的


      function input() {    (function () {        for(var i=0; i < 10; i++) {            console.log(i)        }    })();    
          alert(i)                //报错: i is not defined}
      input()


      可以看到,我们定义了匿名函数,并将 for循环语句放到了该匿名函数内,同时对该匿名函数进行了自调用。此时的匿名函数内部就是一个单独的作用域,这就是我们模仿出来的块级作用域,当该匿名函数内部的代码全部执行完毕以后,因为该匿名函数没有被引用,所以垃圾回收机制待会儿会对它进行清除,那么匿名函数中定义的临时变量 i 也自然会被清除了。


      紧接着就会进入下一个执行环境,也就是函数 input 所在的作用域内,代码 alert(i) 在当前的执行环境中的作用域链上找不到变量 i 了,所以就会报错 i is not defined。这样做就达到了让一些只是临时使用的变量在用完后被清除从而减少占用内存的目的。


      03

      模仿块级作用域的作用


      上面说了那么多,也介绍了JavaScript如何使用匿名函数来模仿块级作用域,那么模仿块级作用域到底有什么作用呢?


      作用主要就是有两种:


      1. 减少闭包占用内存的问题


      1. 避免多人开发时造成的命名冲突


      (1)减少闭包占用内存的问题


      首先,我们先来看一个简单的例子。假设我们做一个前端页面,页面中有这样一个功能:打开网页时,判断当前的时间,并弹出窗口显示当前时间


        <script>
          var now_time = new Date()  alert(now_time)</script>


        在这个例子中,获取了当前的时间并赋值给变量 now_time ,然后执行代码 alert(now_time)。这段代码只会发生在页面刚打开时,并且后续不会再执行了,即变量 now_time 只被用到了一次,因为我们是将该变量定义在全局中的,所以该变量不会被垃圾回收机制清除,也就是说该变量会占用一定的内存。


        所以我们可以通过匿名函数的闭包来让这段代码执行完后,将后续没有用的变量或函数被垃圾回收机制销毁掉。


          <script>
            (function() {      var now_time = new Date()    alert(now_time)      })()</script>


          仔细阅读这段代码,当执行到该匿名函数时,调用自身,执行函数内的代码,执行完毕后,进入下一个执行环境,而在该匿名函数所形成的作用域内的变量属于局部变量并且没有被引用,所以垃圾回收机制就会清除该变量,释放一定的内存供后续其他变量使用。


          (2)避免多人开发时造成的命名冲突


          在公司,如果上级派一个开发项目给你以及你所在的开发团队,让你们其中4个人(你 、张三 、李四 、王五)负责开发其中一个前端页面。


          第一天张三先在文件 index.js 中写了一些代码;第二天李四也在这个文件中写了一些代码;第三天王五也同样在这个文件中写一些代码;最后到你在这个文件中补充一些功能时,你会发现,之前他们三个写代码时,在全局定义了很多很多的全局变量,这时你在完成功能时就会思考,我定义的变量会不会和他们定义的变量重名啊?如果重名了就会引起一些不必要的bug,而且到时候这些bug会很难被查出来。


          例如这个样子


            /*-------------文件 index.js----------------*/
            /*-----张三写的代码------*/var number = 0setTimeout(() => {  number ++  alert(number)}, 1000)
            /*-----李四写的代码------*/var sum = 0var input = 'hello'
            /*-----王五写的代码------*/var height = 180var weight = 62
            /*-----你写的代码------*/var number = 1              //跟张三定义的变量重名了,引起了bug


            那么此时你会想,那每个人在开发时定义的变量前面都加上独有的前缀就好啦,比如张三开发时定义的变量前缀就是 zs-xxxx 、李四开发时定义的变量前缀就是 ls-xxxx……


            但你会发现这样的命名显得很复杂和臃肿,所以此时你就可以通过匿名函数闭包模仿块级作用域来解决这种问题。


            我们来改进一下上述的代码


              /*-------------文件 index.js----------------*/
              /*-----张三写的代码------*/(function() {  var number = 0  setTimeout(() => {    number ++    alert(number)  }, 1000)})()
              /*-----李四写的代码------*/(function() {  var sum = 0  var input = 'hello'})()
              /*-----王五写的代码------*/(function() {  var height = 180  var weight = 62})()
              /*-----你写的代码------*/(function() {  var number = 1              //此时的变量number为局部变量,没有跟张三定义的变量number冲突})()


              04

              使用匿名函数闭包时的注意点


              接下来讲几点使用匿名函数闭包的注意点,大家一定要看一下


              (1)匿名函数自调用的理解


              有人不懂匿名函数自调用是怎么个意思


                (function() {  //相关代码})()


                其实上面的代码可以通过下面这段代码来理解,但他们不完全相同


                  var func = function() {  //相关代码}
                  func()


                  其实很简单,就相当于先定义了一个匿名函数,然后调用一下它。但要注意的是,匿名函数要用一个小括号包裹起来,然后再使用一个小括号用于调用该匿名函数


                  (2)匿名函数自调用前面要加一个分号;


                  直接来看代码


                    ;(function() {  //相关代码})()


                    在多人开发时,如果用到匿名函数自调用,最好在前面加一个分号,因为有时会遇到这样一个情况


                      function input() {  return function(n) {    alert(n)  }}var output = input()(function() {  //相关代码})()

                      这个例子中,匿名函数自调用的上一行是 input() ,有时解析代码时可能会把它解析成下面这种情况


                        var output = input()(function() { //相关代码 })()


                        这样就变成了,函数的链式调用三次,就会引发奇怪的bug。


                        所以我们只需要在前面加一个分号 ; ,就可以避免这样的尴尬情况了。


                        05

                        结束语


                        希望这篇文章对你们有所帮助。

                        相关文章
                        |
                        4月前
                        |
                        存储 开发者 Python
                        探索代码的奥秘:从变量到函数的编程之旅
                        【8月更文挑战第31天】本文将带你走进编程的世界,从基础的变量概念出发,逐步深入到复杂的函数设计。我们将通过实际的代码示例,探讨如何高效地组织和复用代码,以及在面对复杂问题时如何应用模块化思维来简化解决方案。无论你是初学者还是有经验的开发者,这篇文章都将为你提供新的视角和启发,帮助你更好地理解编程语言的内在逻辑和美学。
                        |
                        4月前
                        |
                        JavaScript 前端开发
                        揭开JavaScript变量作用域与链的神秘面纱:你的代码为何出错?数据类型转换背后的惊人秘密!
                        【8月更文挑战第22天】JavaScript是Web开发的核心,了解其变量作用域、作用域链及数据类型转换至关重要。作用域定义变量的可见性与生命周期,分为全局与局部;作用域链确保变量按链式顺序查找;数据类型包括原始与对象类型,可通过显式或隐式方式进行转换。这些概念直接影响代码结构与程序运行效果。通过具体示例,如变量访问示例、闭包实现计数器功能、以及动态表单验证的应用,我们能更好地掌握这些关键概念及其实践意义。
                        49 0
                        |
                        4月前
                        |
                        存储 JavaScript 前端开发
                        JavaScript——对闭包的看法,为什么要用闭包?说一下闭包原理以及应用场景
                        JavaScript——对闭包的看法,为什么要用闭包?说一下闭包原理以及应用场景
                        40 0
                        |
                        6月前
                        |
                        存储 JavaScript 前端开发
                        技术经验解读:什么是闭包?闭包的优缺点?
                        技术经验解读:什么是闭包?闭包的优缺点?
                        61 1
                        |
                        7月前
                        |
                        存储 编译器 C++
                        C++程序中的函数调用:模块化编程的基石
                        C++程序中的函数调用:模块化编程的基石
                        61 1
                        |
                        7月前
                        |
                        存储 缓存 自然语言处理
                        高阶函数离不开闭包
                        高阶函数离不开闭包
                        |
                        7月前
                        |
                        存储 编译器 C语言
                        “编程界的隐形斗篷:C语言作用域与生命周期的喜怒哀乐”
                        “编程界的隐形斗篷:C语言作用域与生命周期的喜怒哀乐”
                        |
                        7月前
                        |
                        算法 程序员 编译器
                        C ++匿名函数:揭开C++ Lambda表达式的神秘面纱
                        C ++匿名函数:揭开C++ Lambda表达式的神秘面纱
                        195 0
                        |
                        7月前
                        |
                        存储 缓存 Java
                        探秘闭包:隐藏在函数背后的小秘密(下)
                        探秘闭包:隐藏在函数背后的小秘密(下)
                        探秘闭包:隐藏在函数背后的小秘密(下)
                        |
                        7月前
                        |
                        缓存 自然语言处理 前端开发
                        探秘闭包:隐藏在函数背后的小秘密(上)
                        探秘闭包:隐藏在函数背后的小秘密(上)
                        探秘闭包:隐藏在函数背后的小秘密(上)